Files
logos-app-poc/app/ModuleInstanceModel.h
T
Dario Gabriel Lipicar 9b8cf6e47e feat(shell): ship main_ui as a plugin that links no logos runtime
Measured on the built artefact: main_ui.dylib DEFINES zero and IMPORTS zero of
TokenManager, StoreRegistry, LogosAPI, LogosAPIClient and logos_core_*, out of
3410 symbols read. It links Qt and nothing else from this workspace, which
nix/symbol-gate.nix enforces across the in-process image set.

Getting there needed the last non-Qt types off the boundary: the model
properties cross as QAbstractItemModel*, catalogInstallStageChanged carries an
int rather than InstallStage::Value, and the two prebuilt AppsFilterProxy
instances are declared in QML instead of owned by MainUIBackend. That last one
removes a real inversion -- PackageCoordinator called setRequiredPackages() on
a proxy the host held a pointer to; it now emits requiredPackagesResolved() and
QML binds to the republished property.

Window resolves the plugin, qobject_casts it to IShellView, checks
hostAbiVersion() against IShellHost_abi, and calls createShell(IShellHost*).
No error-label fallback widget: that degraded to something that looked like a
working app with an empty window.

Filter proxies read role constants off host-side models and InstallEnums is
used by nine host files, so app/interfaces/ gains the contract headers both
sides compile against -- the models inherit the role structs, leaving every
AppsModel::NameRole call site unchanged. The plugin's include path is
app/interfaces only, so including a host header does not compile.

Four things only running it finds:
  * Logos::DesignSystem may be linked by exactly ONE image -- both linked it
    and the app aborted with "Cannot add multiple registrations for
    Logos.Icons"; QML module registration is process-global
  * qmltyperegistrar emits no #include for a SOURCES header given as an
    absolute path outside the project
  * each qt_add_qml_module is its own target and inherits no include dirs
  * AUTOMOC pairs header<->cpp by same-basename-same-DIRECTORY, which the
    split breaks

tst_AppManagerView.qml grows five tests for the QML binding, checked with a
negative control: breaking one assertion fails qml-tests, so they run.
2026-08-22 16:42:13 -03:00

69 lines
2.5 KiB
C++

#pragma once
#include "BasecampModelRoles.h"
#include <QAbstractListModel>
#include <QByteArray>
#include <QHash>
#include <QList>
#include <QString>
#include <QVariantList>
// One row per loaded/known module, normalised so QML can bind by named role
// (`model.label`, `model.isLoaded`, `model.cpu`, …). Rows come from
// MainUIBackend::buildUiModulesSnapshot() /
// MainUIBackend::buildCoreModulesSnapshot() (composed over UIPluginManager +
// CoreModuleManager + PackageCoordinator) as QVariantList maps — this model
// wraps that data in a real Qt model so the inspectors don't need a
// QML-side adapter (see the deleted ModuleTableModel.qml).
//
// Two consumers share the same schema: the UI-plugin inspector uses the
// version/description/iconPath columns; the core-module inspector uses
// cpu/memory. Roles unused by either side simply render as empty.
//
// `replaceRows` patches in place when the row identities are unchanged, so the
// 2-second core-stats poll doesn't reset the model (which would drop the
// selection + flicker every delegate).
class ModuleInstanceModel : public QAbstractListModel, public ModuleInstanceRoles {
Q_OBJECT
public:
Q_ENUM(Roles)
explicit ModuleInstanceModel(QObject* parent = nullptr);
int rowCount(const QModelIndex& parent = {}) const override;
QVariant data(const QModelIndex& index, int role) const override;
QHash<int, QByteArray> roleNames() const override;
// Replace the model with normalised rows derived from `installedModules`
// (the QVariantList shape produced by MainUIBackend's snapshot builders).
// Patches in place when the incoming rows share names 1:1 with existing
// rows in the same order — that's the common case on the stats poll.
void replaceRows(const QVariantList& installedModules);
private:
struct Row {
QString name;
QString label;
QString description;
QString category;
QString type;
QString version;
QString iconPath;
QString installType;
bool isLoaded = false;
bool isMainUi = false;
bool hasMissingDeps = false;
double cpu = 0.0;
double memory = 0.0;
QString statusText() const;
};
static Row toRow(const QVariantMap& m);
// Returns the diff mask for a single row so patchRow can emit dataChanged
// with only the roles that actually moved. Empty vector = identical.
static QList<int> diffRoles(const Row& a, const Row& b);
QList<Row> m_rows;
};