Dario Gabriel LipicarandClaude Opus 5 1c24a60d7b fix(deps): restore aboutToUnload() on the generated UI plugin
This repo's ui_qml plugin has not carried the module teardown hook. Not
"since a recent regression" — it never has, and the reason changed
underneath it without the symptom changing.

Before module-builder#211 the glue came from logos-qt-generator, whose ui
emitter had no hook. #211 moved generation to logos-view-generator but
landed pinning logos-view-module 1f95a75, which predates the reconciliation
that GAVE that emitter the hook. Two different causes, one outcome: no
aboutToUnload(). module-builder#214 fixed the pin; this takes the bump.

    logos-module-builder  352df67 -> e45caf1
      ...which carries logos-view-module 1f95a75 -> d6c8885

Measured on the BUILT plugin, loaded with QPluginLoader and dumped from its
QMetaObject — not read off the generator:

    PRE   PackageManagerUiPlugin  methodCount 5   [own] initLogos only
                                  indexOfMethod(aboutToUnload())  = -1
                                  indexOfMethod(unloadFinished()) = -1
    POST  PackageManagerUiPlugin  methodCount 7   [own] unloadFinished(),
                                                        aboutToUnload() -> int
                                  indexOfMethod(aboutToUnload())  = 6
                                  indexOfMethod(unloadFinished()) = 4

BOTH BUILDS SUCCEEDED. That is the whole difficulty with this bug: the host
reaches the hook by NAME through the meta-object, so a plugin without it has
no such meta-method, invokeMethod returns false, and the host proceeds —
indistinguishable from a plugin answering "Synchronous, nothing to wait
for". logos-module-loader-qt/src/host/logos_host.cpp:238-246 documents that
path verbatim.

Worth recording: checks.integration-test PASSED on the pre-bump tree with
the hook absent. Nothing in this repo would have caught this, which is why
the evidence above is a meta-object dump rather than a green check.

Only logos-module-builder moves at root. Verified on aarch64-darwin and
x86_64-linux: every check plus the module outputs, and the hook confirmed in
the plugin inside the shipped .lgx payload as well as the bare .so/.dylib.

Follow-up: this lock still reaches module-builder 352df67 through
`package_manager` and `package_downloader`. Harmless — both are type: core
and emit no ui glue — but worth a second relock once their own bumps land.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 00:18:58 -03:00

logos-package-manager-ui

A Qt/QML UI plugin that browses multi-repository module catalogs and lets users install, upgrade, downgrade, sidegrade, and uninstall packages. The catalog comes from the package_downloader module (merged across every configured repository); the UI also manages the repository list (add / remove / enable / disable).

📖 Catalog formats (logos-repo.json / index.json): logos-modules-release-tool/docs/catalog-format.md.

Architecture

Each catalog row's primary action is resolved against its currently selected version (Install / Upgrade / Downgrade / Reinstall / Retry / Installed / Not available) and pinned to that row's source repository, so a package name published by two repos can't cross-wire.

Dependency-aware per-row actions (owned by PMU)

When the user triggers an Install / Upgrade / Downgrade / Reinstall, PMU previews the dependency impact before anything downloads:

  1. package_downloader.resolveDependencies(deps, installed) resolves what the action would change, omitting transitive deps already satisfied on-disk.
  2. No transitive changes → proceed straight to the download/install.
  3. Transitive changes → the backend stashes the pending request (keyed by an opaque requestKey = repositoryUrl + name, unique across repos) and emits installDepsConfirmationRequested; QML shows the InstallDepsConfirm dialog offering Install with dependencies / Install just this / Cancel. The chosen path routes back through confirmInstallWith{,out}Deps / cancelInstallConfirm by that key.

So, unlike the uninstall/upgrade cascade dialog, PMU does own this dep-confirm preview state.

Uninstall / upgrade cascade (owned by Basecamp)

For the destructive cascade confirmation, PackageManagerBackend is a stateless view — Basecamp's PluginManager owns the single cascade-confirmation dialog and the two-phase ack protocol:

  1. User clicks Uninstall / Upgrade in the PMU tab
  2. PMU calls package_manager.requestUninstallAsync / requestUpgradeAsync
  3. package_manager emits beforeUninstall / beforeUpgrade
  4. Basecamp's PluginManager acks, shows the cascade dialog, confirms/cancels
  5. On completion, package_manager emits corePluginFileInstalled / uiPluginFileInstalled / corePluginUninstalled / uiPluginUninstalled, which PMU consumes (debounced) to refresh the catalog rows

PMU subscribes to uninstallCancelled / upgradeCancelled events for error toast display. User-initiated cancels are silent; system-originated cancellations (e.g. the module's ack-timeout when no listener takes over the gated flow) are surfaced via the dedicated cancellationOccurred(name, message) signal, which QML renders as a plain toast. The install-progress channel (installationProgressUpdated) is reserved for install progress and install failures; routing cancellations through it would render them with a misleading "Failed to install" prefix.

Note: the bulk multi-select "Run Actions" surface is currently hidden (selectionMode: None) — per-row actions are the supported path. The backend plumbing for it remains in place behind that flag.

How to Build

Build Complete UI Plugin

# Build everything (default)
nix build

# Or explicitly
nix build '.#default'

The result will include:

  • /lib/package_manager_ui.dylib (or .so on Linux) - The Package Manager UI plugin

Build Individual Components

# Build only the library (plugin)
nix build '.#lib'

# Build the standalone Qt application
nix build '.#app'

Development Shell

# Enter development shell with all dependencies
nix develop

Note: In zsh, you need to quote the target (e.g., '.#default') to prevent glob expansion.

If you don't have flakes enabled globally, add experimental flags:

nix build --extra-experimental-features 'nix-command flakes'

The compiled artifacts can be found at result/

Running the Standalone App

After building the app with nix build '.#app', you can run it:

# Run the standalone Qt application
# you need sudo if you install packages due to the permissions needed on `./result/` which is a nix mounted folder
sudo ./result/bin/logos-package-manager-ui-app

note: installed packages will go to ./result/modules

Nix Organization

The nix build system is organized into modular files in the /nix directory:

  • nix/default.nix - Common configuration (dependencies, flags, metadata)
  • nix/lib.nix - UI plugin compilation
  • nix/app.nix - Standalone Qt application compilation

Output Structure

When built with Nix:

Library build (nix build '.#lib'):

result/
└── lib/
    └── package_manager_ui.dylib    # Logos Package Manager UI plugin

App build (nix build '.#app'):

result/
├── bin/
│   ├── logos-package-manager-ui-app    # Standalone Qt application
│   ├── logos_host                       # Logos host executable (for plugins)
│   └── logoscore                        # Logos core executable
├── lib/
│   ├── liblogos_core.dylib              # Logos core library
│   └── liblogos_sdk.dylib               # Logos SDK library
├── modules/
│   ├── capability_module_plugin.dylib
│   └── package_manager_plugin.dylib
└── package_manager_ui.dylib             # Qt plugin (loaded by app)

Testing

UI integration tests use logos-qt-mcp to drive the plugin UI inside logos-standalone-app (headless).

Hermetic CI test (one command)

nix build .#integration-test -L

This builds everything (including logos-standalone-app and the QML inspector), launches the plugin in headless mode, and runs the UI tests automatically. No extra inputs needed — the test infrastructure is provided by logos-module-builder via logos-standalone-app.

Interactive testing

# 1. Build the plugin
nix build

# 2. Build the test framework (one-time)
nix build .#test-framework -o result-mcp

# 3. Run the plugin in logos-standalone-app (inspector starts on :3768)
nix run

# 4. Run the tests against the running app
node tests/ui-tests.mjs

Requirements

Build Tools

  • CMake (3.16 or later)
  • Ninja build system
  • pkg-config

Dependencies

  • Qt6 (qtbase)
  • Qt6 Widgets (included in qtbase)
  • Qt6 Remote Objects (qtremoteobjects)
  • Qt6 Declarative (qtdeclarative)
  • logos-liblogos
  • logos-cpp-sdk (for header generation)
  • logos-package-manager-module
  • logos-capability-module
  • zstd
  • krb5
  • abseil-cpp
S
Description
No description provided
Readme
1.4 MiB
Languages
C++ 58.9%
QML 28.1%
JavaScript 11.6%
Nix 1.1%
CMake 0.3%