omarchy-ui 0.0.6-x86_64-linux → 0.0.8-x86_64-linux

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8c8e67a6ee733ab185881c3af8614d428d5888fcea3fed015780ccc4bae6a3b5
4
- data.tar.gz: 648c98f47aea9d30e857809a65052bdb2ca374ecaebc122a3ea192b99aa2b815
3
+ metadata.gz: bbe75336899d8f9674166011c929469db798869f88b32f45a3b61505b0713d01
4
+ data.tar.gz: bb5f2e4f0dfec4578992d17d5f3fdc5530d5b2c73d4aeebcfada553355077f94
5
5
  SHA512:
6
- metadata.gz: 5f7664f1dafd992a22872bc029e8c48a242452ad5a8df9185a2aa0707b94b14df8724bd2e8c4fe3bd79afedeaf957e838fb1e27ecabb9467ba0ec41806e8f6d8
7
- data.tar.gz: 97ec7a87a20e40653d8247cf69fd2534febb44b89ea7601355ada885d5f1978753332418ae756c9cf2e934a5534d108f43a850eca1c2ef3dc1c741435243d4ea
6
+ metadata.gz: 9776119537e37548ea84ca4129647a6f94b64944ae75315e776db661220aefeb4cc80527ed93a1b1c9410e0b80d2a547c77a2aae6674f770decc354bdee2a505
7
+ data.tar.gz: 6856581d58533845967d37daae2679cbf9601f9555aa724b9a9cbf04da0a62a8b15cd146df6d3b36551dec85ad29a13189ba1bdb01a143c2a3cfed87f46d2bb4
data/App.qml CHANGED
@@ -2,6 +2,7 @@ import QtQuick
2
2
  import Quickshell
3
3
  import qs.Commons
4
4
  import qs.Ui as OmarchyUi
5
+ import "Theme" as ZuiTheme
5
6
 
6
7
  ShellRoot {
7
8
  id: root
@@ -82,7 +83,7 @@ ShellRoot {
82
83
  }
83
84
  }
84
85
 
85
- ZuiRenderer {
86
+ ControlNode {
86
87
  id: renderer
87
88
  anchors {
88
89
  left: parent.left
@@ -91,7 +92,7 @@ ShellRoot {
91
92
  bottom: parent.bottom
92
93
  margins: Style.space(24)
93
94
  }
94
- visible: renderReady
95
+ visible: ZuiTheme.Fonts.ready && root.activeSurface !== "" && service.rootId(root.activeSurface) !== ""
95
96
  bridge: service
96
97
  surfaceName: root.activeSurface
97
98
  controlId: service.rootId(surfaceName)
@@ -103,7 +104,7 @@ ShellRoot {
103
104
  anchors.centerIn: parent
104
105
  visible: !renderer.visible
105
106
  text: service.lastError !== "" ? service.lastError
106
- : "Starting Ruby UI with Zui…"
107
+ : (ZuiTheme.Fonts.failed ? "Omarchy UI could not load Zui's bundled fonts" : "Starting Ruby UI…")
107
108
  color: Color.foreground
108
109
  font.family: Style.font.family
109
110
  font.pixelSize: Style.font.body
data/BarWidget.qml CHANGED
@@ -1,6 +1,7 @@
1
1
  import QtQuick
2
2
  import qs.Commons
3
3
  import qs.Ui
4
+ import "Theme" as ZuiTheme
4
5
 
5
6
  BarWidget {
6
7
  id: root
@@ -16,10 +17,10 @@ BarWidget {
16
17
  implicitWidth: Math.max(Style.bar.iconSlot, renderer.implicitWidth + Style.space(12))
17
18
  implicitHeight: barSize
18
19
 
19
- ZuiRenderer {
20
+ ControlNode {
20
21
  id: renderer
21
22
  anchors.centerIn: parent
22
- visible: renderReady
23
+ visible: ZuiTheme.Fonts.ready && root.rubyService && root.rootControlId !== ""
23
24
  bridge: root.rubyService
24
25
  surfaceName: root.surfaceName
25
26
  controlId: root.rootControlId
data/Panel.qml CHANGED
@@ -3,6 +3,7 @@ import Quickshell
3
3
  import Quickshell.Wayland
4
4
  import qs.Commons
5
5
  import qs.Ui as OmarchyUi
6
+ import "Theme" as ZuiTheme
6
7
 
7
8
  Item {
8
9
  id: root
@@ -77,10 +78,10 @@ Item {
77
78
  onClicked: {}
78
79
  }
79
80
 
80
- ZuiRenderer {
81
+ ControlNode {
81
82
  id: renderer
82
83
  anchors.centerIn: parent
83
- visible: renderReady
84
+ visible: ZuiTheme.Fonts.ready && root.service && root.rootControlId !== ""
84
85
  bridge: root.service
85
86
  surfaceName: root.surfaceName
86
87
  controlId: root.rootControlId
@@ -97,7 +98,7 @@ Item {
97
98
  anchors.horizontalCenter: parent.horizontalCenter
98
99
  text: root.service && root.service.lastError !== ""
99
100
  ? root.service.lastError
100
- : "Starting Ruby UI with Zui…"
101
+ : (ZuiTheme.Fonts.failed ? "Omarchy UI could not load Zui's bundled fonts" : "Starting Ruby UI…")
101
102
  color: Color.foreground
102
103
  font.family: Style.font.family
103
104
  font.pixelSize: Style.font.body
data/README.md CHANGED
@@ -10,7 +10,7 @@
10
10
  </p>
11
11
 
12
12
  <p align="center">
13
- <img alt="Omarchy UI 0.0.6" src="https://img.shields.io/badge/Omarchy_UI-0.0.6-b7ff5a?style=flat-square&labelColor=111711">
13
+ <img alt="Omarchy UI 0.0.8" src="https://img.shields.io/badge/Omarchy_UI-0.0.8-b7ff5a?style=flat-square&labelColor=111711">
14
14
  <img alt="Ruby 3.1 or newer" src="https://img.shields.io/badge/Ruby-3.1%2B-cc342d?style=flat-square&logo=ruby&logoColor=white">
15
15
  <img alt="Zui 0.0.10" src="https://img.shields.io/badge/Zui-0.0.10-7ee14b?style=flat-square&labelColor=111711">
16
16
  <img alt="241 components" src="https://img.shields.io/badge/components-241-75d943?style=flat-square&labelColor=111711">
@@ -53,8 +53,8 @@ and the Omarchy adapter.
53
53
 
54
54
  | Build | Use it for | Primary surface | Delivery |
55
55
  | --- | --- | --- | --- |
56
- | Omarchy application | Full desktop tools and standalone interfaces | `app :main` | `omarchy_ui run` or `omarchy_ui bundle` |
57
- | Omarchy plugin | Bar widgets, panels, and shell-integrated tools | `bar_widget` and `panel` | `omarchy_ui validate` and `omarchy_ui push` |
56
+ | Omarchy application | Full desktop tools and standalone interfaces | `app :main` | `omarchy_ui new NAME`, then `run` or `bundle` |
57
+ | Omarchy plugin | Bar widgets, panels, and shell-integrated tools | `bar_widget` and `panel` | `omarchy_ui new NAME --plugin`, then `push` or `bundle` |
58
58
  | Portable Zui application | The same application UI outside Omarchy | `app :main` | The platform-neutral [Zui toolchain](https://github.com/AdamMusa/zui#developer-workflow) |
59
59
 
60
60
  ## Requirements
@@ -63,11 +63,11 @@ and the Omarchy adapter.
63
63
  - Ruby 3.1 or newer while installing the gem and developing applications;
64
64
  - optional Qt modules for components that use features such as Quick 3D or Multimedia.
65
65
 
66
- The finished Omarchy bundle includes the Omarchy adapter and mruby executable. Shared renderers,
67
- controls, theme, and fonts stay in the compatible Zui gem instead of being duplicated into every
68
- plugin. The destination therefore needs Ruby and `omarchy-ui` installed. A component fails with an
69
- explicit error when its gem, required Qt module, or resource is unavailable; Omarchy UI never
70
- substitutes a different component.
66
+ Omarchy UI generates a complete Omarchy-compatible application or plugin package from the
67
+ developer's Ruby and assets. It owns the Omarchy metadata, entry points, adapter, validation,
68
+ tree-shaken Zui QML, and embedded mruby runtime. The finished package does not need Ruby or framework
69
+ gems. A component fails with an explicit error when its required Qt module or resource is unavailable;
70
+ Omarchy UI never substitutes a different component.
71
71
 
72
72
  ## Quick start
73
73
 
@@ -79,20 +79,37 @@ gem install omarchy-ui
79
79
 
80
80
  The gem installs the compatible Zui version as a dependency.
81
81
 
82
- ### 2. Generate an application
82
+ ### 2. Generate a complete project
83
83
 
84
84
  ```bash
85
85
  omarchy_ui new Counter
86
86
  cd counter
87
87
  ```
88
88
 
89
+ For an integrated Omarchy plugin instead:
90
+
91
+ ```bash
92
+ omarchy_ui new "System Status" --plugin
93
+ cd system-status
94
+ ```
95
+
96
+ Both commands create a ready-to-run Ruby project. Omarchy UI generates the complete compliant
97
+ application or plugin package; developers do not write QML, shell entry points, or an Omarchy
98
+ manifest by hand.
99
+
100
+ Each project includes a Ruby-native `config.rb` containing its type, identity, release version,
101
+ entry point, and—for plugins—public marketplace metadata. `bundle`, `validate`, and `push` read this
102
+ single configuration when assembling the complete package.
103
+
89
104
  The generator creates a small, host-independent project:
90
105
 
91
106
  ```text
92
107
  counter/
108
+ ├── LICENSE
93
109
  ├── app.rb # reusable Zui application
94
110
  ├── components/
95
111
  │ └── welcome.rb # application-owned Ruby UI module
112
+ ├── config.rb # project identity and build settings
96
113
  ├── main.rb # Omarchy launcher
97
114
  └── README.md
98
115
  ```
@@ -103,7 +120,7 @@ counter/
103
120
  omarchy_ui run main.rb
104
121
  ```
105
122
 
106
- `run` bundles the project's Ruby entry point, prepares the Omarchy host, resolves the exact Zui gem,
123
+ `run` bundles the project's Ruby entry point, asks Zui to tree-shake the QML runtime, prepares the Omarchy host,
107
124
  then launches it through Quickshell.
108
125
 
109
126
  ## Application model
@@ -229,48 +246,18 @@ QML is not required.
229
246
 
230
247
  ## Omarchy plugins
231
248
 
232
- A Ruby-powered plugin is an ordinary Omarchy plugin repository with `manifest.json` and `main.rb`
233
- at its root. Omarchy UI adds the generated service, panel, bar-widget, framework, and runtime files
234
- while staging the plugin; do not commit generated copies to the application source.
249
+ Generate a complete plugin project with one command:
235
250
 
236
- ```text
237
- system-status/
238
- ├── manifest.json
239
- ├── main.rb
240
- └── assets/
251
+ ```bash
252
+ omarchy_ui new "System Status" --plugin
253
+ cd system-status
241
254
  ```
242
255
 
243
- ### Manifest
244
-
245
- This complete manifest declares the adapter surfaces used by the Ruby definition below:
246
-
247
- ```json
248
- {
249
- "schemaVersion": 1,
250
- "id": "example.system-status",
251
- "name": "System Status",
252
- "version": "0.1.0",
253
- "author": "Example Author",
254
- "license": "MIT",
255
- "description": "A Ruby-powered status panel for Omarchy.",
256
- "kinds": ["service", "bar-widget", "panel"],
257
- "keepLoaded": true,
258
- "entryPoints": {
259
- "service": "Service.qml",
260
- "barWidget": "BarWidget.qml",
261
- "panel": "Panel.qml"
262
- },
263
- "barWidget": {
264
- "displayName": "System Status",
265
- "description": "Open the current system status.",
266
- "category": "System",
267
- "allowMultiple": false,
268
- "defaultSection": "right"
269
- }
270
- }
271
- ```
256
+ The generated project already satisfies Omarchy's structural requirements. Developers work in Ruby
257
+ and add their own assets; Omarchy UI owns the required plugin metadata, shell surfaces, native host,
258
+ tree-shaken Zui package, validation, and install lifecycle.
272
259
 
273
- ### Ruby entry point
260
+ The Ruby entry point uses ordinary Zui components:
274
261
 
275
262
  ```ruby
276
263
  # main.rb
@@ -295,35 +282,29 @@ OmarchyUI.plugin do
295
282
  end
296
283
  ```
297
284
 
298
- `bar_widget` declares the shell-owned bar surface. `panel :status` declares the panel opened by
299
- `open_panel :status`. The manifest must declare the corresponding entry points.
285
+ `bar_widget` and `panel :status` are the complete developer-facing surface declarations. Omarchy UI
286
+ maps them to the required Omarchy shell entry points when it builds or stages the plugin.
300
287
 
301
288
  ### Validate and install
302
289
 
303
290
  ```bash
304
- omarchy_ui validate path/to/system-status
305
- omarchy_ui push path/to/system-status
291
+ omarchy_ui validate
292
+ omarchy_ui push
306
293
  ```
307
294
 
308
- `validate` checks a fully staged copy, including the bundled Ruby entry point and generated adapter.
309
- `push` then:
310
-
311
- 1. stages and validates the complete plugin;
312
- 2. backs up an existing installation when present;
313
- 3. installs it atomically under `~/.config/omarchy/plugins/<plugin-id>`;
314
- 4. enables the plugin; and
315
- 5. restarts the Omarchy shell.
295
+ `validate` generates and checks the complete plugin without mutating the Ruby project. `push` safely
296
+ builds, validates, installs, enables, and reloads the plugin for development.
316
297
 
317
298
  During development, suppress either lifecycle action when needed:
318
299
 
319
300
  ```bash
320
- omarchy_ui push path/to/system-status --no-enable
321
- omarchy_ui push path/to/system-status --no-restart
301
+ omarchy_ui push --no-enable
302
+ omarchy_ui push --no-restart
322
303
  ```
323
304
 
324
305
  > [!WARNING]
325
306
  > Omarchy plugins execute as unsandboxed user code inside the shell environment. Review the Ruby,
326
- > assets, manifest, generated package, and provenance before installing a third-party plugin.
307
+ > assets, generated package, and provenance before installing a third-party plugin.
327
308
 
328
309
  ## Command-line reference
329
310
 
@@ -333,9 +314,10 @@ omarchy_ui <command> [arguments]
333
314
 
334
315
  | Command | Arguments | Result |
335
316
  | --- | --- | --- |
336
- | `omarchy_ui new NAME` | One application name | Creates a new application directory using a URL-safe form of the name |
317
+ | `omarchy_ui new NAME` | One project name | Creates a complete Omarchy application project |
318
+ | `omarchy_ui new NAME --plugin` | One project name and `--plugin` | Creates a complete Omarchy-compliant plugin project |
337
319
  | `omarchy_ui run FILE` | One Ruby entry point | Runs an application through the Omarchy/Quickshell host |
338
- | `omarchy_ui bundle [DIRECTORY]` | Zero or one project directory | Creates `dist/<project-name>` with the application, Omarchy adapter, and runtime; shared UI comes from Zui |
320
+ | `omarchy_ui bundle [DIRECTORY]` | Zero or one project directory | Generates the complete distributable package and prints its location |
339
321
  | `omarchy_ui validate [DIRECTORY]` | Zero or one plugin directory | Stages the complete plugin and delegates validation to `omarchy plugin validate` |
340
322
  | `omarchy_ui push [DIRECTORY]` | Optional `--no-enable` and `--no-restart` | Atomically installs and activates a validated plugin |
341
323
  | `omarchy_ui version` | None | Prints the installed Omarchy UI version |
@@ -345,36 +327,19 @@ not forward arbitrary Ruby arguments.
345
327
 
346
328
  ## Packaging
347
329
 
348
- Create a distributable application from the project root:
330
+ Create the complete distributable package from the project root:
349
331
 
350
332
  ```bash
351
333
  omarchy_ui bundle
352
- ./dist/my-application/run
353
- ```
354
-
355
- The bundle contains:
356
-
357
- ```text
358
- dist/my-application/
359
- ├── main.rb # bundled application source
360
- ├── assets/ # application-owned resources
361
- ├── App.qml # Omarchy host entry point
362
- ├── Service.qml # process and surface bridge
363
- ├── Panel.qml # shell panel adapter
364
- ├── BarWidget.qml # shell bar adapter
365
- ├── ZuiRenderer.qml # resolves Zui's shared protocol renderer
366
- ├── Commons@ Ui@ # links to Omarchy's shell modules
367
- ├── omarchy-ui-runtime # embedded mruby executable
368
- ├── omarchy-ui-runtime.sha256
369
- ├── RUNTIME_PROVENANCE.md
370
- └── run
371
334
  ```
372
335
 
373
- Plugin bundles use the same runtime package and are validated against their manifest before the
374
- bundle command succeeds. Generated packages exclude `.git`, previous `dist` output, stale
375
- adapter/runtime files, and every Zui-owned QML directory. Install `omarchy-ui` on the destination so
376
- RubyGems supplies the exact Zui 0.0.10 catalog once for every plugin. The exact pin intentionally
377
- matches the Zui core embedded in the deterministic mruby runtime.
336
+ Omarchy UI prints the generated package location. It automatically assembles the bundled Ruby and
337
+ assets, Omarchy metadata and entry points, native host, checksummed runtime provenance, and only the
338
+ Zui QML components used by the project. Application packages receive a direct launcher; plugin
339
+ packages are validated against Omarchy's requirements before the command succeeds. Omarchy UI pins
340
+ Zui 0.0.10 so generated QML exactly matches the Zui core embedded in the deterministic mruby runtime.
341
+ The source `config.rb` remains the authoritative build configuration; plugin manifests are generated
342
+ from it in the distributable package and are never maintained separately by the developer.
378
343
 
379
344
  ## Component catalog
380
345
 
@@ -461,10 +426,10 @@ The dependency direction is deliberate:
461
426
  integration, plugin lifecycle commands, and Omarchy packaging.
462
427
  - **Applications own** Ruby source, assets, state, behavior, and reusable UI modules.
463
428
 
464
- At runtime, the adapter locates the compatible Zui gem and loads its protocol renderer, components,
465
- controls, theme, and fonts directly from `Zui::FRAMEWORK_ROOT`. Bundle, validation, and push install
466
- only the Omarchy host files and never copy Zui component implementations into an application or
467
- plugin. Read the full [QML ownership and adapter boundary](docs/qml-support.md).
429
+ At bundle, validation, and push time, Omarchy UI asks Zui to analyze the bundled Ruby syntax and
430
+ generate only the required component adapters plus their renderer dependencies. The four Omarchy
431
+ host files then replace Zui's generic desktop host. Read the full [QML ownership and adapter
432
+ boundary](docs/qml-support.md).
468
433
 
469
434
  ## Runtime integrity
470
435
 
@@ -503,11 +468,11 @@ bodies are never recorded. Per-plugin logs rotate at 128 KiB under
503
468
  | `Ruby file not found` | Pass an existing entry point to `omarchy_ui run`, normally `main.rb` |
504
469
  | `Omarchy QML module not found` | Confirm the Omarchy shell installation provides `/usr/share/omarchy/shell/Commons` and `Ui` |
505
470
  | A component reports a missing module | Install or enable the Qt module required by that declared component |
506
- | Plugin validation fails | Run `omarchy_ui validate` and correct the manifest or staged project error before pushing |
471
+ | Plugin validation fails | Run `omarchy_ui validate` and correct the reported Ruby, asset, or generated-metadata error before pushing |
507
472
  | A pushed plugin is installed but inactive | Run `omarchy plugin enable <id>` and `omarchy restart shell` after resolving the reported shell error |
508
- | The bundle destination already exists | Review or move the existing `dist/<project-name>` directory before bundling again |
473
+ | The bundle destination already exists | Review or move the previously generated package before bundling again |
509
474
 
510
- Omarchy UI reports unsupported properties, components, events, paths, manifests, and runtime
475
+ Omarchy UI reports unsupported properties, components, events, paths, generated metadata, and runtime
511
476
  resources directly. Preserve the original error when opening an issue.
512
477
 
513
478
  ## Development
data/Service.qml CHANGED
@@ -14,11 +14,6 @@ Item {
14
14
  readonly property string rubyProgram: pluginDir + "/main.rb"
15
15
  property string program: ""
16
16
  readonly property string effectiveRubyProgram: program !== "" ? program : rubyProgram
17
- readonly property string requiredZuiVersion: "0.0.10"
18
- property string zuiRoot: ""
19
- property string zuiVersion: ""
20
- readonly property bool zuiReady: zuiRoot !== ""
21
-
22
17
  property bool ready: false
23
18
  property bool stopping: false
24
19
  property string lastError: ""
@@ -418,7 +413,7 @@ Item {
418
413
 
419
414
  function componentSource(typeName) {
420
415
  var definition = componentDefinitions[String(typeName || "")]
421
- return definition && zuiReady ? zuiRoot + "/Components/Builtins/" + definition.qml : ""
416
+ return definition ? pluginDir + "/Components/Builtins/" + definition.qml : ""
422
417
  }
423
418
 
424
419
  function componentDefinition(typeName) {
@@ -446,7 +441,7 @@ Item {
446
441
  }
447
442
 
448
443
  function startRuby() {
449
- if (stopping || pluginDir === "" || !zuiReady || rubyProcess.running) return
444
+ if (stopping || pluginDir === "" || rubyProcess.running) return
450
445
  var configuredRuntime = String(Quickshell.env("OMARCHY_UI_RUNTIME") || "")
451
446
  rubyProcess.command = [
452
447
  configuredRuntime !== "" ? configuredRuntime : pluginDir + "/omarchy-ui-runtime",
@@ -456,68 +451,14 @@ Item {
456
451
  rubyProcess.running = true
457
452
  }
458
453
 
459
- function locateZui() {
460
- if (stopping || pluginDir === "" || zuiReady || zuiLocator.running) return
461
- zuiLocator.command = [
462
- "ruby", "-e",
463
- 'require "json"; gem "zui", "= ' + requiredZuiVersion
464
- + '"; require "zui"; STDOUT.write(JSON.generate({root: Zui::FRAMEWORK_ROOT, version: Zui::VERSION}))'
465
- ]
466
- zuiLocator.running = true
467
- }
468
-
469
- onPluginDirChanged: {
470
- zuiRoot = ""
471
- zuiVersion = ""
472
- Qt.callLater(locateZui)
473
- }
474
-
475
- Component.onCompleted: Qt.callLater(locateZui)
454
+ onPluginDirChanged: Qt.callLater(startRuby)
476
455
 
477
456
  Component.onDestruction: {
478
457
  stopping = true
479
458
  restartTimer.stop()
480
- if (zuiLocator.running) zuiLocator.running = false
481
459
  if (rubyProcess.running) rubyProcess.running = false
482
460
  }
483
461
 
484
- Process {
485
- id: zuiLocator
486
-
487
- stdout: StdioCollector {
488
- id: zuiLocatorOutput
489
- waitForEnd: true
490
- }
491
-
492
- stderr: StdioCollector {
493
- id: zuiLocatorError
494
- waitForEnd: true
495
- }
496
-
497
- onExited: function(exitCode) {
498
- if (root.stopping || root.pluginDir === "") return
499
- var detail = String(zuiLocatorError.text || "").trim()
500
- if (exitCode !== 0) {
501
- root.lastError = "Zui " + root.requiredZuiVersion
502
- + " is required from the omarchy-ui gem"
503
- + (detail !== "" ? ": " + detail.slice(0, 240) : "")
504
- return
505
- }
506
- try {
507
- var located = JSON.parse(String(zuiLocatorOutput.text || ""))
508
- if (!located || typeof located.root !== "string" || located.root[0] !== "/"
509
- || String(located.version || "") !== root.requiredZuiVersion)
510
- throw new Error("invalid Zui gem location")
511
- root.zuiRoot = String(located.root)
512
- root.zuiVersion = String(located.version)
513
- root.lastError = ""
514
- root.startRuby()
515
- } catch (error) {
516
- root.lastError = "Could not locate Zui " + root.requiredZuiVersion + " from RubyGems"
517
- }
518
- }
519
- }
520
-
521
462
  Process {
522
463
  id: rubyProcess
523
464
  stdinEnabled: true
@@ -27,7 +27,7 @@ module OmarchyUI
27
27
  when "validate" then validate(arguments)
28
28
  when "version", "--version", "-v" then @out.puts(OmarchyUI::VERSION); 0
29
29
  else
30
- @err.puts("Usage: omarchy_ui <new NAME|run FILE|bundle [DIRECTORY]|push [DIRECTORY]|validate [DIRECTORY]|version>")
30
+ @err.puts("Usage: omarchy_ui <new NAME [--plugin]|run FILE|bundle [DIRECTORY]|push [DIRECTORY]|validate [DIRECTORY]|version>")
31
31
  command.nil? ? 0 : 64
32
32
  end
33
33
  rescue Interrupt
@@ -67,14 +67,30 @@ module OmarchyUI
67
67
  end
68
68
 
69
69
  def new_project(arguments)
70
+ plugin = !arguments.delete("--plugin").nil?
70
71
  name = arguments.shift || raise(ArgumentError, "new requires a project name")
71
- raise ArgumentError, "new accepts only an application name" unless arguments.empty?
72
+ raise ArgumentError, "new accepts a name and optional --plugin" unless arguments.empty?
72
73
  destination = File.expand_path(slug(name))
73
- Generator.new(path: destination, name: name).create
74
- @out.puts("Created standalone Omarchy UI app in #{destination}")
74
+ kind = plugin ? :plugin : :application
75
+ Generator.new(path: destination, name: name, kind:, author: default_author).create
76
+ label = plugin ? "Omarchy-compliant plugin" : "self-contained Omarchy application"
77
+ @out.puts("Created #{label} in #{destination}")
75
78
  0
76
79
  end
77
80
 
81
+ def default_author
82
+ configured = ENV["OMARCHY_UI_AUTHOR"].to_s.strip
83
+ return configured unless configured.empty?
84
+
85
+ output, _error, status = Open3.capture3("git", "config", "--get", "user.name")
86
+ git_author = output.to_s.strip
87
+ return git_author if status.success? && !git_author.empty?
88
+
89
+ ENV["USER"].to_s.strip.then { |user| user.empty? ? "Omarchy UI Developer" : user }
90
+ rescue SystemCallError
91
+ "Omarchy UI Developer"
92
+ end
93
+
78
94
  def slug(value)
79
95
  result = value.to_s.downcase.gsub(/[^a-z0-9]+/, "-").gsub(/\A-|\-\z/, "")
80
96
  raise ArgumentError, "name must contain letters or numbers" if result.empty?
@@ -83,6 +99,10 @@ module OmarchyUI
83
99
 
84
100
  def validate(arguments)
85
101
  source = File.expand_path(arguments.shift || Dir.pwd)
102
+ raise ArgumentError, "validate accepts one directory" unless arguments.empty?
103
+ config = ProjectConfig.load(source)
104
+ raise ArgumentError, "validate requires a plugin project" if config&.application?
105
+
86
106
  with_staged_project(source) do |staging|
87
107
  system("omarchy", "plugin", "validate", staging) ? 0 : 1
88
108
  end
@@ -91,15 +111,19 @@ module OmarchyUI
91
111
  def bundle_project(arguments)
92
112
  source = File.expand_path(arguments.shift || Dir.pwd)
93
113
  raise ArgumentError, "bundle accepts one directory" unless arguments.empty?
94
- raise ArgumentError, "main.rb not found: #{source}" unless File.file?(File.join(source, "main.rb"))
95
- destination = File.join(source, "dist", File.basename(source))
114
+ config = ProjectConfig.load(source)
115
+ entrypoint = config&.entrypoint || "main.rb"
116
+ raise ArgumentError, "#{entrypoint} not found: #{source}" unless File.file?(File.join(source, entrypoint))
117
+ destination = File.join(source, "dist", config&.slug || File.basename(source))
96
118
  raise ArgumentError, "bundle destination already exists: #{destination}" if File.exist?(destination)
97
119
  FileUtils.mkdir_p(destination)
98
- entries = application_entries(source)
120
+ entries = application_entries(source, config:)
99
121
  FileUtils.cp_r(entries.map { |entry| File.join(source, entry) }, destination)
100
- bundle_ruby_entrypoint(source, destination)
122
+ config.write_manifest(destination) if config&.plugin?
123
+ bundle_ruby_entrypoint(source, destination, entrypoint:)
101
124
  Runtime.install_package(destination)
102
- if File.file?(File.join(destination, "manifest.json"))
125
+ plugin = config ? config.plugin? : File.file?(File.join(destination, "manifest.json"))
126
+ if plugin
103
127
  raise ArgumentError, "bundled plugin validation failed" unless system("omarchy", "plugin", "validate", destination)
104
128
  @out.puts("Bundled plugin in #{destination}")
105
129
  return 0
@@ -115,7 +139,7 @@ module OmarchyUI
115
139
  app_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
116
140
  export OMARCHY_UI_RUNTIME="$app_dir/omarchy-ui-runtime"
117
141
  export OMARCHY_UI_PROJECT_DIR="$app_dir"
118
- export OMARCHY_UI_RUBY_PROGRAM="$app_dir/main.rb"
142
+ export OMARCHY_UI_RUBY_PROGRAM="$app_dir/#{entrypoint}"
119
143
  export QT_LOGGING_RULES="${QT_LOGGING_RULES:+$QT_LOGGING_RULES;}qt.qpa.services.warning=false"
120
144
  exec quickshell --path "$app_dir/App.qml"
121
145
  SH
@@ -131,8 +155,9 @@ module OmarchyUI
131
155
  [ENV["QT_LOGGING_RULES"], "qt.qpa.services.warning=false"].compact.reject(&:empty?).join(";")
132
156
  end
133
157
 
134
- def application_entries(source)
158
+ def application_entries(source, config: ProjectConfig.load(source))
135
159
  generated = Runtime::GENERATED_ENTRIES + Runtime::AUDIT_FILES + %w[run Commons Ui]
160
+ generated.concat([ProjectConfig::FILE_NAME, "manifest.json"]) if config
136
161
  Dir.children(source).reject { |entry| %w[.git dist].include?(entry) || generated.include?(entry) }
137
162
  end
138
163
 
@@ -140,8 +165,11 @@ module OmarchyUI
140
165
  enable = !arguments.delete("--no-enable")
141
166
  restart = !arguments.delete("--no-restart")
142
167
  source = File.expand_path(arguments.shift || Dir.pwd)
168
+ raise ArgumentError, "push accepts one directory" unless arguments.empty?
169
+ config = ProjectConfig.load(source)
170
+ raise ArgumentError, "push requires a plugin project" if config&.application?
143
171
  manifest_path = File.join(source, "manifest.json")
144
- manifest = JSON.parse(File.read(manifest_path))
172
+ manifest = config ? config.manifest : JSON.parse(File.read(manifest_path))
145
173
  plugin_id = manifest.fetch("id")
146
174
  raise ArgumentError, "invalid plugin id" unless VALID_ID.match?(plugin_id)
147
175
  plugin_root = File.expand_path("~/.config/omarchy/plugins")
@@ -184,11 +212,14 @@ module OmarchyUI
184
212
 
185
213
  def stage_project(source, parent: nil, prefix: ".omarchy-ui-staging-")
186
214
  raise ArgumentError, "project directory not found: #{source}" unless File.directory?(source)
215
+ config = ProjectConfig.load(source)
187
216
  staging = parent ? Dir.mktmpdir(prefix, parent) : Dir.mktmpdir(prefix)
188
- entries = application_entries(source)
217
+ entries = application_entries(source, config:)
189
218
  FileUtils.cp_r(entries.map { |entry| File.join(source, entry) }, staging) unless entries.empty?
190
- if File.file?(File.join(staging, "main.rb"))
191
- bundle_ruby_entrypoint(source, staging)
219
+ config.write_manifest(staging) if config&.plugin?
220
+ entrypoint = config&.entrypoint || "main.rb"
221
+ if File.file?(File.join(staging, entrypoint))
222
+ bundle_ruby_entrypoint(source, staging, entrypoint:)
192
223
  Runtime.install_package(staging)
193
224
  end
194
225
  staging
@@ -197,9 +228,11 @@ module OmarchyUI
197
228
  raise
198
229
  end
199
230
 
200
- def bundle_ruby_entrypoint(source, destination)
201
- entrypoint = File.join(source, "main.rb")
202
- File.write(File.join(destination, "main.rb"), SourceBundle.new(entrypoint, root: source).call)
231
+ def bundle_ruby_entrypoint(source, destination, entrypoint: "main.rb")
232
+ source_entrypoint = File.join(source, entrypoint)
233
+ destination_entrypoint = File.join(destination, entrypoint)
234
+ FileUtils.mkdir_p(File.dirname(destination_entrypoint))
235
+ File.write(destination_entrypoint, SourceBundle.new(source_entrypoint, root: source).call)
203
236
  end
204
237
 
205
238
  def activate_plugin(plugin_id)
@@ -4,9 +4,15 @@ require "fileutils"
4
4
 
5
5
  module OmarchyUI
6
6
  class Generator
7
- def initialize(path:, name: nil)
7
+ PROJECT_KINDS = %i[application plugin].freeze
8
+
9
+ def initialize(path:, name: nil, kind: :application, plugin_id: nil, author: nil)
8
10
  @path = File.expand_path(path)
9
11
  @name = name || File.basename(@path).split(/[-_]/).map(&:capitalize).join(" ")
12
+ @kind = kind.to_sym
13
+ @plugin_id = plugin_id || "local.#{project_slug}"
14
+ @author = author.to_s.strip.empty? ? "Omarchy UI Developer" : author.to_s.strip
15
+ raise ArgumentError, "unsupported project kind: #{@kind}" unless PROJECT_KINDS.include?(@kind)
10
16
  end
11
17
 
12
18
  def create
@@ -14,9 +20,11 @@ module OmarchyUI
14
20
  created = true
15
21
  FileUtils.mkdir_p(File.join(@path, "components"))
16
22
  File.write(File.join(@path, "main.rb"), main_program)
17
- File.write(File.join(@path, "app.rb"), application_program)
23
+ File.write(File.join(@path, "app.rb"), application_program) unless plugin?
24
+ File.write(File.join(@path, ProjectConfig::FILE_NAME), project_config)
18
25
  File.write(File.join(@path, "components", "welcome.rb"), welcome_component)
19
26
  File.write(File.join(@path, "README.md"), readme)
27
+ File.write(File.join(@path, "LICENSE"), license)
20
28
  @path
21
29
  rescue StandardError
22
30
  FileUtils.remove_entry(@path) if created && File.directory?(@path)
@@ -26,6 +34,8 @@ module OmarchyUI
26
34
  private
27
35
 
28
36
  def main_program
37
+ return plugin_program if plugin?
38
+
29
39
  <<~RUBY
30
40
  # frozen_string_literal: true
31
41
 
@@ -36,6 +46,33 @@ module OmarchyUI
36
46
  RUBY
37
47
  end
38
48
 
49
+ def plugin_program
50
+ <<~RUBY
51
+ # frozen_string_literal: true
52
+
53
+ require "omarchy_ui"
54
+ require_relative "components/welcome"
55
+
56
+ OmarchyUI.plugin(ui: #{constant_name}::UI) do
57
+ bar_widget do
58
+ row spacing: 7 do
59
+ text "#{@name}"
60
+ end
61
+ on_click { open_panel :#{surface_name} }
62
+ end
63
+
64
+ panel :#{surface_name} do
65
+ scroll width: 560, height: 620 do
66
+ welcome_card(
67
+ title: "#{@name}",
68
+ message: "Your Omarchy plugin is ready. Build the interface with Ruby and Zui components."
69
+ )
70
+ end
71
+ end
72
+ end
73
+ RUBY
74
+ end
75
+
39
76
  def application_program
40
77
  <<~RUBY
41
78
  # frozen_string_literal: true
@@ -80,6 +117,8 @@ module OmarchyUI
80
117
  end
81
118
 
82
119
  def readme
120
+ return plugin_readme if plugin?
121
+
83
122
  <<~MARKDOWN
84
123
  # #{@name}
85
124
 
@@ -92,10 +131,97 @@ module OmarchyUI
92
131
  ```
93
132
 
94
133
  Reusable Ruby UI components live in `components/` and load with ordinary Ruby
95
- `require_relative`. Run `omarchy_ui bundle` to create a self-contained distribution.
134
+ `require_relative`. Project identity and build settings live in `config.rb`.
135
+
136
+ ## Build
137
+
138
+ ```bash
139
+ omarchy_ui bundle
140
+ ```
141
+
142
+ Omarchy UI generates the complete self-contained application package required by Omarchy.
143
+ MARKDOWN
144
+ end
145
+
146
+ def plugin_readme
147
+ <<~MARKDOWN
148
+ # #{@name}
149
+
150
+ A pure Ruby Omarchy plugin built with Omarchy UI and Zui.
151
+
152
+ ## Validate
153
+
154
+ ```bash
155
+ omarchy_ui validate
156
+ ```
157
+
158
+ ## Install for development
159
+
160
+ ```bash
161
+ omarchy_ui push
162
+ ```
163
+
164
+ ## Build
165
+
166
+ ```bash
167
+ omarchy_ui bundle
168
+ ```
169
+
170
+ Write the plugin interface and behavior in Ruby under `main.rb` and `components/`.
171
+ Public plugin identity and release metadata live in `config.rb`. Omarchy UI uses it to
172
+ generate and validate the complete Omarchy-compatible plugin package.
96
173
  MARKDOWN
97
174
  end
98
175
 
176
+ def project_config
177
+ config = ProjectConfig.generate(
178
+ name: @name,
179
+ slug: project_slug,
180
+ kind: @kind,
181
+ author: @author,
182
+ plugin_id: @plugin_id
183
+ )
184
+ config
185
+ end
186
+
187
+ def license
188
+ <<~TEXT
189
+ MIT License
190
+
191
+ Copyright (c) #{Time.now.year} #{@author}
192
+
193
+ Permission is hereby granted, free of charge, to any person obtaining a copy
194
+ of this software and associated documentation files (the "Software"), to deal
195
+ in the Software without restriction, including without limitation the rights
196
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
197
+ copies of the Software, and to permit persons to whom the Software is
198
+ furnished to do so, subject to the following conditions:
199
+
200
+ The above copyright notice and this permission notice shall be included in all
201
+ copies or substantial portions of the Software.
202
+
203
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
204
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
205
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
206
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
207
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
208
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
209
+ SOFTWARE.
210
+ TEXT
211
+ end
212
+
213
+ def plugin?
214
+ @kind == :plugin
215
+ end
216
+
217
+ def project_slug
218
+ File.basename(@path).downcase.gsub(/[^a-z0-9]+/, "-").gsub(/\A-|\-\z/, "")
219
+ end
220
+
221
+ def surface_name
222
+ project_slug.tr("-", "_")
223
+ end
224
+
99
225
  def constant_name
100
226
  @name.gsub(/[^a-zA-Z0-9]+/, " ").split.map(&:capitalize).join.then do |name|
101
227
  name.empty? || name.match?(/\A\d/) ? "OmarchyApplication" : name
@@ -0,0 +1,207 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module OmarchyUI
6
+ class ProjectConfig
7
+ FILE_NAME = "config.rb"
8
+ PROJECT_TYPES = %i[application plugin].freeze
9
+ SLUG = /\A[a-z0-9]+(?:-[a-z0-9]+)*\z/
10
+ SECTIONS = %i[left center right].freeze
11
+
12
+ class Builder
13
+ FIELDS = %i[type id name slug version author license description entrypoint].freeze
14
+
15
+ FIELDS.each do |field|
16
+ define_method(field) { |value| @values[field] = value }
17
+ end
18
+
19
+ def initialize
20
+ @values = {}
21
+ end
22
+
23
+ def bar_widget(&definition)
24
+ widget = WidgetBuilder.new
25
+ widget.instance_eval(&definition)
26
+ @values[:bar_widget] = widget.to_h
27
+ end
28
+
29
+ def build(path: FILE_NAME)
30
+ ProjectConfig.new(@values, path:).tap(&:validate!)
31
+ end
32
+ end
33
+
34
+ class WidgetBuilder
35
+ FIELDS = %i[display_name description category default_section].freeze
36
+
37
+ FIELDS.each do |field|
38
+ define_method(field) { |value| @values[field] = value }
39
+ end
40
+
41
+ def initialize
42
+ @values = {}
43
+ end
44
+
45
+ def to_h
46
+ @values.dup
47
+ end
48
+ end
49
+
50
+ attr_reader :path
51
+
52
+ def self.define(&definition)
53
+ raise ArgumentError, "project config requires a block" unless definition
54
+
55
+ Builder.new.tap { |builder| builder.instance_eval(&definition) }.build
56
+ end
57
+
58
+ def self.generate(name:, slug:, kind:, author:, plugin_id: nil)
59
+ type = kind.to_sym
60
+ raise ArgumentError, "unsupported project type: #{type}" unless PROJECT_TYPES.include?(type)
61
+
62
+ lines = [
63
+ "# frozen_string_literal: true",
64
+ "",
65
+ "OmarchyUI.configure do",
66
+ " type #{type.inspect}"
67
+ ]
68
+ lines << " id #{(plugin_id || "local.#{slug}").inspect}" if type == :plugin
69
+ lines.concat([
70
+ " name #{name.inspect}",
71
+ " slug #{slug.inspect}",
72
+ " version \"0.1.0\""
73
+ ])
74
+ if type == :plugin
75
+ lines.concat([
76
+ " author #{author.inspect}",
77
+ " license \"MIT\"",
78
+ " description #{"A Ruby-powered #{name} plugin generated by Omarchy UI.".inspect}",
79
+ " entrypoint \"main.rb\"",
80
+ "",
81
+ " bar_widget do",
82
+ " display_name #{name.inspect}",
83
+ " description #{"Open #{name}.".inspect}",
84
+ " category \"System\"",
85
+ " default_section :right",
86
+ " end"
87
+ ])
88
+ else
89
+ lines << " entrypoint \"main.rb\""
90
+ end
91
+ lines << "end"
92
+ lines.join("\n") + "\n"
93
+ end
94
+
95
+ def self.load(root)
96
+ path = File.join(File.expand_path(root), FILE_NAME)
97
+ return nil unless File.file?(path)
98
+
99
+ value = Module.new.module_eval(File.read(path), path, 1)
100
+ unless value.is_a?(ProjectConfig)
101
+ raise ArgumentError, "#{FILE_NAME} must return an OmarchyUI::ProjectConfig"
102
+ end
103
+
104
+ new(value.to_h, path:).tap(&:validate!)
105
+ rescue SyntaxError, NameError, NoMethodError => error
106
+ raise ArgumentError, "invalid #{FILE_NAME}: #{error.message}"
107
+ end
108
+
109
+ def initialize(data, path:)
110
+ @data = data
111
+ @path = path
112
+ end
113
+
114
+ def type
115
+ value = @data[:type]
116
+ value.respond_to?(:to_sym) ? value.to_sym : value
117
+ end
118
+ def application? = type == :application
119
+ def plugin? = type == :plugin
120
+ def name = @data.fetch(:name)
121
+ def slug = @data.fetch(:slug)
122
+ def entrypoint = @data.fetch(:entrypoint)
123
+
124
+ def to_h
125
+ @data.dup
126
+ end
127
+
128
+ def manifest
129
+ raise ArgumentError, "project config describes an application, not a plugin" unless plugin?
130
+
131
+ widget = @data.fetch(:bar_widget)
132
+ {
133
+ "schemaVersion" => 1,
134
+ "id" => @data.fetch(:id),
135
+ "name" => name,
136
+ "version" => @data.fetch(:version),
137
+ "author" => @data.fetch(:author),
138
+ "license" => @data.fetch(:license),
139
+ "description" => @data.fetch(:description),
140
+ "kinds" => ["service", "bar-widget", "panel"],
141
+ "keepLoaded" => true,
142
+ "entryPoints" => {
143
+ "service" => "Service.qml",
144
+ "barWidget" => "BarWidget.qml",
145
+ "panel" => "Panel.qml"
146
+ },
147
+ "barWidget" => {
148
+ "displayName" => widget.fetch(:display_name),
149
+ "description" => widget.fetch(:description),
150
+ "category" => widget.fetch(:category),
151
+ "allowMultiple" => false,
152
+ "defaultSection" => widget.fetch(:default_section).to_s
153
+ }
154
+ }
155
+ end
156
+
157
+ def write_manifest(destination)
158
+ File.write(File.join(destination, "manifest.json"), JSON.pretty_generate(manifest) + "\n")
159
+ end
160
+
161
+ def validate!
162
+ raise ArgumentError, "invalid project config" unless @data.is_a?(Hash)
163
+ raise ArgumentError, "invalid project type" unless PROJECT_TYPES.include?(type)
164
+ required_string!(:name)
165
+ required_string!(:slug)
166
+ raise ArgumentError, "invalid project slug" unless SLUG.match?(@data[:slug])
167
+ required_string!(:version)
168
+ required_string!(:entrypoint)
169
+ validate_entrypoint!
170
+ validate_plugin! if plugin?
171
+ self
172
+ end
173
+
174
+ private
175
+
176
+ def required_string!(key, object = @data)
177
+ value = object[key]
178
+ raise ArgumentError, "invalid project config field: #{key}" unless value.is_a?(String) && !value.strip.empty?
179
+
180
+ value
181
+ end
182
+
183
+ def validate_entrypoint!
184
+ parts = entrypoint.split(/[\\\/]/)
185
+ if entrypoint.start_with?("/") || parts.empty? || parts.include?("..") || !entrypoint.end_with?(".rb")
186
+ raise ArgumentError, "invalid project entrypoint"
187
+ end
188
+ end
189
+
190
+ def validate_plugin!
191
+ %i[id author license description].each { |key| required_string!(key) }
192
+ raise ArgumentError, "invalid plugin id" unless VALID_ID.match?(@data[:id])
193
+
194
+ widget = @data[:bar_widget]
195
+ raise ArgumentError, "plugin project config is missing bar widget metadata" unless widget.is_a?(Hash)
196
+
197
+ %i[display_name description category].each { |key| required_string!(key, widget) }
198
+ section = widget[:default_section]
199
+ valid_section = section.respond_to?(:to_sym) && SECTIONS.include?(section.to_sym)
200
+ raise ArgumentError, "invalid default bar section" unless valid_section
201
+ end
202
+ end
203
+
204
+ def self.configure(&definition)
205
+ ProjectConfig.define(&definition)
206
+ end
207
+ end
@@ -1,19 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "fileutils"
4
+ require "json"
5
+ require "tmpdir"
4
6
 
5
7
  module OmarchyUI
6
8
  module Runtime
7
9
  module_function
8
10
 
9
11
  BUNDLED = File.expand_path("../../vendor/runtime/x86_64-linux/omarchy-ui-runtime", __dir__)
10
- ADAPTER_FILES = %w[Service.qml Panel.qml BarWidget.qml App.qml ZuiRenderer.qml].freeze
12
+ ADAPTER_FILES = %w[Service.qml Panel.qml BarWidget.qml App.qml].freeze
11
13
  ZUI_GENERATED_ENTRIES = %w[Desktop.qml ControlNode.qml Components Controls Theme Fonts].freeze
12
- FILES = ADAPTER_FILES.freeze
14
+ TREE_SHAKE_REPORT = "zui-tree-shake.json"
15
+ FILES = (ADAPTER_FILES + %w[ControlNode.qml]).freeze
13
16
  AUDIT_FILES = %w[
14
17
  omarchy-ui-runtime.sha256 runtime-provenance.json RUNTIME_PROVENANCE.md
15
18
  ].freeze
16
- GENERATED_ENTRIES = (ADAPTER_FILES + ZUI_GENERATED_ENTRIES + %w[omarchy-ui-runtime]).freeze
19
+ GENERATED_ENTRIES = (ADAPTER_FILES + ZUI_GENERATED_ENTRIES +
20
+ [TREE_SHAKE_REPORT, "omarchy-ui-runtime"]).freeze
17
21
 
18
22
  def executable
19
23
  override = ENV["OMARCHY_UI_RUNTIME"]
@@ -35,15 +39,24 @@ module OmarchyUI
35
39
  FileUtils.rm_f(temporary) if temporary && File.exist?(temporary)
36
40
  end
37
41
 
38
- def install_package(path, framework_root: FRAMEWORK_ROOT)
42
+ def install_package(path, framework_root: FRAMEWORK_ROOT, project: path)
39
43
  FileUtils.mkdir_p(path)
40
- ZUI_GENERATED_ENTRIES.each do |entry|
44
+ GENERATED_ENTRIES.each do |entry|
41
45
  generated = File.join(path, entry)
42
46
  File.directory?(generated) ? FileUtils.remove_entry(generated) : FileUtils.rm_f(generated)
43
47
  end
48
+ report = install_tree_shaken_zui(path, project:)
44
49
  ADAPTER_FILES.each do |file|
45
50
  FileUtils.cp(File.join(framework_root, file), File.join(path, file))
46
51
  end
52
+ File.write(File.join(path, TREE_SHAKE_REPORT), JSON.pretty_generate({
53
+ "zui_version" => Zui::VERSION,
54
+ "components" => report.components.map(&:to_s),
55
+ "before_bytes" => report.before_bytes,
56
+ "after_bytes" => report.after_bytes,
57
+ "saved_bytes" => report.saved_bytes,
58
+ "warnings" => report.warnings
59
+ }) + "\n")
47
60
 
48
61
  if File.file?(BUNDLED)
49
62
  destination = File.join(path, "omarchy-ui-runtime")
@@ -57,8 +70,22 @@ module OmarchyUI
57
70
  path
58
71
  end
59
72
 
60
- def install_components(path, **)
61
- install_package(path)
73
+ def install_components(path, **options)
74
+ install_package(path, **options)
75
+ end
76
+
77
+ def install_tree_shaken_zui(destination, project:)
78
+ Dir.mktmpdir("omarchy-ui-zui-") do |temporary|
79
+ qml = File.join(temporary, "qml")
80
+ native = File.join(temporary, "native")
81
+ FileUtils.mkdir_p(native)
82
+ Zui::Runtime.install_qml(qml)
83
+ report = Zui::TreeShaker.new(project:, framework: qml, native:).shake!
84
+ FileUtils.rm_f([File.join(qml, "Desktop.qml"), File.join(qml, "Service.qml")])
85
+ entries = Dir.children(qml).map { |entry| File.join(qml, entry) }
86
+ FileUtils.cp_r(entries, destination) unless entries.empty?
87
+ return report
88
+ end
62
89
  end
63
90
  end
64
91
  end
data/lib/omarchy_ui.rb CHANGED
@@ -11,7 +11,7 @@ rescue LoadError
11
11
  end
12
12
 
13
13
  module OmarchyUI
14
- VERSION = "0.0.6"
14
+ VERSION = "0.0.8"
15
15
  ZUI_VERSION = Zui::VERSION
16
16
  FRAMEWORK_ROOT = File.expand_path("..", __dir__)
17
17
 
@@ -126,5 +126,6 @@ module Zui
126
126
  end
127
127
  end
128
128
 
129
+ require_relative "omarchy_ui/project_config"
129
130
  require_relative "omarchy_ui/generator"
130
131
  require_relative "omarchy_ui/runtime"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: omarchy-ui
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 0.0.8
5
5
  platform: x86_64-linux
6
6
  authors:
7
7
  - Adam Moussa Ali
@@ -35,11 +35,11 @@ files:
35
35
  - Panel.qml
36
36
  - README.md
37
37
  - Service.qml
38
- - ZuiRenderer.qml
39
38
  - bin/omarchy_ui
40
39
  - lib/omarchy_ui.rb
41
40
  - lib/omarchy_ui/cli.rb
42
41
  - lib/omarchy_ui/generator.rb
42
+ - lib/omarchy_ui/project_config.rb
43
43
  - lib/omarchy_ui/runtime.rb
44
44
  - vendor/runtime/x86_64-linux/RUNTIME_PROVENANCE.md
45
45
  - vendor/runtime/x86_64-linux/omarchy-ui-runtime
data/ZuiRenderer.qml DELETED
@@ -1,37 +0,0 @@
1
- import QtQuick
2
-
3
- Loader {
4
- id: root
5
-
6
- property var bridge: null
7
- property string surfaceName: ""
8
- property string controlId: ""
9
- property color foreground: "white"
10
- property string fontFamily: ""
11
- readonly property bool renderReady: status === Loader.Ready && item !== null
12
- && String(item.iconFontFamily || "") !== ""
13
- && String(item.brandIconFontFamily || "") !== ""
14
-
15
- active: bridge !== null && bridge.zuiReady && controlId !== ""
16
- source: active ? bridge.zuiRoot + "/ControlNode.qml" : ""
17
-
18
- function syncRenderer() {
19
- if (!item) return
20
- item.bridge = bridge
21
- item.surfaceName = surfaceName
22
- item.controlId = controlId
23
- item.foreground = foreground
24
- item.fontFamily = fontFamily
25
- }
26
-
27
- onLoaded: syncRenderer()
28
- onBridgeChanged: syncRenderer()
29
- onSurfaceNameChanged: syncRenderer()
30
- onControlIdChanged: syncRenderer()
31
- onForegroundChanged: syncRenderer()
32
- onFontFamilyChanged: syncRenderer()
33
- onStatusChanged: {
34
- if (status === Loader.Error && bridge)
35
- bridge.lastError = "Could not load Zui " + bridge.requiredZuiVersion + " renderer from its gem"
36
- }
37
- }