quadraui 0.6.0

Cross-platform Rust UI toolkit: write one AppLogic and run it unmodified on TUI, GTK4 (Linux), macOS, and Windows backends
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
//! # quadraui
//!
//! Cross-platform UI primitives for keyboard-driven desktop and terminal apps.
//!
//! Targets four rendering backends with a single declarative API:
//! - **TUI** (ratatui + crossterm) — full feature parity, works everywhere.
//! - **Linux** (GTK4 + Cairo + Pango) — `gtk4`, full feature parity.
//! - **macOS** (Core Graphics + Core Text) — `objc2`/AppKit. Every
//!   in-window rasteriser shipped; see the crate root `README.md`'s
//!   *Status* section for the two documented divergences from GTK.
//! - **Windows** (Direct2D + DirectWrite) — `windows-rs`. Window creation,
//!   event translation, and platform services are real and exercised by
//!   blocking CI on `windows-latest`, and every per-primitive rasteriser
//!   is shipped; its conformance-matrix column gates like every other
//!   backend's (see `tests/conformance.rs`).
//!
//! ## What's in the box
//!
//! 49 primitives (one module each under `src/primitives/`), each
//! declarative + serde-friendly so apps and Lua plugins can describe UI
//! as data. A representative sample:
//!
//! | Primitive | Use for |
//! |-----------|---------|
//! | [`TreeView`] | File explorers, source-control panels, hierarchical lists |
//! | [`ListView`] | Quickfix, search results, flat selectable lists |
//! | [`Form`] | Settings panels, config editors |
//! | [`Palette`] | Command palettes, fuzzy pickers |
//! | [`StatusBar`] | Mode/file/cursor strips, footer bars |
//! | [`TabBar`] | Editor tabs, document tabs |
//! | [`ActivityBar`] | Vertical icon strips (VSCode-style) |
//! | [`Terminal`] | Cell grids for terminal emulators |
//! | [`TextDisplay`] | Streaming logs, AI chat output |
//!
//! See the crate root `README.md`'s *Primitives* section for the fuller
//! (still not exhaustive) list, or `src/primitives/mod.rs` for the
//! authoritative one.
//!
//! ## How it works
//!
//! Apps build primitive descriptions from their own state. Backends consume
//! the descriptions and rasterise them. Events flow back as `*Event` enums
//! that reference primitives by [`WidgetId`] (owned strings — plugin-safe,
//! no `&'static str`).
//!
//! ```ignore
//! // App (your code) — state-driven, declarative:
//! let bar = quadraui::StatusBar {
//!     id: WidgetId::new("status:editor"),
//!     left_segments: vec![mode_segment(), filename_segment()],
//!     right_segments: vec![lsp_segment(), cursor_segment()],
//! };
//!
//! // Backend (one of the four in-tree ones — see "Backend implementors"
//! // below) — measure + paint:
//! draw_status_bar(cr, &bar, &theme);
//! ```
//!
//! ## Backend implementors
//!
//! [`Backend`] is `pub` but **sealed** — TUI, GTK, Win-GUI, and macOS are
//! the only implementations that will ever exist, and the trait enforces
//! that rather than just documenting it. See [`Backend`]'s
//! own rustdoc for the mechanism (a private supertrait, plus a
//! `compile_fail` doctest proving an out-of-crate `impl Backend` doesn't
//! compile) and the reasoning: a required method can be added to
//! `Backend` in an ordinary PR, with no default and no version bump,
//! whenever a new primitive ships — sealing is what makes that safe
//! rather than a silent trap for a downstream implementor. Want to
//! render onto a target none of the four cover? Contribute a fifth
//! in-tree backend — see `BACKEND.md`.
//!
//! ## Documentation
//!
//! Every file named below ships in the published `.crate` — this list
//! deliberately names only files a consumer of the published crate can
//! actually open.
//!
//! - **`README.md`** (repository root) — quick start, full primitive list,
//!   per-backend status.
//! - **`docs/GUIDE.md`** — build a small two-pane app end to end, from
//!   `cargo new` to something you can run and click on.
//! - **`docs/APP_ARCHITECTURE.md`** — where app state lives vs. what
//!   quadraui owns; read before architecting a consumer app on top of it.
//! - **`docs/CONSUMER_PATTERNS.md`** — recipes for common app shapes
//!   (debug sidebars, source-control panels) built from these primitives.
//! - **`BACKEND.md`** — contributing a new in-tree render backend: mental
//!   model, the three contracts (owned data, measurer-parameterised
//!   algorithms, per-primitive contracts), two-pass paint pattern,
//!   click-intercept hierarchy, implementer checklist.
//! - **`examples/tui_demo.rs`** — runnable ratatui example that
//!   exercises the TabBar + StatusBar contracts end-to-end (cell
//!   units). `cargo run --example tui_demo`.
//! - **`examples/gtk_demo.rs`** — same demo rendered with GTK4 +
//!   Cairo + Pango (pixel units, two-pass paint). Requires the
//!   `gtk` feature: `cargo run --example gtk_demo --features gtk`.
//! - **`docs/decisions/DECISIONS.md`** — running log of API decisions
//!   (which primitives, why this shape, what was deferred), including
//!   D-019, the stability policy for what a `0.x` release may change.
//!
//! ## Status
//!
//! `0.6.x` — pre-1.0, published on crates.io. Breaking changes are
//! grouped into scheduled minor releases and recorded in `CHANGELOG.md`;
//! what a `0.x` release may change is D-019 in
//! `quadraui/docs/decisions/DECISIONS.md`. 49 primitives shipped. The TUI
//! and GTK backends have full feature parity and are battle-tested by
//! vimcode (5000+ tests). The macOS backend implements the whole
//! `Backend` trait and is built/tested for real on `macos-latest` CI. The
//! Windows backend's window/event/platform-services infrastructure is
//! real and CI-blocking on `windows-latest`, and every per-primitive
//! rasteriser is shipped too, with no `todo!()` left in
//! `src/win/backend.rs` — builds, tests and the cross-backend conformance
//! matrix are all blocking on `windows-latest` CI. See the root
//! `README.md`'s *Status* section for the specifics and issue links.
//!
//! ## Plugin invariants (briefly)
//!
//! Applies to every primitive:
//! 1. [`WidgetId`] is owned (`String`) — not `&'static str`.
//! 2. Events are plain data — no Rust closures.
//! 3. Primitives implement `Serialize + Deserialize` — Lua tables map via JSON.
//! 4. WidgetIds are namespaced (e.g. `"plugin:my-ext:send"`).
//! 5. No global event handlers — every event references a `WidgetId`.
//! 6. Primitives don't borrow app state — owned data or explicit `'a`.
//!
//! Verify all six when adding a new primitive or extending an existing one.

// Library code must never write to stdout/stderr — a host embedding
// a quadraui backend (vimcode's TUI, in raw mode on the alternate screen)
// owns the terminal, and a stray `eprintln!`/`println!` lands as raw bytes
// in its live cell grid, bypassing ratatui entirely. Denied here, not just
// asked for at review, so the next print macro added anywhere under `src/`
// fails the build instead of reaching a user's screen. Diagnostics route
// through `diagnostics::emit` instead (see that module).
//
// The handful of genuinely CLI-shaped call sites this deny would otherwise
// break (the GTK headless-smoke harness in `gtk/run.rs`, the macOS
// visual-confirmation dev tool in `macos/headless.rs`, a test-skip notice
// in `gtk/services.rs`) carry their own justified
// `#[allow(clippy::print_stderr)]` — they print to a human running a tool
// directly, not into a host's live UI. `examples/*` and any `bin/` are
// compiled as separate crates and are unaffected by this inner attribute;
// they're expected to print freely.
#![deny(clippy::print_stdout, clippy::print_stderr)]
// `doc(cfg(..))` is a nightly-only rustdoc attribute; docs.rs builds with
// `--cfg docsrs` so feature-gated items can be annotated. A plain build on
// stable never sees this attribute and compiles unchanged.
#![cfg_attr(docsrs, feature(doc_cfg))]
// `cargo doc --no-deps -D warnings` is a CI gate (see `ci.yml`'s `doc`
// job). Two rustdoc lints below are pre-existing debt across ~38 files
// (measured 2026-09-06: ~185 warnings, mostly stale intra-doc links to
// renamed/removed methods, plus doc comments on public re-exports that link
// to private helpers by relative path) that predates this gate and is out of
// scope for the release-metadata work that added it — fixing it
// crate-wide is its own follow-up. Allowed here, narrowly, rather than
// disabling `-D warnings` for the whole doc build: every other rustdoc lint
// (`rustdoc::bare_urls`, `rustdoc::invalid_html_tags`, `missing_docs`-style
// checks, etc.) still fails the build. Do not widen this allow list without
// updating this comment.
#![allow(rustdoc::broken_intra_doc_links, rustdoc::private_intra_doc_links)]

// Not `pub mod`, and not under `primitives/`: `A11yInfo` is shared
// accessibility groundwork, not a widget primitive — no `Layout`, no
// `hit_test`, no `Backend::draw_*` method — so it doesn't belong in the
// primitive count `readme_truth.rs`'s `lib_doc_states_the_real_primitive_count`
// / `root_readme_states_the_real_primitive_count` derive from
// `primitives/mod.rs`'s `pub mod` list, nor is it exposed as its own
// public module path. Re-exported below as `crate::A11yInfo`, its typed
// role/state types (`A11yRole`, `A11yChecked`, `A11yLive`,
// `A11yValueRange`), and the tree builder (`A11yTreeBuilder` and
// friends).
mod a11y;
// Not under `primitives/`, same reasoning as `font_role`/`generic_font`
// below: a blink state machine is not a widget — no `Layout`, no
// `hit_test`, no `Backend::draw_*` method of its own — so it must not
// inflate the primitive count `readme_truth.rs` derives from
// `primitives/mod.rs`'s `pub mod` list.
pub mod caret_blink;
// Not under `primitives/`, same reasoning as `font_role`/`generic_font`
// above: a handle-addressed value store is not a widget — no `Layout`, no
// `hit_test`, no `Backend::draw_*` method of its own — so it must not
// inflate the primitive count `readme_truth.rs` derives from
// `primitives/mod.rs`'s `pub mod` list.
pub mod descriptor_store;
pub mod diagnostics;
pub mod diff;
pub mod focus;
// Not under `primitives/`, for the same reason `a11y` isn't (see the
// comment above `mod a11y`): `font_role` classifies *which font* each
// primitive paints in. It has no `Layout`, no `hit_test`
// and no `Backend::draw_*` method of its own, so it is not a widget
// primitive and must not inflate the primitive count
// `readme_truth.rs`'s `lib_doc_states_the_real_primitive_count` /
// `root_readme_states_the_real_primitive_count` derive from
// `primitives/mod.rs`'s `pub mod` list. It stays a `pub mod` (unlike
// `a11y`) because its module doc *is* the chrome-vs-editor policy every
// pixel backend's `draw_*` methods are written against.
pub mod font_role;
// Not under `primitives/`, same reasoning as `font_role` above:
// `GenericFamily` classifies a font *request*, not a
// widget — no `Layout`, no `hit_test`, no `Backend::draw_*` method of
// its own — so it must not inflate the primitive count `readme_truth.rs`
// derives from `primitives/mod.rs`'s `pub mod` list.
pub mod frame;
pub mod generic_font;
pub mod interaction;
pub mod layout;
pub mod prelude;
pub mod primitives;
pub mod shell;
pub mod style;
pub mod terminal_style;
pub mod testing;
pub mod text_util;
pub mod theme;
// Not under `primitives/`, same reasoning as `font_role`/`generic_font`
// above: a time-based progress helper is not a widget — no `Layout`, no
// `hit_test`, no `Backend::draw_*` method of its own — so it must not
// inflate the primitive count `readme_truth.rs` derives from
// `primitives/mod.rs`'s `pub mod` list.
pub mod transition;
pub mod types;
pub mod undo;

// ── Terminal engine (PTY + vt100 + scrollback) ───────────────────────────────
// Gated behind the `terminal` feature so non-terminal consumers don't pull in
// portable-pty / vt100.
#[cfg(feature = "terminal")]
#[cfg_attr(docsrs, doc(cfg(feature = "terminal")))]
pub mod terminal_engine;

// ── Optional flex/grid layout module ──────────────────────────────────────
// Gated behind the `layout` feature so apps that hand-compute every `Rect`
// today don't pull in `taffy`. Unit-agnostic (plain `f32` in, plain `f32`
// out) and pairs with any backend feature, or none — see `flex`'s module
// doc.
#[cfg(feature = "layout")]
#[cfg_attr(docsrs, doc(cfg(feature = "layout")))]
pub mod flex;

// Small LRU cache for decoded image rasterisations, shared by every pixel
// backend's `Backend::draw_image` that actually uses it.
// Crate-internal — only backend modules need it. `gtk` needs no
// `target_os` gate (gdk_pixbuf runs anywhere); `macos` mirrors `mod
// macos`'s own `all(feature = "macos", target_os = "macos")` gate below,
// since `macos::image` (the only user) is itself gated the same way.
// `win` deliberately does NOT wire this cache in yet — see
// `win::image`'s "No decode cache here yet" module doc for why
// (an `ID2D1Bitmap` is bound to the render target that created it, so
// caching one across `WinBackend::ensure_surface`'s device-lost recovery
// needs its own invalidation logic this change doesn't add) — so `win`
// stays off this gate: turning it on with no real user would leave the
// module provably dead code under `cargo check --features win` on
// Linux (`win::image` itself is `target_os = "windows"`-gated, so that
// check never reaches a call site) and fail that leg's `-D warnings`.
// `tui` needs it not at all — it paints `Image::fallback_text` instead,
// see `tui::image`'s module doc.
#[cfg(any(feature = "gtk", all(feature = "macos", target_os = "macos")))]
pub(crate) mod image_cache;

// Bundled codicon icon font: the PUA codepoints + font bytes every GUI
// backend self-registers so built-in chrome glyphs (tree chevrons, tab
// dirty/close, the context-menu submenu arrow, the data-table sort
// arrow) paint as real vector icons instead of plain text glyphs, with
// no app configuration required. `win` needs no `target_os` gate for
// the same reason `win::text`/`win::backend` don't — only the real
// WinAPI/DirectWrite calls built on these constants are
// `cfg(target_os = "windows")`-gated, so this module itself must
// type-check on every host under `--features win`. TUI is excluded: it
// keeps painting the Unicode glyphs it always has, so TUI output stays
// unchanged.
#[cfg(any(
    feature = "gtk",
    feature = "win",
    all(feature = "macos", target_os = "macos")
))]
pub(crate) mod codicon;

// ── Per-backend rasterisers ─────────────────────────────────────────────────
// Public `draw_*` rasterisers, gated behind feature flags so apps that only
// consume the data layer don't pull in ratatui / gtk4. Lifted out of vimcode
// (`src/tui_main/quadraui_tui.rs`, `src/gtk/quadraui_gtk.rs`) one primitive
// at a time so external apps stop reimplementing the same draw functions.
#[cfg(feature = "gtk")]
#[cfg_attr(docsrs, doc(cfg(feature = "gtk")))]
pub mod gtk;
#[cfg(all(feature = "macos", target_os = "macos"))]
#[cfg_attr(docsrs, doc(cfg(feature = "macos")))]
pub mod macos;
#[cfg(feature = "tui")]
#[cfg_attr(docsrs, doc(cfg(feature = "tui")))]
pub mod tui;
// Unlike `macos` (target-gated in full — see that arm's comment), `win`
// stays available on every host: `src/win/{backend,run}.rs` internally
// `cfg(target_os = "windows")`-gate each real WinAPI call and fall back
// to their original `todo!()` bodies elsewhere, specifically so this
// module keeps compiling on Linux under plain `--features win` (see
// `ci.yml`'s "Compile check (win feature)" step and `Cargo.toml`'s `win`
// feature comment for why that per-repo, not per-OS, check exists).
#[cfg(feature = "win")]
#[cfg_attr(docsrs, doc(cfg(feature = "win")))]
pub mod win;

pub mod compose;

// ── Phase B.1: Backend trait + UiEvent + Accelerator ────────────────────────
// See quadraui/docs/decisions/BACKEND_TRAIT_PROPOSAL.md for design. These modules add
// the unified cross-backend surface alongside the existing per-backend
// free-function draw pattern; no migration yet (that's Phase B.2).
pub mod accelerator;
pub mod backend;
pub mod event;

// ── PaintSurface ────────────────────────────────────────────────────────
// The ~15-verb drawing trait underneath the three pixel backends —
// extracted from helpers each of GtkBackend/MacBackend/WinBackend already
// had privately. `pub`, not `pub(crate)`: this is the public
// backend-paint seam, reached off `Backend::paint_surface` the same way
// `Backend::window`/`Backend::services` expose `WindowControl`/
// `PlatformServices`. TUI is deliberately excluded (see `paint_surface`'s
// module doc) — compiled unconditionally rather than feature-gated:
// a `pub` trait with zero implementors on a `tui`-only build is not
// "dead code" from a library's own crate boundary (an external crate can
// still implement it), so no `-D warnings` dead-code lint applies here the
// way it did back when this was `pub(crate)`.
pub mod paint_surface;

// Host-independent paint-geometry helpers — pure rect/inset
// arithmetic pulled out from behind `src/macos/`'s whole-module
// `target_os = "macos"` gate (mirrors `src/win/msg.rs`'s equivalent split
// for Windows), so `cargo test -p quadraui` exercises it with no
// `--features macos` and no cross-target needed. Deliberately compiled
// unconditionally — see that module's doc for why every item in it also
// carries its own `#[allow(dead_code)]`.
mod paint_geometry;

// ── Phase B.4: cross-backend event routing ──────────────────────────────────
// ModalStack + dispatch free functions. Backends hold one ModalStack and
// call into dispatch to translate raw mouse events into Vec<UiEvent>
// without each backend reimplementing modal-precedence / backdrop-dismiss.
pub mod dispatch;
pub mod modal_stack;

// ── Phase B.5e: runner-crate API ────────────────────────────────────────────
// AppLogic trait + Reaction enum that per-backend `run<A: AppLogic>(app)`
// runners (in `quadraui::tui::run`, `quadraui::gtk::run`) drive against.
// See `docs/BACKEND_SETUP_AUDIT.md` for design rationale.
pub mod runner;
// `ShellAdapter` is constructed only by the TUI/GTK/macOS/Win-GUI shell
// runners. Gate the module on the features that actually consume it rather
// than allowing dead code under `-D warnings`.
#[cfg(any(
    feature = "tui",
    feature = "gtk",
    all(feature = "macos", target_os = "macos"),
    feature = "win"
))]
pub mod shell_adapter;

// Shared runner plumbing (`EventOutcome`, `ReactionSink` + `apply_outcome`,
// `ResizeDebouncer`) used by the shell runners. Gated to prevent dead-code
// warnings on feature combinations that don't use them.
#[cfg(any(
    feature = "tui",
    feature = "gtk",
    all(feature = "macos", target_os = "macos"),
    feature = "win"
))]
mod runtime;

// Shared, backend-neutral desktop-interaction plumbing: window-drag
// arm/threshold/commit, modal-pump re-entrancy guard, headless smoke-mode
// predicates, PointerShape enum-walk scaffold. Compiled unconditionally —
// no toolkit dependency of its own — so a brand-new backend gets it for
// free. See `desktop`'s module doc and `BACKEND.md` §10.
mod desktop;

// Shared text-selection state machine: the region registry plus active-selection
// tracking every `text_selection: true` backend embeds. Gated on the backends
// (TUI, GTK, Win, macOS) that actually use it.
#[cfg(any(
    feature = "tui",
    feature = "gtk",
    feature = "win",
    all(feature = "macos", target_os = "macos")
))]
mod text_selection;

// `BackendCore`: the accelerator registry + `text_selection`
// embedding every one of the four backends above needs, consolidated
// into one place. Gated identically to `text_selection` just above —
// same rationale (no consumer, hard `dead_code` error under `-D
// warnings` otherwise).
#[cfg(any(
    feature = "tui",
    feature = "gtk",
    feature = "win",
    all(feature = "macos", target_os = "macos")
))]
mod backend_core;

pub use a11y::{
    frame_to_tree, A11yChecked, A11yChild, A11yInfo, A11yLive, A11yNodeId, A11yPrimitive, A11yRole,
    A11yTree, A11yTreeBuilder, A11yTreeNode, A11yUpdate, A11yValueRange,
};
pub use caret_blink::CaretBlinkState;
pub use diff::compute_hunks;
pub use focus::FocusManager;
pub use font_role::{ChromePrimitive, EditorClassPrimitive, FontRole};
pub use generic_font::GenericFamily;
pub use primitives::activity_bar::{
    ActivityBar, ActivityBarEvent, ActivityBarHit, ActivityBarLayout, ActivityBarRowHit,
    ActivityBarStyle, ActivityItem, ActivitySide, VisibleActivityItem,
    DEFAULT_ACTIVITY_ICON_SIZE_PX,
};
pub use primitives::board::{
    board_layout, BadgeStatus, BoardAction, BoardCard, BoardColumn, BoardHit, BoardLayout,
    BoardMeasure, BoardModel, CardBadge, CardId, CardLayout, ColumnLayout, MoveDir,
};
pub use primitives::button::{Button, ButtonEvent, ButtonHit, ButtonLayout, ButtonVariant};
pub use primitives::canvas::{Canvas, CanvasHit, CanvasLayout, DrawOp};
pub use primitives::chart::{
    Chart, ChartEvent, ChartHit, ChartKind, ChartLayout, ChartMeasure, Series,
};
pub use primitives::combo_box::{ComboBox, ComboBoxEvent, ComboBoxHit, ComboBoxLayout};
pub use primitives::command_center::{
    CommandCenter, CommandCenterHit, CommandCenterLayout, CommandCenterMeasure,
};
pub use primitives::command_line::{CommandLine, CommandLineLayout, CommandLineMeasure};
pub use primitives::completions::{
    CompletionItem, CompletionItemMeasure, CompletionKind, Completions, CompletionsHit,
    CompletionsLayout, CompletionsPlacement, VisibleCompletion,
};
pub use primitives::context_menu::{
    ContextMenu, ContextMenuGlyphs, ContextMenuHit, ContextMenuItem, ContextMenuItemMeasure,
    ContextMenuLayout, ContextMenuPlacement, ResolvedContextMenuPlacement, VisibleContextMenuItem,
};
pub use primitives::data_table::{
    Column, ColumnAlign, ColumnMeasure, ColumnWidth, DataRow, DataTable, DataTableEvent,
    DataTableGlyphs, DataTableHit, DataTableLayout, ResolvedColumn, SortDirection,
};
pub use primitives::date_picker::{
    CalendarDate, DateFormat, DateLocale, DatePicker, DatePickerEvent, DatePickerHit,
    DatePickerLayout, DayCell, TimeOfDay, Weekday,
};
pub use primitives::dialog::{
    native_dialog_options, Dialog, DialogButton, DialogHit, DialogInput, DialogLayout,
    DialogMeasure, DialogSeverity, DialogTable, DialogTextInput, VisibleDialogButton,
};
pub use primitives::diff_view::{
    DiffDisplayLine, DiffEditability, DiffHeaderGeometry, DiffHunk, DiffLineContent, DiffMode,
    DiffPane, DiffPaneGeometry, DiffRow, DiffRowKind, DiffView, DiffViewGeometry, DiffViewHit,
    DiffViewLayout,
};
pub use primitives::drop_zone::{
    compute_drop_zone, drop_zone_hit_test, drop_zone_overlay, DropEdge, DropGroupRect, DropOverlay,
    DropZone, DropZoneHit, DropZoneKind,
};
pub use primitives::editor::{
    CursorPos as EditorCursorPos, CursorShape as EditorCursorShape, DiagnosticMark,
    DiagnosticSeverity, DiffLine, Editor, EditorCursor, EditorHit, EditorLayout, EditorLine,
    EditorPaintOptions, EditorSelection, EditorStyledSpan, GitLineStatus,
    SelectionCols as EditorSelectionCols, SelectionKind as EditorSelectionKind, SpellMark,
    Style as EditorStyle,
};
pub use primitives::find_replace::{
    compute_hit_regions as compute_find_replace_hit_regions, FindReplaceClickTarget,
    FindReplaceHit, FindReplacePanel, FrHitRegion, FR_PANEL_WIDTH,
};
pub use primitives::float::{Float, FloatHit, FloatLayout, FloatMeasure};
pub use primitives::form::{
    ButtonRowItem, FieldKind, Form, FormEvent, FormField, FormFieldMeasure, FormHit,
    FormItemMeasure, FormLayout, ToggleGroupItem, ValidationState, VisibleFormField,
};
pub use primitives::image::{Image, ImageFit, ImageHit, ImageLayout, ImageSource};
pub use primitives::list::{
    ListItem, ListItemMeasure, ListView, ListViewEvent, ListViewHit, ListViewLayout,
    VisibleListItem,
};
pub use primitives::menu_bar::{
    MenuBar, MenuBarHit, MenuBarItem, MenuBarItemMeasure, MenuBarLayout, VisibleMenuBarItem,
};
pub use primitives::message_list::{MessageList, MessageListHit, MessageListMeasure, MessageRow};
pub use primitives::minimap::{
    aggregate_spans, reserved_width, sample_blocks, sample_window, window_start_line, Minimap,
    MinimapGrid, MinimapHit, MinimapLayout, MinimapLine, MinimapScale, MinimapSizing, MinimapSpan,
    VisibleMinimapLine,
};
pub use primitives::multi_section_view::{
    ActionId as MsvActionId, AuxHit, Axis as MsvAxis, DividerBounds, EmptyBody, HeaderAction,
    HeaderHit, InlineInput, MsvLayoutMetrics, MultiSectionView, MultiSectionViewHit,
    MultiSectionViewLayout, ScrollMode, ScrollbarHit, Section, SectionAux, SectionBody,
    SectionHeader, SectionId, SectionLayout, SectionMeasure, SectionSize,
};
pub use primitives::number_input::{
    NumberInput, NumberInputEvent, NumberInputHit, NumberInputLayout,
};
pub use primitives::palette::{
    Palette, PaletteEvent, PaletteHit, PaletteItem, PaletteItemMeasure, PaletteLayout, PaletteMode,
    PalettePreview, PaletteScrollbar, VisiblePaletteItem,
};
pub use primitives::panel::{
    Panel, PanelAction, PanelHit, PanelLayout, PanelMeasure, VisiblePanelAction,
};
pub use primitives::pipeline_view::{
    PipelineEvent, PipelineHit, PipelineStage, PipelineView, PipelineViewLayout,
    PipelineViewMeasure, StageBounds, StageStatus,
};
pub use primitives::progress::{
    ProgressBar, ProgressBarHit, ProgressBarLayout, ProgressBarMeasure,
};
pub use primitives::radio_group::{
    RadioGroup, RadioGroupEvent, RadioGroupHit, RadioGroupLayout, RadioOption, RadioOptionMeasure,
};
pub use primitives::rich_text_popup::{
    PopupPlacement, PopupScrollbar, RichTextLink, RichTextPopup, RichTextPopupHit,
    RichTextPopupLayout, RichTextPopupMeasure, TextSelection, VisibleRichTextLine,
};
pub use primitives::scroll_view::{ScrollView, ScrollViewEvent};
pub use primitives::scrollbar::{
    clamp_scroll_offset_f32, fit_thumb, scroll_by_clamped_f32, scroll_rows, wheel_delta,
    wheel_row_delta, PressZone, ScrollAxis, Scrollbar,
};
pub use primitives::sidebar_panel::{
    SidebarPanel, SidebarPanelHit, SidebarPanelLayout, SidebarPanelMeasure,
};
pub use primitives::spinner::{Spinner, SpinnerHit, SpinnerLayout, SpinnerMeasure};
pub use primitives::split::{Split, SplitDirection, SplitHit, SplitLayout, SplitMeasure};
pub use primitives::split_tree::{
    SplitTree, SplitTreeDivider, SplitTreeLayout, SplitTreeMeasure,
    MAX_RATIO as SPLIT_TREE_MAX_RATIO, MIN_RATIO as SPLIT_TREE_MIN_RATIO,
};
pub use primitives::status_bar::{
    StatusBar, StatusBarEvent, StatusBarHit, StatusBarHitRegion, StatusBarLayout, StatusBarSegment,
    StatusSegmentMeasure, StatusSegmentSide, VisibleStatusSegment,
};
pub use primitives::switch::{Switch, SwitchEvent, SwitchHit, SwitchLayout};
// `TabBarHits` is deprecated (D-6 pre-refactor hit struct; `TabBarLayout` with
// `Rect` is the replacement). Allow it here since `pub use` of a deprecated
// item is itself a use site. Removal is blocked on the six-method/four-backend
// signature swap to `TabBarLayout` — see `TabBarHits`'s own doc.
#[allow(deprecated)]
pub use primitives::tab_bar::{
    tab_icon_at, tab_icon_cols, SegmentMeasure, TabBar, TabBarEvent, TabBarGlyphs, TabBarHit,
    TabBarHits, TabBarLayout, TabBarSegment, TabChrome, TabFrame, TabIcon, TabItem, TabMeasure,
    VisibleSegment, VisibleTab,
};
pub use primitives::terminal::{
    Terminal, TerminalCell, TerminalCellSize, TerminalCursorShape, TerminalEvent, TerminalHit,
    TerminalLayout, TerminalScrollbar, TerminalSplitHit, TerminalSplitLayout,
};
pub use primitives::text_display::{
    TextDisplay, TextDisplayEvent, TextDisplayHit, TextDisplayLayout, TextDisplayLine,
    TextDisplayLineMeasure, VisibleTextDisplayLine,
};
pub use primitives::text_input::{
    EditOp, TextEditor, TextInput, TextInputHit, TextInputLayout, TextInputMeasure,
    VisibleTextInputLine,
};
pub use primitives::toast::{
    Toast, ToastButton, ToastCorner, ToastFocus, ToastFocusTarget, ToastHit, ToastMeasure,
    ToastOverlay, ToastSeverity, ToastStackLayout, VisibleToast,
};
pub use primitives::toolbar::{
    Toolbar, ToolbarButton, ToolbarHit, ToolbarIcons, ToolbarItemKind, ToolbarItemMeasure,
    ToolbarLayout, ToolbarPaintOptions, ToolbarVAlign, VisibleToolbarItem,
};
pub use primitives::tooltip::{
    ResolvedPlacement, Tooltip, TooltipBorder, TooltipChrome, TooltipHit, TooltipLayout,
    TooltipMeasure, TooltipPlacement,
};
pub use primitives::tree::{
    selected_paths_for_indices, TreeEvent, TreeRow, TreeRowEditState, TreeRowMeasure, TreeView,
    TreeViewHit, TreeViewLayout, VisibleTreeRow,
};
pub use style::Style;
pub use text_util::{
    fuzzy_score, next_char_boundary, prev_char_boundary, safe_prefix, safe_slice,
    snap_to_char_boundary, word_wrap, wrap_spans, WrapPolicy,
};
pub use theme::Theme;
pub use transition::{lerp, lerp_rect, Transition};
pub use types::{
    apply_selection_gesture, Badge, Color, Decoration, Icon, Modifiers, SelectionGesture,
    SelectionMode, StyledSpan, StyledText, TreePath, TreeStyle, WidgetId,
};
pub use undo::UndoStack;

// Phase B.1 re-exports.
pub use accelerator::{
    parse_key_binding, render_accelerator, render_binding, Accelerator, AcceleratorId,
    AcceleratorScope, KeyBinding, ParsedBinding, Platform,
};
pub use backend::{
    Backend, BackendCaps, BackendError, Clipboard, ClipboardFormat, ColorDepth, Display,
    FileDialogOptions, InvalidationLog, MenuStyle, MessageDialogButton, MessageDialogChoice,
    MessageDialogOptions, Metrics, Notification, PlatformFontDefaults, PlatformServices,
    PointerShape, ResizeEdge, ResolvedMenuStyle, RgbaImage, SecretStore, ServiceResult,
    SystemTheme, WindowControl,
};
pub use event::{
    mouse_down, mouse_moved, mouse_up, scroll, window_resized, BackendNativeEvent, ButtonMask, Key,
    MouseButton, NamedKey, Point, Rect, ScrollDelta, UiEvent, UserPayload, Viewport,
};
// The public paint-verb seam — see `paint_surface`'s module doc.
pub use frame::{
    check_frame_order, compose_frame, FrameHitMap, FrameOrderViolation, FramePresence, FrameRung,
    FrameZone, OwnedContent, OwnedFrame, OwnedNode, ScreenLayout, Surface,
};
pub use paint_surface::{PaintSurface, PathVerb};
// `InteractionState` — a single hover/pressed store keyed by `WidgetId`,
// meant to replace the bespoke per-primitive trackers and positional arguments
// (`hovered_id`, `pressed_id`, `hovered_toolbar_id`) the old `Backend` methods
// used to take. New `*_interactive` variants take `&InteractionState`; old
// names survive only as `#[deprecated]` shims per rule 3 in `CLAUDE.md` (both
// are `pub` surface with real downstream call sites). See `interaction.rs`'s
// module doc for the full before/after table and the four index-keyed methods
// deliberately left unmigrated. `examples/common/toolbar_app.rs` demonstrates
// the pattern as an app-owned drop-in replacement.
// Shared layout/hit-test foundation (`Anchor` for overlay positioning,
// `visible_range_walk` replacing per-primitive `Visible*{idx, bounds}` structs).
pub use interaction::InteractionState;
// Shared layout/hit-test foundation (`Anchor` for overlay
// positioning, `visible_range_walk` replacing the per-primitive
// `Visible*{idx, bounds}` structs). Nothing in `primitives/` consumes
// these yet — each primitive converges onto them in its own PR behind a
// `#[deprecated]` shim per `docs/PRIMITIVE_RULES.md` rule 8.
pub use layout::{visible_range_walk, Anchor, Axis, ResolvedSide, Side, VisibleItem};
pub use shell::{ShellApp, ShellConfig, ShellContext};

// Phase B.4 re-exports.
pub use compose::markdown::{
    render_markdown_to_styled, render_markdown_to_styled_wrapped, CodeBlockRange, RenderedMarkdown,
};
pub use compose::{
    filter_help_actions, help_actions_to_palette_items, notify_or_toast, AppShell, AppShellEvent,
    AppShellLayout, BackendWidget, BottomBand, BottomPanelConfig, BottomPanelController,
    BottomPanelEvent, BottomPanelLayout, BottomPanelTab, ChatController, ChatControllerEvent,
    ChatRole, ChatTurn, ContextMenuController, ContextMenuOutcome, DualModePaletteController,
    DualModePaletteEvent, FilePickerController, FilePickerEvent, FilePickerMode, FocusGroup,
    FocusRing, FolderPickerController, FolderPickerEvent, FormController, FormControllerEvent,
    GroupLayout, HelpAction, HelpNote, HelpOverlayController, HelpOverlayEvent, HelpRegistry,
    MenuDef, MenuEvent, MenuSystem, MessageDialogController, MessageDialogEvent, NavigationMode,
    Pane, PaneDragRect, PaneTab, PanelDefinition, SectionKind, ShellPosition, SidebarEvent,
    SidebarPanelBody, SidebarPanelBodyLayout, SidebarPanelChrome, SidebarSectionDef, SidebarSystem,
    StatusBarAction, StatusBarInteraction, TabGroupController, TabGroupEvent, TabGroupLayout,
    ToastStackController, ToastStackEvent, ToolbarHoverTracker, TreeController,
    TreeControllerEvent, ViewHelp, WorkspaceController, WorkspaceDoc, WorkspaceEvent,
    WorkspaceLayout, PALETTE_CHROME_ROWS,
};
pub use dispatch::{
    dispatch_click, dispatch_mouse_down, dispatch_mouse_drag, dispatch_mouse_up, dispatch_scroll,
    text_selection_line_range, DragState, DragTarget, ScrollSurface, SurfaceScrollbar, TextRegion,
};
pub use modal_stack::{ModalEntry, ModalStack};
pub use runner::{AppLogic, Reaction, StepOutcome};

/// Crate version, sourced from `Cargo.toml`.
pub const VERSION: &str = env!("CARGO_PKG_VERSION");

#[cfg(test)]
mod tests {
    use super::*;

    /// First `version = "…"` value inside the manifest's `[package]` table.
    ///
    /// Deliberately stops at the next table header, so a `[dependencies.*]`
    /// entry's own `version` key can never be mistaken for the package's.
    /// `rust-version` is not a false positive either — it does not *start*
    /// with `version`.
    fn package_version(manifest: &str) -> Option<&str> {
        manifest
            .lines()
            .map(str::trim)
            .skip_while(|line| *line != "[package]")
            .skip(1)
            .take_while(|line| !line.starts_with('['))
            .find_map(|line| {
                let value = line
                    .strip_prefix("version")?
                    .trim_start()
                    .strip_prefix('=')?;
                value.trim().strip_prefix('"')?.split('"').next()
            })
    }

    /// [`VERSION`] must be exactly the `[package] version` in `Cargo.toml`.
    ///
    /// The expectation is *derived from the manifest text*, never a
    /// hardcoded literal: a literal makes every release bump two edits
    /// instead of one, and forgetting the second is precisely how the
    /// `v0.1.0` bump turned this test red. `include_str!`
    /// resolves relative to this source file and cargo always ships a
    /// normalised `Cargo.toml` at the package root, so this works inside
    /// the published `.crate` as well as in-tree — on every platform, since
    /// rustc accepts `/` in `include_str!` paths everywhere.
    #[test]
    fn version_matches_cargo_toml() {
        let manifest = include_str!("../Cargo.toml");
        let declared = package_version(manifest).expect(
            "quadraui/Cargo.toml should have a `version = \"...\"` key in its \
             `[package]` table",
        );
        assert_eq!(
            VERSION, declared,
            "CARGO_PKG_VERSION ({VERSION}) disagrees with quadraui/Cargo.toml's \
             [package] version ({declared}) — either the build is reading a stale \
             manifest, or this test's parser no longer matches the manifest layout."
        );
    }

    #[test]
    fn package_version_parser_ignores_other_tables() {
        let manifest = "\
[package]\n\
name = \"quadraui\"\n\
version = \"1.2.3\"\n\
rust-version = \"1.97.1\"\n\
\n\
[dependencies.serde]\n\
version = \"9.9.9\"\n";
        assert_eq!(package_version(manifest), Some("1.2.3"));
    }

    #[test]
    fn package_version_parser_reports_a_missing_key() {
        assert_eq!(package_version("[package]\nname = \"quadraui\"\n"), None);
        assert_eq!(package_version("[dependencies]\nversion = \"1.0\"\n"), None);
    }

    /// The manifest's declared `rust-version`, in the exact
    /// `rust-version = "X.Y"` / `rust-version = "X.Y.Z"` shape
    /// `.github/workflows/ci.yml`'s `msrv` job parses with a `sed`
    /// one-liner — that job's own source of truth for the consumer MSRV
    /// floor it builds against. Returns `None` for anything that shape
    /// doesn't cover (missing key, non-numeric component, a fourth
    /// component, trailing text after the closing quote), mirroring what
    /// that job's parser treats as a parse failure.
    fn declared_rust_version(manifest: &str) -> Option<&str> {
        manifest.lines().map(str::trim).find_map(|line| {
            let value = line
                .strip_prefix("rust-version")?
                .trim_start()
                .strip_prefix('=')?
                .trim();
            let inner = value.strip_prefix('"')?.strip_suffix('"')?;
            let mut parts = inner.split('.');
            let major = parts.next()?;
            let minor = parts.next()?;
            let patch = parts.next();
            if parts.next().is_some() {
                return None;
            }
            let is_digits = |s: &str| !s.is_empty() && s.chars().all(|c| c.is_ascii_digit());
            if !is_digits(major) || !is_digits(minor) {
                return None;
            }
            if let Some(p) = patch {
                if !is_digits(p) {
                    return None;
                }
            }
            Some(inner)
        })
    }

    /// `rust-version` must parse in the exact shape
    /// `.github/workflows/ci.yml`'s `msrv` job expects, or that job's own
    /// `::error::` guard fires on every PR instead of testing the declared
    /// floor. Catches a shape drift here, in-tree, instead of in a red CI
    /// run.
    #[test]
    fn rust_version_parses_in_the_shape_the_msrv_job_expects() {
        let manifest = include_str!("../Cargo.toml");
        assert!(
            declared_rust_version(manifest).is_some(),
            "quadraui/Cargo.toml's rust-version line is missing, or not in \
             the exact `rust-version = \"X.Y\"` / `rust-version = \"X.Y.Z\"` \
             shape `.github/workflows/ci.yml`'s `msrv` job parses with `sed`. \
             Fix the field's shape, and update that job's parser to match if \
             the shape itself is deliberately changing.",
        );
    }

    #[test]
    fn declared_rust_version_rejects_malformed_shapes() {
        assert_eq!(declared_rust_version("rust-version = \"1\"\n"), None);
        assert_eq!(declared_rust_version("rust-version = \"1.92.0.1\"\n"), None);
        assert_eq!(declared_rust_version("rust-version = \"x.y\"\n"), None);
        assert_eq!(
            declared_rust_version("rust-version = \"1.92.0\"  # trailing\n"),
            None
        );
        assert_eq!(declared_rust_version("name = \"quadraui\"\n"), None);
        assert_eq!(
            declared_rust_version("rust-version = \"1.92\"\n"),
            Some("1.92")
        );
        assert_eq!(
            declared_rust_version("rust-version = \"1.92.0\"\n"),
            Some("1.92.0")
        );
    }
}