This is an R&D experiment — exploring what a 2D UI toolkit could look like if built from scratch in modern C++ with no legacy constraints. Nothing here is production-ready.
Take Qt's persistent widget tree, strip away moc and QObject, replace signal/slot strings with C++26 senders, and let model structs generate the UI — via P2996 reflection on C++26, or manual
view()on C++23.
Every major UI toolkit — Qt, GTK, wxWidgets — couples the application and the renderer on a single thread. Rendering competes with business logic for CPU time, and if any step exceeds the frame budget, the UI freezes.
graph TD
subgraph "Traditional single-thread event loop"
A[Process input events] --> B[Fire signal/slot callbacks<br/><i>your model logic runs here</i>]
B --> C[Execute layout pass<br/><i>sequential, blocks frame</i>]
C --> D["Call paintEvent() on each widget<br/><i>rendering happens here</i>"]
D --> E[Present frame]
E -->|"If ANY step > 16ms"| F["Frame drop / frozen UI"]
end
This is an architectural problem, not a tuning problem.
PRISM follows a Model-View-Behavior pattern:
- Model — plain structs with
Field<T>members. Data + change notification. Knows nothing about rendering or input. - View —
Widget<T>specializations. Per-type rendering and widget-level input mechanics, automatic via P2996 reflection. PRISM-internal. - Behavior — user-written
on_change()/observe()chains. Business logic that reacts to field mutations.
The application and renderer are decoupled through a versioned, immutable scene snapshot exchanged via atomic pointer swap. Both threads sleep at OS level when idle — zero CPU when nothing changes.
graph TB
subgraph app["Application Thread"]
Model["Model structs<br/>(Field<T> observables)"]
WT["Persistent WidgetTree<br/>(dirty tracking)"]
Model -->|"Field::set() triggers sender"| WT
end
subgraph snap["Scene Description"]
SS["SceneSnapshot<br/>(immutable, versioned, plain data)<br/>{widget_id → DrawList, rect, z_order, ...}"]
end
subgraph render["Backend Thread"]
SDL["SDL event wait"]
Raster["Rasterise + present"]
SDL --> Raster
end
WT -->|"atomic pointer swap"| SS
SS -->|"load latest"| Raster
SDL -->|"InputEvent callback"| Model
The frame contract: the renderer guarantees frame delivery independent of application state. The application never blocks the renderer. The renderer never calls into application code.
Screenshots are auto-generated: each model is compiled, rendered headlessly, and exported as SVG. Run scripts/update_screenshots.sh to regenerate.
Hello World — define a struct, get a UI. With C++26 reflection, there is no boilerplate:
struct Counter {
prism::Field<int> count{42};
prism::Field<std::string> label{"Hello, PRISM!"};
};
Counter counter;
prism::model_app("My App", counter); // reflection generates the UIOn C++23 (no reflection), add a one-liner view() method — same result:
void view(prism::WidgetTree::ViewBuilder& vb) { vb.vstack(count, label); }Widgets from types — the type inside Field<T> determines the widget. No registration:
struct AudioMixer {
prism::Field<prism::Slider<>> volume{{.value = 0.75, .min = 0.0, .max = 1.0}};
prism::Field<prism::Slider<int>> quality{{.value = 3, .min = 1, .max = 5, .step = 1}};
prism::Field<prism::Checkbox> mute{{.checked = false, .label = "Mute"}};
};Composition — nest structs. Reflection walks them recursively:
struct Settings {
prism::Field<std::string> username{"jeandet"};
prism::Field<bool> dark_mode{true};
};
struct Dashboard {
Settings settings; // nested group — recurses automatically
prism::Field<prism::Slider<>> brightness{{.value = 0.6, .min = 0.0, .max = 1.0}};
prism::Field<prism::Button> apply{{"Apply"}};
};For custom layouts (e.g. hstack), add a view() method:
void view(prism::WidgetTree::ViewBuilder& vb) {
vb.vstack([&] {
vb.component(settings);
vb.hstack(brightness, apply);
});
}Resizable split panes — drop a handle() between two children of any hstack/vstack to let
the user drag the divider; sizes stay content-based until the first drag, then persist and rescale
proportionally on resize:
void view(prism::WidgetTree::ViewBuilder& vb) {
vb.hstack([&] {
vb.component(settings);
vb.handle();
vb.widget(brightness);
});
}Canvas escape hatch — drop to raw drawing when you need it:
struct Waveform {
prism::Field<prism::Slider<>> frequency{{.value = 3.0, .min = 0.5, .max = 8.0}};
void canvas(prism::DrawList& dl, prism::Rect bounds, const prism::WidgetNode& node) {
// Custom drawing: polyline, shapes, theme colors...
}
void view(prism::WidgetTree::ViewBuilder& vb) {
vb.canvas(*this).depends_on(frequency);
vb.widget(frequency);
}
};Plot widget — scientific data visualization with zoom, pan, and crosshair:
struct PlotShowcase {
prism::plot::PlotModel plot;
// ... populate with XYData series ...
void view(prism::WidgetTree::ViewBuilder& vb) {
vb.canvas(plot)
.depends_on(plot.x_range).depends_on(plot.y_range)
.depends_on(plot.view).depends_on(plot.cursor)
.depends_on(plot.revision);
}
};Live inspector — point PRISM at any plain struct behind a Shared<T> and get a live,
per-field editable panel, no mirror struct to hand-write. Built for cross-thread state:
device drivers, background workers, plugin config living on another thread:
struct DeviceState {
float voltage = 3.3f;
int mode = 2;
bool enabled = true;
};
prism::Shared<DeviceState> device_state{DeviceState{}}; // owned/updated elsewhere
prism::inspector::Inspector<DeviceState> inspector(device_state);Inspector<T> is an ordinary component — embed it in a larger UI via vb.component(), or
run it standalone like any model:
prism::model_app("Device Control", inspector);Tree widget — lazy, virtualized tree view over any hierarchical data source, with keyboard
nav (arrow keys expand/collapse/move) and a synced detail panel. On C++26, wrap_struct_tree()
turns any nested struct into a browsable tree via reflection, no adapter code:
struct Sensors { float battery_v = 3.7f; float bus_v = 12.1f; };
struct Device { std::string name = "Controller"; Sensors sensors; int firmware = 12; };
struct TreeShowcase {
Device device;
prism::TreeController ctrl{prism::wrap_struct_tree(device)};
void view(prism::WidgetTree::ViewBuilder& vb) { vb.tree(ctrl); }
};For data that isn't a plain struct — a filesystem, a database, a custom model — implement
TreeSource by hand (Tier 1), or satisfy the TreeStorage concept and adapt it with
wrap_tree_storage() (Tier 2, see examples/model_tree_browser/model_tree_browser.cpp for a filesystem browser).
wrap_struct_tree() (Tier 3) is the reflection-only fast path shown above.
Table widget — virtualized rows with headers and single-row selection. wrap_row_storage()
turns a List<T> of Field<T>-bearing rows into a table via reflection; wrap_column_storage()
adapts any type satisfying ColumnStorage without reflection:
struct Reading {
prism::Field<std::string> sensor{""};
prism::Field<double> value{0.0};
};
struct TableShowcase {
prism::List<Reading> readings;
void view(prism::WidgetTree::ViewBuilder& vb) {
vb.table(readings).headers({"Sensor", "Value"});
}
};Tabs — group content behind a tab bar. Each tab body is an independent ViewBuilder closure,
built lazily:
struct TabsShowcase {
prism::Field<prism::TextField<>> username{{.value = "jeandet"}};
prism::Field<bool> dark_mode{true};
prism::Field<prism::Slider<>> volume{{.value = 0.6}};
prism::Field<prism::TabBar<>> tabs;
void view(prism::WidgetTree::ViewBuilder& vb) {
vb.tabs(tabs, [&] {
vb.tab("Account", [&](prism::WidgetTree::ViewBuilder& tvb) {
tvb.vstack(username, dark_mode);
});
vb.tab("Audio", [&](prism::WidgetTree::ViewBuilder& tvb) {
tvb.widget(volume);
});
});
}
};Inspector annotations — customize how individual fields render in the inspector via C++26 attributes, without changing the field's declared type:
struct Settings {
[[=prism::inspector::skip]] int internal_version;
[[=prism::inspector::readonly]] std::string device_id;
[[=prism::inspector::label<"Sample Rate">]] int sample_rate;
[[=prism::inspector::section<"Audio">]] float volume;
};skip excludes a field entirely (still round-trips safely under the hood); readonly renders
it without input wiring; label overrides the caption; section inserts a header row above
the field.
Same MVB architecture, fully multi-threaded from Python — any thread may mutate the model.
import prism
from typing import Annotated
from pydantic import Field as PField
Vol = Annotated[float, PField(ge=0, le=1)]
class Mixer(prism.Model):
volume: Vol = 0.75 # Annotated → validated via TypeAdapter
mute = prism.checkbox(False, label="Mute")
count = prism.field(42)
volume_slider = prism.slider(0.75, min=0.0, max=1.0)
total = prism.derived(lambda self: self.count.value * 2, "count")
def view(self, vb): # optional — else auto-stacked
vb.hstack(self.volume_slider, self.mute)
vb.widget(self.count)
m = Mixer()
prism.run(m, title="Mixer") # blocks, releases GIL around SDL pump
# from any thread:
m.count.value = 43
conn = Mixer.count.observe(m, lambda v: print(v)) # type-safe, no string name
with prism.transaction():
m.count.value = 1
m.volume = 0.9 # coalesced into one publish| Python | C++ equivalent | Notes |
|---|---|---|
prism.field(default) |
Field<T> |
.value get/set, .observe() |
prism.slider / prism.checkbox |
Field<Slider<>> / Field<Checkbox> |
sentinel widgets |
prism.shared(default) |
Shared<T> |
latest-value, cross-thread |
prism.channel(type_hint) |
Channel<T> |
lossless ordered |
prism.derived(fn, *deps) |
Derived<T> |
recomputed on dep change |
prism.list_field([...]) |
List<T> |
push/erase/observe_* |
prism.transaction() |
prism::transaction([]{...}) |
coalesced publish |
prism.validator_for(Annotated) |
— | Pydantic validation |
prism.run(model) |
model_app(title, model) |
vb trampoline via ViewBuilder |
Also: prism.headless() (display-less run for tests/CI) and prism.run(model, headless_seconds=seconds, until=predicate)
(one-liner CLI form — prism.run(m, headless_seconds=1.0 if "--headless" in sys.argv else None)),
any-thread field.add(n) (atomic increment, avoids read-modify-write),
plot.replace_series()/set_labels() (canvas plot updates), and the @prism.on_change(*deps)
method decorator — see Threading guarantees in
the examples README for the full picture.
Install (editable, requires Meson ≥1.5):
pip install -e . --no-build-isolation
meson test -C builddir # 73 C++ + Python bindings via meson test
pytest python/tests -v # direct pytest alternativePython examples (pure prism.Model, no C++ needed) live in python/examples/: 01_counter (field), 02_mixer (slider/checkbox + view), 03_validation_and_transaction (Annotated/pydantic), 04_background_shared_channel (Shared/Channel + threads), 05_lists_and_derived (List + Derived).
PYTHONPATH=build/python python python/examples/01_counter.pySee Python SDK design for threading (SenderHub snapshot + coalescing queue), GIL handling, and lifecycle details. python/tests/test_prism_python.py is the API reference by example.
Two observable types share the same core (.get(), .set(), .on_change()):
Field<T>— data + observable + widget (reflection generates UI for it)State<T>— data + observable + no widget (backend state, synchronisation)
struct Settings {
prism::Field<std::string> username{"jeandet"}; // → text input
prism::Field<bool> dark_mode{true}; // → checkbox
prism::State<std::string> session_token{""}; // → no widget, still observable
};Field<T> holds only the value — no display label. The member name via P2996 reflection provides identity. Display labels are a form-layout concern.
Both support equality-guarded set() (no spurious notifications) and RAII Connection lifetime on on_change().
Two more observable types round out the reactive core, both usable outside any widget tree:
Derived<T>— read-only value recomputed from other observables, recalculated (and only notifies on actual change) whenever a source fires:prism::Derived<double> total{[&]{ return price.get() * quantity.get(); }, price, quantity};
Shared<T>— atomic cross-thread cell (readers and writers take a brief internal lock (std::atomic<std::shared_ptr>); no lock is held while user code runs) (get()/set()from any thread); the owning thread callsdrain_notifications()to fireon_change()on its own turn. This is what backs theShared<DeviceState>in the live inspector example above — devices/background workers publish into it from another thread.
Wrap a batch of field.set() calls in prism::transaction([&]{ ... }) to coalesce their notifications into one flush at the end of the block instead of firing per-call (see Transaction API).
The type inside Field<T> determines which widget (View layer) renders it. Sentinel types are templated wrappers that encode presentation semantics:
enum class Theme { Light, Dark, System };
struct Editor {
prism::Field<std::string> title{""}; // → read-only text (default for StringLike)
prism::Field<prism::Label<>> status{{"OK"}}; // → read-only label
prism::Field<prism::TextField<>> search{{.placeholder = "Search..."}}; // → editable text field
prism::Field<prism::Password<>> secret{{.placeholder = "API key"}}; // → masked input
prism::Field<prism::Slider<>> volume{{.value = 0.8}}; // → continuous slider
prism::Field<prism::Button> save{{"Save"}}; // → clickable button
prism::Field<bool> dark_mode{true}; // → checkbox
prism::Field<prism::Checkbox> notify{{.checked = true, .label = "Enable"}}; // → checkbox with label
prism::Field<Theme> theme{Theme::Dark}; // → auto-dropdown via reflection
prism::Field<prism::Slider<int>> quality{{.value = 3, .min = 1,
.max = 5, .step = 1}}; // → discrete slider
};Widgets are resolved at compile time via concepts, not concrete types. A widget matches on traits (StringLike, Numeric, ScopedEnum), so custom types work automatically if they satisfy the right concept:
// Your own string type works in Label<> if it satisfies StringLike
prism::Field<prism::Label<MyString>> info{{my_string}};
// Any scoped enum gets an auto-dropdown via P2996 enumerators_of
prism::Field<MyEnum> mode{MyEnum::Default};Models are plain structs. Compose by nesting — no inheritance, no macros:
struct Settings {
prism::Field<std::string> username{"jeandet"};
prism::Field<bool> dark_mode{true};
};
struct Dashboard {
Settings settings; // nested group
prism::Field<int> counter{0};
prism::State<int> request_count{0}; // observable, no widget
};graph TD
D["Dashboard"] --> S["Settings"]
D --> C["counter : Field<int>"]
D --> R["request_count : State<int><br/><i>(no widget)</i>"]
S --> U["username : Field<string>"]
S --> DM["dark_mode : Field<bool>"]
On C++26, P2996 reflection walks the struct members automatically — Field<T> gets a widget, State<T> is skipped, nested structs recurse. On C++23, each model provides a view() method using vstack/hstack:
struct Dashboard {
Settings settings;
prism::Field<int> counter{0};
void view(prism::WidgetTree::ViewBuilder& vb) {
vb.vstack(settings, counter); // Fields and nested components in one call
}
};Both paths produce identical output through an intermediate Node layer. No registration, no moc, no string-based identity.
graph LR
subgraph "Entry Points"
MA["model_app(title, model)<br/><b>Model-driven</b><br/>Reflection generates UI"]
MVU["app<State>(title, view, update)<br/><b>Retained layout</b><br/>Manual composition"]
RAW["App + Frame<br/><b>Raw DrawList</b><br/>No state management"]
end
MA -->|primary| W[WidgetTree + SceneSnapshot]
MVU --> W
RAW --> W
1. Model-driven (primary API) — define model structs, reflection does the rest:
Dashboard dashboard;
prism::model_app("My App", dashboard);2. Retained layout — manual row()/column()/spacer() composition:
prism::app<State>("App", State{},
[](auto& ui) { ui.column([&] { /* ... */ }); },
[](State& s, const prism::InputEvent& ev) { /* ... */ }
);3. Raw DrawList — direct rendering, no state management:
using namespace prism::literals;
prism::App app({.title = "Hello", .width = 800, .height = 600});
app.run([](prism::Frame& frame) {
frame.filled_rect({prism::Point{10_x, 10_y}, prism::Size{200_w, 100_h}},
prism::Color::rgba(0, 120, 215));
});All three entry points take a WindowConfig — model_app() accepts one directly in place of a
bare title string:
prism::model_app({.title = "My App", .width = 900, .height = 600,
.resizable = true, .decoration = prism::DecorationMode::Custom},
dashboard);decoration selects the window chrome:
| Mode | Behavior |
|---|---|
DecorationMode::Custom (default) |
PRISM draws its own title bar, close/min/max buttons, and manual resize edges — works identically across platforms, including Wayland where server-side decoration is often unavailable |
DecorationMode::Native |
Defers to the OS window manager's decoration |
DecorationMode::None |
Borderless, undecorated window |
sequenceDiagram
participant App as Application Thread
participant Snap as SceneSnapshot (atomic)
participant Back as Backend Thread (SDL)
Note over App: Sleeps on stdexec run_loop
Note over Back: Blocks on SDL_WaitEvent
Back->>App: InputEvent via run_loop scheduler
App->>App: Widget handle_input (View)
App->>App: field.set() → on_change (Behavior)
App->>App: Rebuild dirty DrawLists only
App->>Snap: Publish new snapshot (atomic swap)
App->>Back: Wake (SDL_PushEvent)
Back->>Snap: Load latest snapshot
Back->>Back: Rasterise + present
Both threads sleep at OS level when idle (futex / SDL event wait). Zero CPU when nothing changes.
One logic thread owns the widget tree and every Field<T>; Field<T> is not thread-safe. Any thread may call Shared<T>::set(), Channel<T>::send(), or post a closure; these are safe under arbitrary concurrency. Shared<T> publishes only the latest value — intermediate values are dropped by design. Channel<T> is lossless and per-producer FIFO. A posted closure wakes an idle logic thread exactly once per burst. Exceptions thrown by posted or observed callbacks are routed to prism::core::set_unhandled_error_handler (default: printed to stderr) and never stop the drain. transaction() batches and coalesces notifications on the calling thread; it does not roll back on exception.
| Feature | Used for | Required |
|---|---|---|
| Static Reflection (P2996) | Auto-generate UI from model structs | Optional — view() method is the fallback |
std::execution (P2300) |
run_loop event loop, prism::then / prism::on pipe adaptors |
Yes (via stdexec) |
| Senders/receivers | Observer pattern — Field<T>::on_change() + SenderHub |
Yes |
| Concepts & Constraints | Widget resolution (StringLike, Numeric, SliderRenderable), strong type algebra |
Yes (C++20) |
std::expected |
Fallible API operations — no exceptions at API boundary | Yes (C++23) |
| Designated initialisers | Named-parameter widget construction | Yes (C++20) |
| magic_enum | Enum introspection fallback when P2996 is unavailable | Auto — only on pre-C++26 |
Minimum: C++23 compiler (GCC 14+, Clang 17+) and Meson >= 1.5.
With reflection: GCC 16+ with -freflection (auto-detected by the build system). When available, model structs don't need a view() method — P2996 generates the UI automatically.
Without reflection: All model structs must provide a view() method that describes the widget tree via ViewBuilder. Enum dropdowns use magic_enum (fetched automatically as a Meson wrap).
meson setup builddir
ninja -C builddir
meson test -C builddirThe build auto-detects -freflection support and conditionally enables P2996. Dependencies (SDL3, stdexec, doctest, magic_enum) are fetched via Meson wraps.
The same application code builds for the web with Emscripten 6.0.10+. There is no reflection there (upstream clang lacks P2996), so models need a view() method. Live demos: https://jeandet.github.io/PRISM/ (built and deployed by .github/workflows/pages.yml; scripts/build_web_site.sh assembles the same site locally).
source /path/to/emsdk/emsdk_env.sh
meson setup builddir-wasm --cross-file cross/wasm32-emscripten.ini
ninja -C builddir-wasm
meson test -C builddir-wasm # tests run under node
emrun builddir-wasm/examples/model_dashboard/model_dashboard.htmlApplications linking prism_dep get the browser setup automatically: SDL3 from Emscripten's ports, the font bundled in, and JSPI so the event loop can wait without freezing the page. PRISM needs threads, so the page must be served with COOP/COEP headers; emrun sends them. JSPI needs Chrome 137+, Firefox 153+ or Safari 27.
graph LR
P1["Phase 1<br/>Core Infrastructure<br/><b>DONE</b>"]
P2["Phase 2<br/>Layout + Observers<br/><b>DONE</b>"]
P3["Phase 3<br/>Widgets + Rendering<br/><b>DONE</b>"]
P35["Phase 3.5<br/>stdexec<br/><b>DONE</b>"]
P36["Phase 3.6<br/>Node Tree<br/><b>DONE</b>"]
P4["Phase 4<br/>Advanced Features<br/><b>DONE</b>"]
P45["Phase 4.5<br/>Strategic Priorities<br/><b>IN PROGRESS</b>"]
P5["Phase 5<br/>GPU Backend"]
P1 --> P2 --> P3 --> P35 --> P36 --> P4 --> P45 --> P5
- Phase 1 (done) — DrawList, SceneSnapshot, SDL3 backend, event-driven loop
- Phase 2 (done) — Layout engine, hit testing,
Connection/SenderHub,Field<T>,List<T>, P2996 reflection,WidgetTree,model_app() - Phase 3 (done) — Widget dispatch, SDL_Renderer + SDL3_ttf, all built-in widgets (Label, TextField, Password, TextArea, Slider, Button, Checkbox, Dropdown), overlay/popup system, keyboard focus (Tab/Shift+Tab), custom
view()override,canvas()escape hatch, strong coordinate types,clip_pushlocal coordinate system - Phase 3.5 (done) — stdexec
run_loopevent loops,prism::then/prism::onpipe adaptors,AppContext - Phase 3.6 (done) —
Nodeintermediate layer (type-erasedField<T>+ children), pre-C++26 support viaview()methods, magic_enum enum fallback,#if __cpp_impl_reflectionguards (only 2 locations) - Phase 4 (done) — Scroll areas, virtual lists, animation system (easing/spring,
Animation<T>,TransitionGuard<T>), mouse capture & drag, table widget (virtual scroll, headers, row selection), tab bar (TabBar<>, lazy per-tabViewBuilder), plot widget (zoom/pan/crosshair, auto-fit), SVG export, window abstraction + custom chrome, theme palette (runtime-switchable), transaction API (deferred/coalesced callbacks),Derived<T>/Shared<T>state taxonomy, codebase reorganization into 8 modules - Phase 4.5 (in progress) — Custom widget from type (
Widget<T>,is_widget_v,EditState), live object inspector (Inspector<T>/FieldMirror<T>— edit aShared<T>cross-thread with zero boilerplate), inspector field annotations (skip/readonly/label/sectionvia C++26[[=...]]attributes),Tree<T>widget (TreeSource/TreeController,wrap_tree_storage()/wrap_struct_tree(), virtualized rows, keyboard nav), live tree inspector (browse a running app's ownWidgetNodetree, synced detail panel with auto-scroll), Python SDK (nanobind, fully multi-threaded,Field/Shared/Channel/Derived/List,ViewBuildertrampoline,Annotatedvalidation,transaction); broader debugging/profiling story (dirty-region viewer) continues - Phase 5 — Vulkan/WebGPU backend, SDF text, tile compositing
Detailed design rationale for each subsystem lives in doc/design/:
- Threading Model — atomic snapshot handoff (readers never block writers; implementation is
std::atomic<std::shared_ptr>, not guaranteed lock-free), thread roles, input flow - Scene Snapshot — structure, versioning, dirty repaint model
- Draw List — command set, strong coordinate types, local coordinate system, serialisation
- Render Backend — BackendBase vtable, software vs GPU path
- Input Events — input queue, event forwarding, hit testing
- Layout Engine — row/column/spacer, two-pass solver, hit testing
- Field/Sender/Widget Spec — Field, observer pattern, persistent widget tree
- Widgets & Sentinels — concept-driven widgets, all sentinel types (incl. TextArea), overlay system, focus policy
- Input Routing — hit_test → dispatch → delegate handle_input → field mutation
- stdexec Integration — run_loop event loops, prism::then/on pipe adaptors, AppContext
- SDL_Renderer Migration — SDL_Renderer + SDL3_ttf replaces PixelBuffer surface-blit
- Dynamic Node Tree —
Nodeintermediate layer, pre-C++26 support, ViewBuilder→Node→WidgetNode pipeline - Styling — theme as data, context propagation (draft)
- Components —
prism::Componentbase class, self-wiring reusable UI + logic bundles (design only) - Table Widget —
ColumnStorage/RowStorageadapters, virtual scroll, headers, row selection - Tab Widget — tabbed container, active-tab state
- Plot Widget —
PlotModel, per-axis zoom/pan, crosshair, auto-fit - SVG Export — draw commands, viewport-aware
to_svg() - Theme Palette — flat
Themestruct, runtime-switchable palette - Window Abstraction —
Window/WindowConfig, custom chrome, manual resize tracking - Transaction API —
prism::transaction()/TransactionGuard, coalesced notifications - State Taxonomy —
Derived<T>signal graph,Shared<T>cross-thread cell - Custom Widget From Type —
Widget<T>,is_widget_v,EditState, user extension story - Live Object Inspector —
Inspector<T>/FieldMirror<T>reflection synthesis - Inspector Field Annotations —
skip/readonly/label/sectionC++26 attributes - Tree Widget —
TreeSource, 3-tier adapter API, virtualized rows, keyboard nav - Live Tree Inspector — live
WidgetNodetree browser, dual-window event loop, detail panel auto-scroll - Python SDK — nanobind, fully multi-threaded, GIL-free 3.14+, posted-mutation queue, ViewBuilder trampoline
MIT
