zui 0.0.5 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,6 +11,8 @@ FocusScope {
11
11
  signal activateRequested()
12
12
  signal textKey(string text)
13
13
  focus: true
14
+ Component.onCompleted: Qt.callLater(function() { root.forceActiveFocus() })
15
+ onVisibleChanged: if (visible) Qt.callLater(function() { root.forceActiveFocus() })
14
16
  Keys.onEscapePressed: if (!blocked) closeRequested()
15
17
  Keys.onDeletePressed: if (!blocked) deleteRequested()
16
18
  Keys.onReturnPressed: if (!blocked) returnRequested()
data/Desktop.qml CHANGED
@@ -64,11 +64,14 @@ QQC.ApplicationWindow {
64
64
  }
65
65
  Text {
66
66
  anchors.horizontalCenter: parent.horizontalCenter
67
+ width: Math.max(0, Math.min(680, window.width - 48))
67
68
  text: service.lastError !== "" ? service.lastError
68
69
  : (Fonts.failed ? "Zui could not load its bundled fonts" : "Starting Zui…")
69
70
  color: Color.foreground
70
71
  font.family: Style.font.family
71
72
  font.pixelSize: Style.font.body
73
+ horizontalAlignment: Text.AlignHCenter
74
+ wrapMode: Text.Wrap
72
75
  }
73
76
  }
74
77
 
data/README.md CHANGED
@@ -1,24 +1,80 @@
1
- # Zui
1
+ <p align="center">
2
+ <img src="docs/assets/readme-hero.svg" width="100%" alt="Zui — native desktop applications in pure Ruby">
3
+ </p>
2
4
 
3
- Zui builds beautiful native desktop applications in pure Ruby, with first-class UI, state,
4
- events, bindings, animation, media, GPU effects, 3D, and application logic. Applications are
5
- Ruby and assets—there is no application UI language to learn alongside Ruby.
5
+ <h1 align="center">Native desktop applications in pure Ruby</h1>
6
6
 
7
- Zui is desktop-only. Its native client intentionally excludes browser-engine payloads while
8
- retaining the complete desktop component catalog.
7
+ <p align="center">
8
+ Build reactive Linux, macOS, and Windows interfaces with one Ruby API, a native Qt renderer,
9
+ and no application-owned QML or browser runtime.
10
+ </p>
9
11
 
10
- This repository is the reusable core. Distribution integrations live at its edges:
12
+ <p align="center">
13
+ <a href="https://rubygems.org/gems/zui"><img alt="RubyGem" src="https://img.shields.io/gem/v/zui?style=flat-square&color=9cff57&labelColor=111711"></a>
14
+ <a href="https://github.com/AdamMusa/zui/actions/workflows/platforms.yml"><img alt="Native platforms" src="https://github.com/AdamMusa/zui/actions/workflows/platforms.yml/badge.svg"></a>
15
+ <img alt="Ruby 3.1 or newer" src="https://img.shields.io/badge/Ruby-%E2%89%A5%203.1-cc342d?style=flat-square&labelColor=111711">
16
+ <img alt="241 components" src="https://img.shields.io/badge/components-241-46e8ff?style=flat-square&labelColor=111711">
17
+ <a href="LICENSE"><img alt="MIT license" src="https://img.shields.io/badge/license-MIT-a79cff?style=flat-square&labelColor=111711"></a>
18
+ </p>
11
19
 
12
- - Linux and Windows desktop applications use the standalone Qt host.
13
- - macOS applications use the same host inside a standard application bundle.
14
- - Omarchy plugins and applications use the separate
15
- [`omarchy-ui`](https://github.com/AdamMusa/omarchy-ui) adapter.
20
+ <p align="center">
21
+ <a href="#quick-start">Start</a> ·
22
+ <a href="#platform-support">Platforms</a> ·
23
+ <a href="#component-catalog">Components</a> ·
24
+ <a href="#reactive-by-design">Reactivity</a> ·
25
+ <a href="#how-zui-runs">Architecture</a> ·
26
+ <a href="#ship-an-application">Distribution</a> ·
27
+ <a href="#showcase-applications">Examples</a> ·
28
+ <a href="https://zui.alkimist.dev">Documentation</a>
29
+ </p>
16
30
 
17
- Zui ships a standard Qt `ApplicationWindow`, a bidirectional `QProcess` bridge, its own neutral
18
- theme and controls, and the full built-in component catalog. It has no runtime dependency on
19
- Quickshell or an Omarchy installation.
31
+ ---
20
32
 
21
- ## Core API
33
+ Zui is a desktop UI framework for Ruby. Application code owns the interface, state, behavior,
34
+ and assets; Zui owns the rendering protocol, native host, platform-neutral QML implementation,
35
+ and complete component catalog. The result is a normal desktop application—not a web page inside
36
+ a window.
37
+
38
+ <table>
39
+ <tr>
40
+ <td width="33%"><strong>One application language</strong><br>Compose UI, state, bindings, events, commands, timers, and application logic in Ruby.</td>
41
+ <td width="33%"><strong>Native desktop renderer</strong><br>Render through Qt Quick, Controls, Multimedia, GPU effects, Shapes, and optional 3D modules.</td>
42
+ <td width="33%"><strong>Private verified runtime</strong><br>Install a checksummed native client without changing system Qt or shell configuration.</td>
43
+ </tr>
44
+ <tr>
45
+ <td><strong>241 named components</strong><br>Use specific controls with validated properties and events instead of a generic markup escape hatch.</td>
46
+ <td><strong>Reactive by default</strong><br>Update only changed properties and publish multi-value transactions as atomic patch batches.</td>
47
+ <td><strong>Portable source</strong><br>Run the same application on Linux, macOS, Windows, or through an environment adapter such as Omarchy UI.</td>
48
+ </tr>
49
+ </table>
50
+
51
+ <p align="center">
52
+ <img src="examples/nova_pour/preview.png" width="880" alt="Nova Pour, a complete pure-Ruby Zui desktop application">
53
+ </p>
54
+
55
+ <p align="center"><sub>Nova Pour is one of the complete applications included in the showcase catalog.</sub></p>
56
+
57
+ ## Quick start
58
+
59
+ Install Ruby 3.1 or newer and the gem. Zui downloads its version-matched native client only when
60
+ you explicitly configure it:
61
+
62
+ ```bash
63
+ gem install zui
64
+ zui doctor --fix
65
+ zui new telemetry-console
66
+ cd telemetry-console
67
+ zui run main.rb
68
+ ```
69
+
70
+ That is the complete development setup. You do not need a Qt SDK, CMake, a C++ compiler, or a
71
+ system-wide Qt installation.
72
+
73
+ `zui doctor --fix` downloads the native client for the installed Zui version, verifies its
74
+ SHA-256 checksum and manifest, and activates it atomically in the user cache. It does not modify
75
+ shell startup files or global Qt environment variables.
76
+
77
+ ## Your first application
22
78
 
23
79
  ```ruby
24
80
  require "zui"
@@ -46,51 +102,52 @@ end
46
102
  Counter.run
47
103
  ```
48
104
 
49
- ## Developer workflow
50
-
51
- ```bash
52
- gem install zui
53
- zui doctor --fix
54
- zui new telemetry-console
55
- cd telemetry-console
56
- zui run main.rb
57
- zui bundle
58
- ```
105
+ The source is ordinary Ruby. Zui validates the declared tree, sends it through a versioned JSON
106
+ protocol, and renders it with native Qt components. Application QML is neither required nor
107
+ accepted through the runtime protocol.
59
108
 
60
- Build and install the gem directly from a checkout:
109
+ ## Reactive by design
61
110
 
62
- ```bash
63
- gem build zui.gemspec
64
- gem install ./zui-0.0.5.gem
65
- ```
111
+ State, bindings, animation, scheduled work, and events live in the same builder context:
66
112
 
67
- `zui doctor --fix` is the one-time setup for each Zui version and platform. It downloads the
68
- matching versioned client from GitHub Releases, verifies its SHA-256 checksum and manifest, and
69
- installs it in the native user cache. `zui configure` performs the same explicit setup operation.
70
- The prebuilt client is never packaged in or downloaded from the RubyGem. It contains the native
71
- host and its Qt/QML engine libraries—not the developer's application, Ruby source, or the Zui
72
- Ruby/QML framework. Setup never changes the global shell environment; `zui run` supplies the
73
- client paths only to the child application.
113
+ ```ruby
114
+ Zui.app do
115
+ state :status, "online"
116
+ state :uptime, 0
74
117
 
75
- Developers install Ruby and the `zui` gem. They do not install CMake, a C++ compiler, or Qt. Those
76
- tools are used only by Zui's release CI to produce the platform clients.
118
+ app :main, title: "Service Monitor", width: 720, height: 480 do
119
+ card padding: 24 do
120
+ column spacing: 12 do
121
+ indicator = badge state.status
122
+ bind(indicator, :value) { state.status.upcase }
123
+ bind(indicator, :foreground) { state.status == "online" ? "#9cff57" : "#ff6f7d" }
77
124
 
78
- `zui bundle` uses a platform template to produce an application directory on Linux and Windows or
79
- a standard `.app` on macOS. The package combines the application's Ruby/assets, the Zui framework
80
- runtime and catalog, and a private copy of the configured native Qt/QML client. No system Qt
81
- installation is used by the finished bundle. The current bundle format expects Ruby 3.1 or newer
82
- on the destination and supports `ZUI_RUBY` when an installer provides a private Ruby executable.
125
+ text { "Uptime: #{state.uptime}s" }
83
126
 
84
- See [Platform support](docs/platforms.md) for host requirements and bundle layouts.
127
+ button "Reconnect" do
128
+ transaction do
129
+ state.status = "online"
130
+ state.uptime = 0
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
85
136
 
86
- `zui doctor` is read-only: it reports platform, Ruby, client, run, and bundle readiness. Add
87
- `--fix` to download and install the missing client from GitHub Releases. Zui has no separate
88
- validation command; Ruby, DSL, protocol, resource, and QML errors are reported by the operation
89
- that encounters them.
137
+ every(1) { state.uptime += 1 }
138
+ end
139
+ ```
90
140
 
91
- ## Reusable application UI
141
+ - `state` defines application-owned values.
142
+ - Value components can take a block for their primary reactive property.
143
+ - `bind` connects any declared property to application state.
144
+ - `transaction` publishes related changes together.
145
+ - `after`, `every`, and `async` schedule application work.
146
+ - `animate` and animation components use native render-side transitions.
147
+ - `dynamic` rebuilds data-dependent child structures without rebuilding the whole application.
92
148
 
93
- Application UI modules are scoped to one builder instead of being mixed into Zui globally:
149
+ Reusable UI modules are scoped to one application builder, so domain components do not leak into
150
+ other applications:
94
151
 
95
152
  ```ruby
96
153
  module TelemetryConsole
@@ -105,30 +162,290 @@ module TelemetryConsole
105
162
  app(:main, title: "Telemetry Console") { dashboard }
106
163
  end
107
164
  end
108
-
109
- def self.run = build.run
110
165
  end
166
+ ```
167
+
168
+ ## Platform support
169
+
170
+ Zui uses the same Ruby API, protocol, renderer, catalog, and application source on every supported
171
+ desktop target. Each release is gated by CI that builds, packages, installs, and launches the
172
+ matching native client.
173
+
174
+ | Operating system | Architecture | Native client | `zui bundle` output | Release status |
175
+ | --- | --- | --- | --- | --- |
176
+ | Linux | x86-64 | `zui-client-linux-x86_64` | Portable application directory | Supported and CI verified |
177
+ | macOS | Apple Silicon | `zui-client-macos-arm64` | Standard `.app` bundle | Supported and CI verified |
178
+ | macOS | Intel x86-64 | `zui-client-macos-x86_64` | Standard `.app` bundle | Supported and CI verified |
179
+ | Windows | x86-64 | `zui-client-windows-x86_64` | Portable application directory | Supported and CI verified |
180
+
181
+ The application bundle expects Ruby 3.1 or newer on the destination. Its launcher prefers
182
+ `ZUI_RUBY`, then the Ruby executable that created the bundle when that path is still available,
183
+ and finally a `ruby` executable on `PATH`. Installers that carry a private Ruby can point
184
+ `ZUI_RUBY` at that executable.
111
185
 
112
- TelemetryConsole.run
186
+ Unsupported architectures fail explicitly during configuration. Zui never silently compiles Qt,
187
+ uses a system Qt installation, or downloads an asset for a different platform. See the complete
188
+ [platform and bundle layouts](docs/platforms.md).
189
+
190
+ ## Component catalog
191
+
192
+ Zui 0.0.7 registers **241 built-in components**. Every entry has a named Ruby builder method,
193
+ property and event schema, native QML renderer, reactive patch support, and contract tests.
194
+
195
+ | Category | Count | Representative components |
196
+ | --- | ---: | --- |
197
+ | Foundation and layout | 28 | `container`, `grid_layout`, `scroll`, `split_view`, `application_window` |
198
+ | Display, content, and media | 24 | `text`, `image`, `markdown`, `video`, `model_view_3d` |
199
+ | Buttons and input | 36 | `button`, `text_field`, `slider`, `date_picker`, `multi_select` |
200
+ | Navigation and structure | 20 | `tabs`, `drawer`, `stack_view`, `breadcrumb`, `key_catcher` |
201
+ | Menus, dialogs, and feedback | 19 | `dialog`, `popup`, `toast`, `progress_ring`, `bottom_sheet` |
202
+ | Data and collections | 22 | `list_view`, `grid_view`, `table_view`, `tree_view`, `calendar` |
203
+ | Charts and visualization | 16 | `line_chart`, `candlestick_chart`, `heatmap`, `gauge`, `legend` |
204
+ | Drawing and interaction | 16 | `canvas`, `shape`, `shader_effect`, `drag_area`, `particle_system` |
205
+ | Animation, state, and timing | 32 | `animation`, `transition`, `timer`, `state_group`, `spring_animation` |
206
+ | Effects | 7 | `multi_effect`, `blur`, `drop_shadow`, `colorize`, `glow` |
207
+ | Multimedia and capture | 12 | `media_player`, `camera`, `audio_input`, `video_output`, `screen_capture` |
208
+ | Models and utilities | 9 | `list_model`, `settings`, `clipboard`, `standard_paths` |
209
+
210
+ Zui does not silently replace unavailable components with screenshots, generic controls, or
211
+ application-specific fallbacks. A declared component either renders as that component or reports
212
+ an explicit error.
213
+
214
+ - Browse the [complete source-backed component reference](https://zui.alkimist.dev/components).
215
+ - Review the [component coverage matrix](docs/component-coverage.md).
216
+
217
+ ## Command-line workflow
218
+
219
+ | Command | Purpose |
220
+ | --- | --- |
221
+ | `zui new NAME` | Generate a pure-Ruby application with a reusable UI module and tests |
222
+ | `zui doctor` | Report Ruby, platform, native-client, run, and bundle readiness without changing anything |
223
+ | `zui doctor --fix` | Download, verify, and install the missing versioned native client |
224
+ | `zui configure` | Perform the same explicit native-client installation directly |
225
+ | `zui run FILE` | Launch a Ruby entry point through the private native client |
226
+ | `zui bundle [DIRECTORY]` | Tree-shake and assemble the application, Zui framework, and native runtime for the current OS |
227
+ | `zui bundle --name NAME --output PATH` | Override the generated product name and destination |
228
+ | `zui bundle --no-tree-shake` | Retain the complete component and Qt feature catalog for metaprogrammed applications |
229
+ | `zui bundle --dist [DIRECTORY]` | Build release installers from the required project-root `config.rb` |
230
+ | `zui version` | Print the installed framework version |
231
+
232
+ Zui has no separate validation command. Ruby, DSL, schema, resource, protocol, and renderer errors
233
+ are reported by the operation that encounters them.
234
+
235
+ ## How Zui runs
236
+
237
+ ```text
238
+ ┌─────────────────────────────────────────────────────────────────────┐
239
+ │ Ruby application │
240
+ │ UI modules · state · bindings · events · commands · assets │
241
+ └──────────────────────────────┬──────────────────────────────────────┘
242
+ │ versioned JSON protocol
243
+ ┌──────────────────────────────▼──────────────────────────────────────┐
244
+ │ Zui native client │
245
+ │ process transport · schema validation · lifecycle · error boundary │
246
+ └──────────────────────────────┬──────────────────────────────────────┘
247
+ │ declared component tree + patches
248
+ ┌──────────────────────────────▼──────────────────────────────────────┐
249
+ │ Platform-neutral renderer │
250
+ │ ControlNode · theme · controls · 241-component QML catalog │
251
+ └──────────────────────────────┬──────────────────────────────────────┘
252
+
253
+ Qt Quick · Controls · Multimedia · GPU · 3D
113
254
  ```
114
255
 
115
- This keeps `main.rb` at one domain-level call and prevents components from one application leaking
116
- into another application's DSL.
256
+ The Ruby process owns application logic. The native client owns the Qt event loop and graphics
257
+ runtime. The renderer applies validated trees and bounded reactive patch batches across the
258
+ process boundary.
259
+
260
+ The client intentionally excludes browser-engine payloads. Zui is desktop-only and does not use
261
+ HTML, CSS, JavaScript, WebView, Electron, or Qt WebEngine to render application interfaces.
262
+
263
+ ## Native client integrity
117
264
 
118
- ## Architecture
265
+ The RubyGem contains the Ruby framework and QML catalog, but not a platform client. Native clients
266
+ are built by the release matrix and attached to the matching GitHub tag.
267
+
268
+ During setup, Zui verifies:
269
+
270
+ 1. The release asset name matches the detected platform and architecture.
271
+ 2. The downloaded archive matches its published SHA-256 checksum.
272
+ 3. Every archive path is safe before extraction.
273
+ 4. `client.json` has the expected format, Zui version, platform, executable, and bundle capability.
274
+ 5. Activation completes atomically in the versioned user cache.
275
+
276
+ The private client includes the host executable, linked Qt libraries, QML modules, plugins, and
277
+ translations required by the catalog. `zui run` exposes those paths only to its child process.
278
+
279
+ ## Ship an application
280
+
281
+ ```bash
282
+ zui bundle
283
+ # or
284
+ zui bundle path/to/application --name "Telemetry Console"
285
+ ```
286
+
287
+ Every distribution combines three deliberately separate payloads:
119
288
 
120
289
  ```text
121
- Ruby application
122
- Zui DSL/state/events
123
- versioned JSON protocol
124
- → native Qt host and process transport
125
- → platform-neutral QML renderer
126
- Qt Quick / Controls / Multimedia / optional modules
290
+ application Ruby source and assets
291
+ + Zui Ruby/QML framework runtime
292
+ + configured native Qt/QML client
293
+ ```
294
+
295
+ | Platform | Generated shape | Suitable next step |
296
+ | --- | --- | --- |
297
+ | Linux | Self-contained directory with `run` and desktop entry | AppImage, Flatpak, Snap, `.deb`, `.rpm`, or Arch package |
298
+ | macOS | Standard `.app` directory | Sign, notarize, and distribute with the application's release identity |
299
+ | Windows | Self-contained directory with `run.cmd` | MSIX, MSI, WiX, Inno Setup, or another installer |
300
+
301
+ No system Qt installation is used by the finished bundle. Signing, notarization, installer format,
302
+ store submission, and application identity remain the release owner's responsibility.
303
+
304
+ `zui bundle` statically analyzes every production Ruby source file, including code in conditional
305
+ branches, and retains only the referenced Zui adapters, QML modules, plugins, and native library
306
+ dependency closure. Test, spec, vendor, temporary, and previous distribution directories do not
307
+ affect the result. The selected components and byte savings are recorded in `zui-bundle.json`.
308
+
309
+ Ruby can compute method names at runtime, so applications that invoke components through
310
+ metaprogramming must declare those possible types in `.zui-bundle.json`:
311
+
312
+ ```json
313
+ {
314
+ "components": ["camera", "video_output"]
315
+ }
316
+ ```
317
+
318
+ Use `--no-tree-shake` only when the possible component set cannot be declared.
319
+
320
+ ### Native installers
321
+
322
+ `zui bundle --dist` creates the same tree-shaken application bundle and then packages it for the
323
+ current operating system:
324
+
325
+ | Host platform | Distribution artifacts |
326
+ |---|---|
327
+ | Linux | `.deb` and `.rpm` |
328
+ | macOS | `.dmg` containing the `.app` and an Applications shortcut |
329
+ | Windows | Inno Setup `.exe` installer |
330
+
331
+ Release packaging requires a `config.rb` file in the project root. It is executable Ruby using a
332
+ validated Zui DSL:
333
+
334
+ ```ruby
335
+ Zui::Dist.configure do
336
+ name "Telemetry Console"
337
+ identifier "com.example.telemetry-console"
338
+ version "1.0.0"
339
+ publisher "Example Company <dev@example.com>"
340
+ description "A native telemetry dashboard."
341
+ license "MIT"
342
+ homepage "https://example.com/telemetry-console"
343
+
344
+ icon linux: "assets/icon.png",
345
+ macos: "assets/icon.icns",
346
+ windows: "assets/icon.ico"
347
+ categories "Utility", "Development"
348
+ end
349
+ ```
350
+
351
+ Only the current platform's icon is required when packaging: PNG or SVG on Linux, ICNS on macOS,
352
+ and ICO on Windows. Paths must remain inside the project. `--output DIRECTORY` selects the artifact
353
+ directory, and existing artifacts are never overwritten. Linux RPM creation requires `rpmbuild`
354
+ (`rpm-build` or `rpm-tools`), macOS uses the system `hdiutil`, and Windows requires Inno Setup 6's
355
+ `ISCC.exe` on `PATH`.
356
+
357
+ The installers carry the application, Zui, Qt, and the selected native dependencies. Like the
358
+ directory bundle, Ruby 3.1 or newer is currently required on the destination; DEB and RPM metadata
359
+ declare that dependency. Code signing, Apple notarization, and Windows Authenticode signing remain
360
+ release-owner steps.
361
+
362
+ ## Showcase applications
363
+
364
+ The repository includes complete Ruby applications rather than isolated visual snippets:
365
+
366
+ | Application | What it demonstrates |
367
+ | --- | --- |
368
+ | [Avatar Runner](examples/avatar_runner/) | Keyboard focus, pointer input, timed physics, Canvas drawing, and atomic state patches |
369
+ | [Nova Pour](examples/nova_pour/) | Image loading, filtering, cart state, dialogs, bindings, and order simulation |
370
+ | [Tesla Drive Dashboard](examples/tesla_drive_dashboard/) | Vehicle simulation, image stacks, Canvas maps, local audio, telemetry, and animation |
371
+ | [Lumen Forge](examples/lumen_forge/) | Compiled shaders, pointer uniforms, GPU effects, charts, and timers |
372
+ | [Cardiac Health Monitor](examples/cardiac_health_monitor/) | Medical imagery, ECG visualization, gauges, heatmaps, particles, and live state |
373
+ | [Orbital Weather Console](examples/orbital_weather_console/) | Image-backed weather scenes, forecasts, gauges, charts, and simulation |
374
+ | [Quantum Market Terminal](examples/quantum_market_terminal/) | Portfolio state, trading simulation, charts, allocation controls, and transactions |
375
+ | [Smart Home Energy](examples/smart_home_energy/) | Room imagery, lighting, device state, energy telemetry, and home simulation |
376
+ | [Cinematic Music Studio](examples/cinematic_music_studio/) | Local and remote media, playback state, seeking, playlists, and audio controls |
377
+
378
+ Run any showcase through the normal client:
379
+
380
+ ```bash
381
+ zui run examples/avatar_runner/main.rb
382
+ zui run examples/tesla_drive_dashboard/main.rb
383
+ zui bundle examples/nova_pour
127
384
  ```
128
385
 
129
- Every declared component renders as that component or reports an explicit component error. Zui
130
- does not silently substitute images, alternate controls, or application-specific fallbacks.
386
+ See the [complete showcase index](examples/).
387
+
388
+ ## Omarchy integration
389
+
390
+ Zui is platform-neutral and has no runtime dependency on Quickshell or Omarchy. The separate
391
+ [`omarchy-ui`](https://github.com/AdamMusa/omarchy-ui) adapter adds shell applications, bar widgets,
392
+ panels, plugin lifecycle, theme integration, and Omarchy packaging while reusing the same Ruby DSL,
393
+ protocol, and component catalog.
394
+
395
+ The dependency direction remains one-way: desktop-environment adapters depend on Zui; Zui never
396
+ contains environment-specific branches.
397
+
398
+ ## Develop Zui itself
399
+
400
+ Clone the repository and run the complete local suite:
401
+
402
+ ```bash
403
+ git clone https://github.com/AdamMusa/zui.git
404
+ cd zui
405
+ scripts/test
406
+ ```
407
+
408
+ The suite covers the Ruby API, state engine, protocol, CLI, distributions, examples, QML contracts,
409
+ QML linting when available, the C++ host build, and an offscreen runtime smoke test.
410
+
411
+ Build and install the gem directly:
412
+
413
+ ```bash
414
+ gem build zui.gemspec
415
+ gem install ./zui-0.0.7.gem
416
+ ```
417
+
418
+ Release CI additionally constructs and audits a relocatable native client, installs the built gem,
419
+ repairs a fresh client cache, and launches a real application on every supported target.
420
+
421
+ ## Troubleshooting
422
+
423
+ | Symptom | Resolution |
424
+ | --- | --- |
425
+ | `Client: not configured` | Run `zui doctor --fix` once for the installed Zui version |
426
+ | Platform or architecture is unsupported | Use one of the release-gated targets above or add a verified native-client runner and artifact |
427
+ | A bundle cannot find Ruby | Install Ruby 3.1+ or point `ZUI_RUBY` at an application-private Ruby executable |
428
+ | A component reports a resource or module error | Check the declared asset path and run `zui doctor`; Zui will not substitute another component |
429
+ | Audio, camera, or capture is unavailable | Confirm OS permissions, devices, codecs, and platform media services |
430
+ | The native client looks stale after a framework update | Run `zui doctor --fix`; client caches are isolated by Zui version |
431
+
432
+ ## Documentation and support
433
+
434
+ | Resource | Purpose |
435
+ | --- | --- |
436
+ | [Official documentation](https://zui.alkimist.dev) | Guides, concepts, and searchable API documentation |
437
+ | [Component reference](https://zui.alkimist.dev/components) | Properties, events, container behavior, and validated Ruby examples for all 241 components |
438
+ | [Platform support](docs/platforms.md) | Native-client setup, target matrix, and OS-specific bundle layouts |
439
+ | [Component coverage](docs/component-coverage.md) | Complete built-in catalog checklist |
440
+ | [GitHub Releases](https://github.com/AdamMusa/zui/releases) | Versioned native clients and SHA-256 checksums |
441
+ | [RubyGems](https://rubygems.org/gems/zui) | Published framework versions |
442
+ | [Issue tracker](https://github.com/AdamMusa/zui/issues) | Focused bug reports and feature proposals |
131
443
 
132
444
  ## License
133
445
 
134
- MIT
446
+ Zui is available under the [MIT License](LICENSE). Bundled third-party fonts and runtime
447
+ dependencies retain their respective licenses; see [third-party notices](THIRD_PARTY_NOTICES.md).
448
+
449
+ <p align="center">
450
+ <strong>Build the interface in Ruby. Let Zui carry it to the desktop.</strong>
451
+ </p>
data/Service.qml CHANGED
@@ -13,6 +13,7 @@ Item {
13
13
  property bool ready: false
14
14
  property bool stopping: false
15
15
  property string lastError: ""
16
+ property string runtimeDiagnostics: ""
16
17
  property var surfaces: ({})
17
18
  property var surfaceOptions: ({})
18
19
  property var nodeIndex: ({})
@@ -158,11 +159,20 @@ Item {
158
159
 
159
160
  surfaces = message.surfaces
160
161
  surfaceOptions = message.surface_options || ({})
161
- nodeIndex = nextIndex
162
+ commitNodeIndex(nextIndex)
162
163
  revision += 1
163
164
  lastError = ""
164
165
  }
165
166
 
167
+ // nodeIndex intentionally keeps a stable object identity. ControlNode observes
168
+ // revision, so publishing a new index object before its revision would expose
169
+ // intermediate patch state and invalidate every renderer in a batch.
170
+ function commitNodeIndex(nextIndex) {
171
+ for (var oldId in nodeIndex)
172
+ if (nextIndex[oldId] === undefined) delete nodeIndex[oldId]
173
+ for (var nextId in nextIndex) nodeIndex[nextId] = nextIndex[nextId]
174
+ }
175
+
166
176
  function validateSurfaceOptions(options) {
167
177
  if (!plainObject(options)) return false
168
178
  var allowed = {
@@ -192,6 +202,8 @@ Item {
192
202
  if (!validSetPatch(message.patches[batchIndex])) return reject("patch batch rejected")
193
203
  for (var applyIndex = 0; applyIndex < message.patches.length; applyIndex++)
194
204
  applySetPatch(message.patches[applyIndex], false)
205
+ // All replacements above are invisible to bindings until this single
206
+ // revision publishes the complete, internally consistent batch.
195
207
  revision += 1
196
208
  return true
197
209
  }
@@ -220,7 +232,7 @@ Item {
220
232
  var containerReplacement = ({ type: node.type, id: node.id, props: node.props || {}, children: message.children })
221
233
  if (node.events !== undefined) containerReplacement.events = node.events
222
234
  childrenIndex[node.id] = containerReplacement
223
- nodeIndex = childrenIndex
235
+ commitNodeIndex(childrenIndex)
224
236
  revision += 1
225
237
  return true
226
238
  }
@@ -250,10 +262,7 @@ Item {
250
262
  if (node.children !== undefined) animatedReplacement.children = node.children
251
263
  if (node.events !== undefined) animatedReplacement.events = node.events
252
264
  animatedReplacement.transitions = validatedTracks
253
- var animatedIndex = ({})
254
- for (var animatedId in nodeIndex) animatedIndex[animatedId] = nodeIndex[animatedId]
255
- animatedIndex[node.id] = animatedReplacement
256
- nodeIndex = animatedIndex
265
+ nodeIndex[node.id] = animatedReplacement
257
266
  revision += 1
258
267
  return true
259
268
  }
@@ -299,10 +308,7 @@ Item {
299
308
  }
300
309
  }
301
310
 
302
- var nextIndex = ({})
303
- for (var id in nodeIndex) nextIndex[id] = nodeIndex[id]
304
- nextIndex[node.id] = replacement
305
- nodeIndex = nextIndex
311
+ nodeIndex[node.id] = replacement
306
312
  if (incrementRevision) revision += 1
307
313
  return true
308
314
  }
@@ -406,6 +412,7 @@ Item {
406
412
 
407
413
  function startRuby() {
408
414
  if (stopping || projectDir === "" || program === "" || transport.running) return
415
+ runtimeDiagnostics = ""
409
416
  transport.start(runtimeExecutable, program, projectDir, rubyLoadPath)
410
417
  }
411
418
 
@@ -424,14 +431,24 @@ Item {
424
431
  function onLineReceived(line) { root.handleLine(line) }
425
432
  function onErrorLineReceived(line) {
426
433
  var message = String(line || "").trim()
427
- if (message !== "") console.warn("zui ruby:", message)
434
+ if (message === "") return
435
+ console.warn("zui ruby:", message)
436
+ var separator = root.runtimeDiagnostics === "" ? "" : "\n"
437
+ root.runtimeDiagnostics = (root.runtimeDiagnostics + separator + message).slice(0, 512)
438
+ Qt.callLater(function() {
439
+ if (!root.ready && !root.transport.running && root.runtimeDiagnostics !== "")
440
+ root.lastError = root.runtimeDiagnostics
441
+ })
428
442
  }
429
443
  function onExited(exitCode) {
430
444
  root.ready = false
431
445
  if (root.stopping || root.projectDir === "") return
432
- root.lastError = exitCode === 0
446
+ var summary = exitCode === 0
433
447
  ? "Zui runtime stopped"
434
448
  : "Zui runtime crashed (exit " + exitCode + ")"
449
+ root.lastError = root.runtimeDiagnostics === ""
450
+ ? summary
451
+ : summary + "\n\n" + root.runtimeDiagnostics
435
452
  restartTimer.interval = root.restartDelayMs
436
453
  restartTimer.start()
437
454
  root.restartDelayMs = Math.min(30000, root.restartDelayMs * 2)