mirror of
https://github.com/logos-co/logos-basecamp.git
synced 2026-08-27 14:51:07 +00:00
fix/plugin-loader-object-dependency-entries
424
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
432f6663c0 |
fix(plugins): read object-form core dependency entries, refuse the rest
The load gate now understands both forms the LGX spec allows, and reports
what it cannot understand instead of walking past it.
Green — 14/14, was 9/14:
PASS : object_entry_yields_its_name()
PASS : object_entry_without_constraints_yields_its_name()
PASS : json_object_entry_yields_its_name()
PASS : hash_object_entry_yields_its_name()
PASS : a_number_is_unrecognised()
Totals: 14 passed, 0 failed
Two behaviour changes at PluginLoader::loadCoreDependencies:
* {"name": "wallet_module", "version": "^2.0.0", "signer": "did:…"} now
loads wallet_module. Before, QVariant::toString() returned a null
QString for it and the `if (depName.isEmpty()) continue;` skipped the
dependency with no error and no log line — the ui plugin then mounted
on top of a core module that was never loaded, and the first symptom
was a dead endpoint somewhere else entirely.
Latent today only because the module ABI still sends bare strings
(package_manager_impl.cpp's toLogosMap flattens the constraint away).
It arms itself the moment that is widened. Every other reader in the
fleet already handles both forms — logos-module's module_metadata.cpp,
logos-standalone-app's mainwindow.cpp, logos-cpp-sdk's
metadata_dependencies.h, lgpm's manifest scan. Basecamp was the outlier.
* An entry that is neither a name nor an object with a string "name" is
now a hard, logged load failure rather than a silent skip. Same reason:
we cannot honour a dependency we cannot name, and mounting anyway hides
it. This also stops a stray scalar being stringified into a module
name — QVariant(42).toString() is "42", and the old loop would have
gone looking for a module called that.
Unreachable from `lgpm install` today: logos-package's Manifest::fromJson
rejects a non-string/non-object entry outright, and lgpm's own scan warns
and drops what gets past it. The branch exists for the manifests that
bypass both — embedded installs written at build time, and anything
edited in place afterwards.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
c96ef04be6 |
test(plugins): pin what the core-dependency loader actually reads
PluginLoader::loadCoreDependencies read each `dependencies[]` entry as
QString depName = dep.toString();
if (depName.isEmpty()) continue;
QVariant::toString() on a QVariantMap returns a NULL QString — no
diagnostic, no exception. So the moment the module ABI is widened to send
the object form {"name": …, "version": "^2.0.0", "signer": "did:…"} that
the LGX spec already allows and lgpm already parses, every CONSTRAINED
core dependency is silently skipped and the ui plugin mounts on top of an
unloaded dependency.
This commit changes no behaviour. It gives that decision a name
(logos::readDependencyEntry) and a test, so the next commit's fix is
visible as a diff in outcomes rather than a diff in expressions.
Red, as expected — 5 of 14 cases fail against the extracted behaviour:
FAIL! : object_entry_yields_its_name() kind 1, want 0
FAIL! : object_entry_without_constraints_yields_its_name() kind 1, want 0
FAIL! : json_object_entry_yields_its_name() kind 1, want 0
FAIL! : hash_object_entry_yields_its_name() kind 1, want 0
FAIL! : a_number_is_unrecognised() kind 0, want 1
(1 = Unrecognised, i.e. skipped. The last one is the mirror image: a
non-string scalar is currently stringified and loaded as a module name.)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
ec5579830c |
Test/pre-seed test_qml_only app for integration-test boot (#343)
* ci(tests): run shutdown-test in the PR gate and enforce the combined suite time budget
* test(harness): add shared fixtures (LGX generator, G-ERR/G-ALIVE/G-EXIT gates, xfail runner) and rea
* test(ui): assert welcome page state on first launch before any interaction (A1)
* fix(tests): retry the A1 greeting assertion so it cannot race the async launcherApps refresh
* ci(tests): tee the app log into BASECAMP_APP_LOG so the ui-tests G-ERR gate scans real output
* fix(tests): typename
* fix(tests): shutdown test to follow behavior from
|
||
|
|
f6bca12270 |
Test/MCP preparation UI tests (#338)
* ci(tests): run shutdown-test in the PR gate and enforce the combined suite time budget
* test(harness): add shared fixtures (LGX generator, G-ERR/G-ALIVE/G-EXIT gates, xfail runner) and rea
* test(ui): assert welcome page state on first launch before any interaction (A1)
* fix(tests): retry the A1 greeting assertion so it cannot race the async launcherApps refresh
* ci(tests): tee the app log into BASECAMP_APP_LOG so the ui-tests G-ERR gate scans real output
* fix(tests): typename
* fix(tests): shutdown test to follow behavior from
|
||
|
|
1e07e9887e |
refactor(plugins): admit consumers through the shared verb (#359)
* refactor(plugins): admit consumers through the shared verb Replaces the hand-rolled isolate/mint/register sequence with logos::admitConsumer. Net -16 lines. This is where the duplication bug lived: the registration used to sit inside the has-a-backend branch, below an early return, so a pure-QML plugin registered nothing and called out on the host's ambient ring. That worked only because the ring already held every token and the handshake was never reached — and logos-protocol #71 removes the ring, so it would now be a hard failure rather than a silent elevation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(build): link the view-module-runtime archive before the qt host The Windows cross-build of this branch failed to link, and it is the only platform that could have told us: liblogos_view_module_runtime.a(LogosQmlBridge.cpp.obj): undefined reference to `LogosAPI::forIdentity(QString const&, QObject*)' The symbol is not missing. It is present and correctly mangled in both halves of the host package — nm shows T _ZN8LogosAPI11forIdentityERK7QStringP7QObject and the matching __imp_ thunk in liblogos_qt_host.dll.a — and the archive asks for exactly that name. What was wrong is the ORDER. liblogos_qt_host.dll.a is an IMPORT library, so GNU ld treats it like any other archive: it takes only the members that satisfy references already undefined when it reaches them. logos_qt_host_shared sat AHEAD of LOGOS_VIEW_MODULE_RUNTIME_LIB, so when ld walked the host nothing had asked for forIdentity yet, the member was skipped, and the archive's demand a moment later had nowhere left to resolve. This is the same rule the comment on logos_core below already spells out for four other symbols; the host simply had not needed it yet. It had not needed it because something else was holding the old order up. While app/PluginLoader.cpp called LogosAPI::forIdentity itself, basecamp's own objects demanded the symbol before ld ever reached the host, the member was pulled in early, and the archive's later reference resolved against it for free. Admitting consumers through logos::admitConsumer — the commit right before this one — removed the last first-party call, and with it the accident. So the defect is latent-made-live, not new: the link line has been wrong for as long as the archive has referenced a symbol it does not define. Linux and macOS cannot see any of this. There the host is a real shared object, and shared libraries satisfy undefined references regardless of position, which is why the full x86_64-linux matrix — 19 outputs, every check — was green with the broken order. A green Linux build is not evidence about this line. Measured on a 24-core x86_64-linux box, cross-building x86_64-windows: before packages.x86_64-windows.symbol-gate FAILS to link symbol-gate-negative / bin-bundle-dir / default all FAIL, same cause after packages.x86_64-windows.symbol-gate OK and basecamp master cross-builds the same output green on the same box, which is what established this as a regression of this branch rather than a broken toolchain. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore(deps): move onto logos-protocol 0.8 and logos-plugin-qt master logos-plugin-qt#26 made protocol 0.8 a HARD FLOOR for every consumer of logos-qt-host — cpp/logos_provider_object.cpp and cpp/qt_provider_object.cpp call TokenManager::saveInboundToken unguarded — so plugin-qt and protocol move together or not at all. logos-protocol 2e3344ac -> 42460e5b (0.7 -> 0.8, protocol#73) logos-plugin-qt 1aa3e31c -> 048152f2 (plugin-qt#26) logos-liblogos eeb5cd32 -> 5c095129 (liblogos#186, NOT MERGED YET) liblogos is a branch pin because #186 is the liblogos half of this same wave and is still open; retire it for a plain master URL once that lands. This PR's CI was last seen failing at logos-co/setup-nix-cache-action with HTTP 502, every build step `skipped`, which made it look like pure infrastructure. It was not only that. Building the pre-relock tree of THIS branch on a real box reproduces a second, independent failure — the same one logos-standalone-app#42 had: app/PluginLoader.h:12:10: fatal error: logos_consumer.h: No such file or directory logos_consumer.h arrives with #26, so admitting consumers through the shared verb could never have compiled against plugin-qt 1aa3e31c. The 502 hid it. TWO follows added on logos-liblogos, and the honest status of both is that they are REDUNDANT TODAY. That was measured, not assumed: against the pin above, liblogos#186's lock already names plugin-qt 048152f2 — this root's rev — so adding or removing them leaves 6 logos-qt-host derivations and exactly ONE in the built runtime closure, unchanged. Today the two sides agree by coincidence of two locks rather than by constraint. They are added because the next step breaks that coincidence, which was measured too. nix/app.nix:729 copies ${logosLiblogos}/lib/*.{so,dylib,dll} over this app's own lib/ while the binary links logos-qt-host directly. Point this input back at plain master — exactly what retiring the pin does — and liblogos resolves logos-plugin-qt through its own lock again: liblogos master bfbb1998 pins 1aa3e31c and packages a lib/liblogos_qt_host.so at kdz79ljg… against this root's ka5vgzb8…, differing byte-wise. The derivation count goes 6 -> 7. The app would LINK one host runtime and SHIP another in the same directory. Closure audit on packages.x86_64-linux.default (384 paths): logos-qt-host 1 ka5vgzb8…-logos-qt-host-0.1.0 logos-protocol 1 derivation at 0.8, 3 outputs (lib, headers, join) and lib/liblogos_qt_host.so compares BYTE-IDENTICAL to the one in that store path, so the linked host and the shipped host are one image rather than two that merely agree. liblogos_protocol.so is the sole definer of TokenManager:: — LogosBasecamp and ui-host define none of it. logos-view-module-runtime is deliberately NOT given the same follows. It is still 7cbc5a6e, built against plugin-qt ef11c210 and protocol 79894727, so this build does link a 0.7-era static archive against a 0.8 host. That seam was checked rather than waved through: * LAYOUT is safe. LogosAPI's private members are identical between ef11c210 and 048152f2 — same five members, same order, no new virtuals — and 0.8 freezes TokenManager's layout by design. * BEHAVIOUR is safe HERE, for a specific reason. 048152f2 changes LogosAPI::forIdentity to create the private store EMPTY where it used to be bootstrap-seeded, and vmr's include/LogosQmlBridge.h:64 still documents the old contract. But nothing reaches it: basecamp constructs the bridge directly at app/PluginLoader.cpp:292, `new LogosQmlBridge(consumer.api, this)`, on the API logos::admitConsumer already credentialed. vmr's LogosQmlBridge::forIdentity factory is dead code in this consumer; it is only in the link line because it shares an object file with the constructor that is used. Give vmr the follows when vmr#27 moves it to 0.8 — not before, since that PR owns the migration. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> VERIFIED BY BUILDING, each output individually with --print-out-paths on a 24-core x86_64-linux box. 22 of 23 non-empty: x86_64-linux packages default app main-ui-plugin package-manager-ui-plugin bin-bundle-dir bin-bundle-dir-inspector bin-appimage mcp-server logos-qt-mcp coverage x86_64-linux checks smoke-test symbol-gate symbol-gate-negative unit-tests qml-tests sandbox-test host-services-test integration-test x86_64-windows symbol-gate symbol-gate-negative bin-bundle-dir default symbol-gate passes on BOTH targets, and Windows is the one that counts: PE has no symbol interposition, so a duplicate runtime that Linux collapses to one is fatal there. symbol-gate-negative — the planted-duplicate control — passes on both too, so the gate is still capable of failing. The single red is checks.x86_64-linux.shutdown-test, and it is PRE-EXISTING, not a regression of this branch. basecamp master builds the same check on the same box and fails it identically — "Linux: Window.close() quits (Alt+F4 / X button convention) ... FAIL — did not exit within 10000ms", 3 passed / 1 skipped / 1 failed, byte for byte the same shape. It needs a window manager this environment does not have. Note also that no CI job builds it: build.yml runs unit-tests, qml-tests, sandbox-test, integration-test, host-services-test and the symbol gates, and never shutdown-test. NOT COVERED HERE, and this box cannot cover it — stated rather than implied: * aarch64-linux — both build-appimage and test-linux matrix legs * macOS — build-macos-app, test-macos, and the *-bundle outputs only they build (integration-test-bundle, host-services-test-bundle, smoke-test-bundle) * the build-windows job's zip packaging step and its staged-file count assertion; the three nix builds it runs are covered, the packaging is not * doctests.yml on both ubuntu-latest and macos-latest * the three Jenkins packaging jobs (jenkins/prs/package/linux/{x86_64,aarch64} and macos/aarch64), which reported nothing at all on the last run * bump --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
6362789960 |
chore(deps): relock the package-manager repos, now that they are cut too
The previous relock took this lock to 10,215 nodes and named what was left:
logos-package-manager-ui alone reached ~9,700 of them, and no bump on this
side could touch it, because that repo and its two siblings were still
pinning pre-cut copies of logos-module-builder internally.
Those three have now had the same treatment upstream — one stale pin each,
plus a second pass in the UI once its siblings landed:
logos-package-manager-module 3,178 nodes -> 379
logos-package-downloader-module 3,178 nodes -> 379
logos-package-manager-ui 9,751 nodes -> 1,123
So this side is a three-input bump with nothing clever in it:
logos-package-manager-ui 6bf15008 -> e3bbf3d5
logos-package-manager-module 63a6f8ab -> cb1ca3c9
logos-package-downloader-module 1a47ae68 -> 50dafbf3
10,215 nodes / 259,003 lines -> 1,587 / 36,026
Across the whole sequence this lock goes 19,832 -> 1,587 nodes and
507,875 -> 36,026 lines: -92% and -93%.
Only those three of the 21 root inputs move; the other eighteen resolve to
the same revs, and flake.nix is untouched.
Worth recording why it took this shape. The first attempt was deep
`follows` declared HERE, describing three other repos' internal wiring. It
reached -85% and was rejected twice on evidence: once when this branch
moved and the derivation-graph proof stopped holding — the same follows
then broke main_ui's dylib load — and once when a parent overriding
logos-capability-module, which is exactly how logos-workspace consumes this
flake, silently dragged a build-time liblogos eight days older. Fixing it
upstream has neither failure mode: four repos each dropped one stale pin,
and nothing here encodes anyone else's structure.
Verified on the merged master (
|
||
|
|
e5a4be41a4 |
feat(ui): let an in-process UI plugin finish before its widget is destroyed
teardownUiPluginWidget destroyed the widget immediately. Core modules and
views (ui-host) both got a grace period when the teardown hook shipped;
in-process `type: ui` plugins did not.
WHY THIS PATH IS NOT THE OTHER TWO. The shipped helper runs a nested
QEventLoop, which is correct where it is used: logos_host calls it after
QtApp::exec() has returned and ui-host after app.exec() has, so in both
cases there is no outer loop left. Here there is. This runs on the LIVE UI
thread, from a user action, in the middle of widget destruction —
pluginWindowRemoveRequested, component->destroyWidget, deleteLater. Spinning
a nested loop there is the re-entrancy hazard that already cost this
codebase a SIGSEGV in the wallet's QtRO read stack. So Asynchronous DEFERS
rather than blocks: connect unloadFinished(), arm a deadline, and run the
existing teardown body from whichever fires first.
WHAT ui_qml CANNOT DO, and why that is not a gap. For ui_qml there is no
in-process plugin object to ask: PluginLoader emits pluginLoaded(...,
nullptr, UiQml, viewHost) — the component is null because the plugin is
QPluginLoader-loaded inside the ui-host CHILD PROCESS. Basecamp holds a
QQuickWidget and a QProcess wrapper. No host-side call from here could ever
reach it, which is exactly why that path's grace period lives in ui-host
instead (logos-view-module-runtime#26). unloadHookTarget() encodes that:
legacy plugins have a target, ui_qml returns nullptr and the teardown
proceeds as before.
Consequences handled rather than hoped for:
* Idempotence, which this function documents and callers rely on: a
second call while a deferral is in flight neither starts a second
teardown nor tears down underneath the first.
* The widget is held by QPointer across the deferral, not raw — anything
else may destroy it meanwhile, and a stale raw pointer would be a
use-after-free rather than a skipped teardown.
* Both the signal and the deadline are disconnected on whichever arrives
first, so the body cannot run twice.
* The synchronous path — no hook, or Synchronous — is byte-for-byte what
it was. That is the common case and it stays free.
The trade-off is stated on the declaration: a caller that needs the widget
gone before it proceeds cannot get that guarantee from this function any
more. Blocking to restore it is the thing that is not available here.
unit-tests and shutdown-test green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
819a586cab |
docs: retire references to files, outputs and pins that no longer exist
Sweep of documentation and comments the shell split and the pin retirement
made false. No behaviour change except one dead nix binding, below.
flake.nix
* Deletes `appImage = import ./nix/appimage.nix {...}`. That file is NOT in
the tree; the binding survived only because nix is lazy and nothing ever
forced it. Anyone referencing `appImage` would have hit a file-not-found at
eval. The shipped AppImage is the `bin-appimage` output, built by
nix-bundle-appimage.
* Four stale `Rev-pinned:` comment blocks -- logos-module-loader-qt,
logos-liblogos, logos-capability-module, logos-package-manager-ui. None of
those inputs carries a rev or ref any more;
|
||
|
|
c8b6189e92 |
chore(deps): take the cycle cut that already landed upstream
The follows merged in #353 stopped this lock unrolling two private copies
of the package-manager flakes, and left 13,476 nodes — most of them the
logos-module-builder cycle: capability-module -> module-builder ->
standalone-app -> liblogos -> capability-module, which Nix unrolls one
fresh copy of per lap.
That cycle has since been cut UPSTREAM, which is where it always belonged.
The three repos in it are now small on their own masters —
logos-standalone-app 252 nodes, logos-capability-module 130,
logos-liblogos 211 — so this repo does not need to describe three other
repos' internal wiring to get the benefit. It just needs to stop pinning
pre-cut revisions:
logos-capability-module c670f7f2 -> 973f71ba
logos-liblogos b2a9a0ba -> eeb5cd32
logos-package-manager 3ceabe25 -> 7c5aad9a
13,476 nodes / 346,113 lines -> 10,215 / 259,003 (-24%, -48% from
the original)
Only these three of the 21 root inputs move; the other eighteen resolve to
the same revs. Worth naming why the win is smaller than the upstream
numbers suggest: logos-package-manager-ui (9,722 nodes),
logos-package-manager-module and logos-package-downloader-module are
ALREADY at their masters here, and their own locks still pin pre-cut
capability/liblogos internally. A bump on this side cannot reach those —
they collapse when those repos relock, not when this one does.
Kept separate from #353 deliberately. That change moved no versions at all,
which is what made it safe to verify once; this one moves three, and
bundling version movement into an unrelated change is how #350 went red
across four jobs earlier — when CI fails you cannot tell which half did it.
Verified on
|
||
|
|
2c32cfef22 |
fix(ci): run the symbol gate — on Windows too, where it matters (#354)
* fix(ci): run the symbol gate, and teach it the Windows layout
The gate was exposed in flake.nix `checks` and built by nothing. Both
workflows enumerate individual `nix build .#<output>` steps by hand and no
`nix flake check` runs in this repo, so `git grep -E 'symbol-gate|flake check'
-- .github/` returned nothing: the one control that mechanically enforces the
one-runtime invariant was dead code. A PR relinking the logos runtime into
main_ui would have merged green — and on Linux and macOS it would have RUN
green too, because both interpose a duplicate definition away at load time.
Two things were wrong with the gate itself, both specific to the platform
where a duplicate is actually fatal:
* the peer-image sweep globbed `$ROOT/lib` for *.dylib/*.so only. On Windows
every liblogos_* shared image is staged into bin/ as a .dll, so the sweep
matched nothing, no definer was found, and the exactly-one assertion
reported "0 definers" for TokenManager, LogosAPI and LogosAPIClient — it
could not pass on a correct tree. Its three sibling probes (provider,
negative control, consumer) had each been taught bin/*.dll; this one had
not.
* `nm -D` was selected for every non-Darwin target including the mingw
cross, and a PE has no ELF dynamic symbol table. Measured against a real
mingw PE (libffi-8.dll, binutils 2.46): `nm -D` reads 0 lines and errors,
plain `nm` reads 687, `nm --defined-only` 686. valid() would have caught
that and aborted the gate as vacuous rather than passing it, so this was
fail-closed — but the gate would never once have run on Windows.
x86_64-windows is not wired into CI here: that attribute cannot be evaluated
as pinned (see flake.nix's binBundleDir note), a pre-existing blocker. The
gate now understands the layout, so wiring it is a one-line follow-up.
Also drops two stale comments that pointed at LogosSharedFromDll.cmake, the
single-provider shim deleted in #348, and the claim that liblogos_core is the
provider — liblogos_protocol and liblogos_qt_host define these types now, which
is exactly why the assertion is exactly-one rather than naming an owner.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(symbol-gate): call nm and c++filt by their target-prefixed names
The cross bintools installs ONLY x86_64-w64-mingw32-nm and
x86_64-w64-mingw32-c++filt; there is no bare `nm` or `c++filt` on PATH inside
the Windows derivation. Every measurement therefore produced nothing, and
valid() refused to assert over an empty read -- so the gate got as far as
identifying the right images and then stopped:
provider = bin/liblogos_core.dll
consumers = bin/LogosBasecamp.exe
plugins/package_manager_ui/package_manager_ui_replica_factory.dll
plugins/main_ui/main_ui.dll
== each runtime type is defined by EXACTLY ONE image ==
liblogos_core.dll ERROR: nm read 0 symbols — vacuous
That is the vacuity guard working exactly as intended: fail-closed rather than
report a reassuring zero. But it meant the gate could never actually run on
Windows. The tool was missing, not incapable -- the host's own nm reads these
PEs fine (7350 symbols out of liblogos_core.dll).
stdenv.cc.targetPrefix is "" natively, so this is a no-op on Linux and macOS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(symbol-gate): do not count PE import thunks as definitions
With nm reachable, the gate ran on Windows for the first time and reported a
split-brain that is not there:
TokenManager 4 definers: liblogos_core.dll(3) LogosBasecamp.exe(1)
liblogos_protocol.dll(41) liblogos_qt_host.dll(5)
liblogos_core.dll defines ZERO runtime symbols by design, and measures 0 on
macOS. The Windows numbers are an artifact of PE: for every imported function
GNU ld synthesizes a jump stub in .text AND an __imp_<mangled> slot in the
import address table, and `nm --defined-only` reports the stub as `T`. The
pairing is visible directly:
I __imp__ZN12TokenManager8instanceEv
T TokenManager::instance()
So the filter is that pairing: a symbol counts as DEFINED only when the image
has no __imp_ slot for it. That is the right discriminator rather than merely a
working one -- a genuine second copy statically linked into an image has no
__imp_ slot and still counts, which is exactly the case this gate exists to
catch.
The PE export table would also have suppressed the phantom (liblogos_core.dll
exports 0 TokenManager symbols, liblogos_protocol.dll exports 45), and it was
the first thing I reached for. It is the wrong tool here: a real private copy
is absent from the export table too, so it would trade this false positive for
a false negative.
Measured on the cross build, the filter reproduces the macOS shape exactly:
liblogos_core.dll TokenManager 0 LogosAPI/Client 0
liblogos_protocol.dll 41 109
liblogos_qt_host.dll 0 29
LogosBasecamp.exe 0 0
main_ui.dll 0 0
package_manager_ui_replica_factory.dll 0 0
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* ci(windows): run the symbol gate where it actually matters
The gate now evaluates, builds and passes for x86_64-windows, so wire it into
build-windows and drop the caveat the other two jobs carried saying it could
not be. PE has no symbol interposition, so a duplicate runtime that Linux and
macOS silently collapse to one is fatal only here -- and CI cross-builds this
bundle and never runs it, which makes a build-time assertion the only signal
that exists.
Measured on a real cross build (needs an x86_64-linux builder; logos_build_info.h
is an x86_64-linux derivation, so this cannot run from an aarch64-darwin host):
TokenManager 1 definer: liblogos_protocol.dll(41) OK
LogosAPI 1 definer: liblogos_qt_host.dll(32) OK
LogosAPIClient 1 definer: liblogos_protocol.dll(122) OK
TIER 1 LogosBasecamp.exe 0 · main_ui.dll 0 · pmui_replica_factory.dll 0
SYMBOL GATE: PASS
and the negative control rejects a planted duplicate on PE too, catching 41
TokenManager and 122 LogosAPIClient definitions including the one that names
the failure exactly:
guard variable for TokenManager::instance()::instance
That control is what makes the pass mean something. It also proves the __imp_
thunk filter does not over-reach: a genuine private copy is still detected at
full strength.
Also corrects the binBundleDir note, which was stale in BOTH of its claims --
logos-package-manager-ui not cross-compiling, and the separate EVAL-time
blocker in logos-package-downloader-module. Both were fixed upstream. The
bundle the gate just ran against contains package_manager_ui, so the tree the
note's measurements describe is missing a plugin that is no longer missing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
da5da842b9 |
fix(shell): stop reading the package_manager_ui widget after the host frees it
m_pmuiWidget was a raw QWidget* pointing at a widget the HOST owns and frees:
UIPluginManager::unloadUiModuleImpl and ::teardownUiPluginWidget each emit
pluginWindowRemoveRequested and then deleteLater() it. The shell's handler
returned early for exactly that widget without clearing the pointer, so after
an unload it was read at four sites as a freed pointer, and `!m_pmuiWidget`
never became true again -- onSectionIndexChanged(2) therefore never re-issued
loadUiModule("package_manager_ui").
QPointer alone is not the whole fix. The widget had replaced the placeholder at
kModulesStackIndex, so freeing it also takes a page out of a stack that every
section switch indexes into by constant: setCurrentIndex(kModulesStackIndex)
then addresses a two-page stack and the Package Manager section is dead for the
rest of the session. The removal path now puts the placeholder back, which also
restores the exact startup state that onPluginWindowRequested already knows how
to consume -- it looks up widget(kModulesStackIndex) and swaps it out.
Placeholder construction moves into one helper used by both paths, so the
startup page and the restored page cannot drift.
This is the same guard the host applies to its own widget maps
(m_uiModuleWidgets / m_qmlPluginWidgets are QPointer); the shell's copy of the
pointer had not been given it. Reachable from the UI: package_manager_ui is
listed by UIPluginManager::uiModules(), and the Apps Inspector row wires
onAppUnloadRequested to backend.unloadUiModule(name).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
5caa8fc619 |
chore(deps): stop unrolling two private copies of the package-manager flakes
flake.lock is 19,832 nodes / 507,875 lines. 7,018 of those carry their own
logos-nix edge, and therefore their own nixpkgs. For contrast,
logos-view-module-runtime costs SEVEN nodes — it is the one input here that
already declares `follows`.
logos-package-manager-ui reaches logos-package-manager-module and
logos-package-downloader-module under DIFFERENT INPUT NAMES
(`package_manager`, `package_downloader`) than this root does. Same flakes,
same revs, identical resolved subtrees — Nix just cannot know that, so it
unrolls a private ~3,178-node copy of each. Two root-to-root follows,
retargeted across the name difference:
19,832 nodes / 507,875 lines -> 13,476 / 346,113 (-32%)
Nothing about what is built changes: all 21 root inputs resolve to the same
revs before and after, and `nix flake lock` re-runs as a byte-level no-op
with no unmatched-follows warnings.
WHAT IS DELIBERATELY NOT HERE. Four deeper follows take this to 2,992 nodes
(-85%) by sharing logos-module-builder subtrees between consumers and
closing a real cycle (capability-module -> module-builder -> standalone-app
-> liblogos -> capability-module, which Nix unrolls one fresh copy per lap).
They were proven meaning-preserving against an earlier tip — 1361/1361
derivations identical, plus a bisimulation over both lock graphs — and then
that tip moved. On the commit that reworked main_ui into "a plugin that
links no logos runtime", the same follows produce
Failed to load the main UI plugin from .../main_ui/main_ui.dylib:
The shared library was not found
with shutdown-test and smoke-test red while the base is green. A
derivation-graph proof is only as durable as the tip it was taken on.
One of the four was disqualified separately, for a reason that would have
outlived any tip: pointing this UI's standalone-app/liblogos at
logos-capability-module's is a no-op HERE, but a parent that overrides
logos-capability-module — exactly how logos-workspace consumes this flake —
pulls them apart, and this UI's build-time liblogos silently follows the
parent's down. Measured at the workspace's own pin: 93207e41 -> 56aa8bb4,
eight days older, no warning.
The two kept are ordinary root-to-root sharing, the idiom already used by
logos-view-module-runtime and nix-bundle-macos-app above. The remaining
10,484 nodes are the module-builder cycle, and the fix for that belongs
upstream in logos-module-builder / logos-capability-module, where closing it
once collapses every consumer at the same time.
Rebased onto master after logos-basecamp#337 merged and its branch was
deleted; re-verified from scratch rather than carried over, because the base
moving is what invalidated the deeper follows. On
|
||
|
|
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.
|
||
|
|
9c3d023062 |
feat(shell): put the UI shell behind IShellHost, and order its shutdown
The shell held the host's objects: MainContainer owned MainUIBackend, took a
LogosAPI* and a QtLogosCore*, and connected to backend signals by concrete
type. Nothing stopped it minting identities or reading the token store.
Three headers in app/interfaces/, on the include path both targets share, so
there is exactly one copy and source drift is impossible:
* IShellHost -- 8 operations the shell may perform
* IShellObserver -- 5 notifications the host may deliver
* IShellView -- how the host builds and tears down the shell
Only QObject*, QWidget* and Qt value types cross. MainContainer holds one
IShellHost* and nothing else; QML reaches the backend as an opaque QObject*
via backendObject(), resolved through the metaobject, so no host C++ type has
to be nameable by the shell.
Ownership inverts: Window owns MainUIBackend, ShellHostAdapter and
MainShellView; the shell borrows. Teardown stops being a consequence of
construction order and becomes a stated contract:
1. beginShutdown() unmounts in-process UI plugin widgets WHILE the shell's
tree is intact -- they are docked inside it
2. destroyShell() detaches the observer, then deletes the shell
3. the backend goes, tearing down Package -> UIPlugin -> Core
4. main() destroys the core facade
Every observer forward is null-guarded: PluginLoader dispatches through
QTimer::singleShot(0, ...) and a 30s ViewModuleHost timeout, so a callback can
land after teardown starts, and QPointer cannot help -- IShellObserver is not
a QObject. UIPluginManager's plugin-widget maps become QPointer for the
mirror-image reason: those widgets are docked inside the shell, so its Qt
parent can destroy them without going through unloadUiModule.
IComponent is untouched and still serves the third-party legacy widget
plugins PluginLoader loads.
|
||
|
|
13665a5e34 |
feat(host-core): adopt the SDK core facade, and delete both C ABI mirrors
CoreModuleManager and main.cpp each carried a hand-written `extern "C"` mirror
of liblogos' core-management ABI -- two blocks declaring the same symbols in
one image, an ODR hazard with no diagnostic. Both are replaced by
logos::qt::QtLogosCore over logos::host::LogosCore, which owns the
char*/char** marshalling, the `delete[]`-not-`free()` rule, and the pre-start
ordering constraint -- the last as constructor arguments, so the illegal order
stops being expressible.
Four things about the stats path that fail INVISIBLY, all preserved:
* "cpu"/"memory" stay 1-decimal STRINGS -- QML and ModuleInstanceModel bind
those names, so the facade's cpuPercent/memoryMb spelling stops here
* allStats() is called ONCE per tick; each moduleStats(name) repeats the
full C call and parse, and the snapshot walks every known module
* the three-way back-compat fallbacks are kept for older runtimes
* memoryMb (double), never memoryBytes
The cpp-sdk lock moves for that last point: the pin predated the rename, and
that pair does not compile -- qt-sdk's header reads s.memoryMb while the
pinned cpp-sdk struct still called it memoryBytes.
Also drops a QTimer in main.cpp that was started and stopped with no
connect() at all.
|
||
|
|
5a91e65022 |
chore(deps): pick up ui-host's teardown grace period
Gives an out-of-process UI host time to shut down cleanly instead of being killed mid-teardown. |
||
|
|
4877c7b0b7 |
feat(shared-runtime): link the shared libraries, and delete the shim
The app and every in-process plugin now take TokenManager, StoreRegistry, LogosAPI and LogosAPIClient from the shared libraries that own them (liblogos_protocol, liblogos_qt_host) instead of each linking a static copy. A second copy is a second TokenManager: the host writes a capability token into one store, another image reads an empty one, and every cross-module call is refused at runtime with no build diagnostic. The static archives are emptied on every platform, so the shared library is the only provider and the LOGOS_SHARED_USE_DLL shim is gone. nix/symbol-gate.nix enforces the invariant -- across the images sharing one process, each runtime type is defined by EXACTLY ONE -- with a negative control that plants a real duplicate and asserts the gate rejects it. Two measurement traps it had to survive: nm reads zero symbols from nix's shell wrappers and must be pointed at the real binary, and `find` without -L skips a symlinked plugin entirely, so the gate silently measured nothing. |
||
|
|
3c21c1f075 |
chore(deps): retire the rev pins, now that each upstream has merged
Also pins package-manager-ui at the branch carrying both fixes it needs. |
||
|
|
7851bf9f46 |
feat(host): per-plugin identities, an opt-in access policy, and the source split
Each loaded plugin -- including pure-QML ones -- gets its own LogosAPI identity rather than sharing the host's, so a plugin's calls are attributable and can be refused independently. The host-services grant is wired through to capability_module, and its trust root is guarded on an OUTCOME rather than a log line. Inter-module access policy stays OFF by default: enforce mode's derived deny-by-default gates every ui_qml app's calls to its own backend module, because UI plugins load out-of-process and are not tracked as dependents in the core ModuleRegistry. Operators opt in per launch with --access-policy enforce or LOGOS_ACCESS_POLICY. Takes the Qt host runtime from logos-plugin-qt rather than logos-qt-sdk, which keeps only the Qt<->lp seam headers, and moves logos-protocol onto the rev that split host needs. On Windows logos_core must come LAST on the link line: GNU ld resolves an archive left to right, so the view runtime's references have to be undefined already when it reaches the import library. Separates the two source trees -- app/ is the host, src/ is the UI shell -- and brings the CI onto setup-nix-cache-action. Merges master. |
||
|
|
d7c2f6e355 |
fix: Reinstall doesn't remove files deleted by the new version — install merges into the existing directory
fixes #313 |
||
|
|
9118071484 |
fix(ci): wrong path to linux arm pipeline in combined jenkinsfile (#335)
Signed-off-by: markoburcul <marko@status.im> |
||
|
|
3c1a88e9f0 |
Test/failure paths (#318)
* test: failure paths * fix: add the connect guard * fix: add assertion for root.errorMessage |
||
|
|
f57e1692b9 |
feat(windows): build and publish a Windows bundle alongside the other platforms (#334)
* feat(windows): build and publish a Windows bundle alongside the other platforms
Windows had CI but no downloadable artifact. A release/* push produced an
AppImage per Linux arch and a macOS .app tarball, and nothing a Windows user
could open.
`build-windows` mirrors those jobs: a CROSS build on ubuntu-latest -- nix does
not run on Windows, so there is no Windows runner here; executing the result on
real Windows is what logos-windows-ci's native-smoke leg does. The release job
needs it, downloads it, and attaches artifacts/*-windows.zip.
bin-bundle-dir rather than `default`: it is the same portable directory the
AppImage and .app jobs package, laid out the way the app expects (bin/ +
lib/qt-6/{plugins,qml} + an explicit qt.conf). A PE records no rpath, so that
layout IS the deployment contract.
.zip rather than .tar.gz, because this is the one artifact here whose users are
on Windows, where Explorer opens a zip and nothing opens a tarball.
THE PACKAGING STEP ASSERTS RATHER THAN TRUSTING ITS EXIT CODE. The bundle's DLLs
are symlinks into the nix store, and an archive of dangling links extracts to a
tree that cannot start. nix-bundle-lgx shipped exactly that: `cp -a` implies
--no-dereference, the payload lost 75% of its files, and the step exited 0. So
this copies with -L and then compares the zip's entry count against the bundle's
file count, failing on a shortfall; it also refuses a bundle with no .exe.
The comparison is `<`, not `!=`: a zip counts directory entries too, so a
healthy bundle legitimately produces MORE entries than files. Rehearsed against
a real bundle on an x86_64-linux builder -- 1677 files -> 1768 zip entries,
3 .exe, 0 dangling links after cp -rL, 117 MB.
Named logos-basecamp-x86_64-windows.zip: the full word `windows`, because
logos-release-set's classifier matches on it and a bare `win` would swallow
every darWINd asset.
* ci(windows): drop the disk reclaim -- the runner has 108 GiB, not 14
The step's own justification was the reason to keep it and it does not hold:
"ubuntu-latest ships ~14 GiB free" came from this project's notes, written
before GitHub upgraded the hosted runners. A real run of this workflow reports
/dev/root 145G 37G 108G 26% /
so the step was deleting ~10 GiB from a disk with 108 GiB already free, and
paying ~20 s per job for it.
The Windows job substitutes the mingw closure from the Attic cache rather than
building it, so the build-time store requirement is lower still.
Kept available, not deleted: logos-windows-ci's nix-setup still exposes
free-disk-space, defaulting to false, so a genuinely disk-bound job can opt in.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
536234e343 |
fix(windows): bump logos-package-manager-ui so the Windows build completes
The x86_64-windows target could not build. It failed 15 files into
package_manager_ui's compile with
generated_code/include/logos_sdk.h:5:10:
fatal error: package_manager_api.h: No such file or directory
and basecamp is only the messenger. `package_manager_api.h` is the typed API of
the package_manager MODULE, and this lock carried TWO revisions of it:
63a6f8ab publishes ...and x86_64-windows
975dcc50 publishes darwin, linux only
The bad one arrived through this repo's pin of a package_manager_ui that
predated logos-package-manager-ui#68. With no Windows target for that
dependency the header resolved to nothing, no -I reached the compiler, and the
first file including the generated logos_sdk.h failed. Nothing earlier
complained.
`nix flake update logos-package-manager-ui` collapses it to 63a6f8ab alone. It
also brings logos-module-builder 632f601, which includes
ea3b393 fix(cross): fail loudly when a dep publishes nothing for the
target system (#199)
so the next dependency in this position fails in seconds naming itself, rather
than as a missing include after an 87-minute build.
VERIFIED on an x86_64-linux builder -- packages.x86_64-windows.default builds
clean, ma16290wbzdnfpglasxn0dy2k0f9j018:
DLL import closure converged after 5 round(s); staged 9 DLL(s) into bin/
PE import closure verified: 117 root(s), 7141 import(s) read, 0 unresolved.
Found by the Windows cache-prime job's third stage, which is the first thing to
have cross-built this closure from a store with NO history. package_manager_ui
was verified working on Windows months ago, but hand-built on a machine whose
store already held what a clean runner has to produce -- so the missing pin had
never been exercised.
|
||
|
|
adee4734e6 |
ci: take Nix and the Attic cache from setup-nix-cache-action
build.yml owned its own Nix installer, substituter list, trusted keys, cache selection and push -- four times over, in two different spellings. All of it now comes from logos-co/setup-nix-cache-action@v1. 237 lines -> 203, and four (installer + attic-action) pairs collapse to four one-step uses. WHAT THIS FIXES, beyond the duplication: * THE SIGNING KEY. This was the only repo in the fleet trusting public:Z1wyVBEx8PHbXujYB52Mysv9Rd8rWIhyQ3bQyef9yy4=; the shared action configures public:l4HrXgL4nw246+LBh2SOJyhz64BoGegOYLheT/iIAPU= for the same cache name. I could not determine from outside which is current -- a narinfo's Sig names the key `public` but not its value, and attempts to discriminate by verifying a copy failed on incomplete closures rather than on signatures. Two answers to that question was the actual problem; there is now one. * NIX VERSION. Two jobs ran cachix/install-nix-action@v27, which is Nix 2.22 -- below the 2.26 floor where `--override-input` keeps the overridden input's own lock. The shared action uses @v31 (nix_version=2.35.1). The other two jobs ran DeterminateSystems/nix-installer-action@main, i.e. an unpinned ref on a release-producing workflow. * IT NOW READS THE ci CACHE. Every block here configured only cache.nix.logos.co/public as a substituter while pushing to `ci` on branches -- so a branch build could not reuse what any earlier branch build had pushed. The shared action configures both. ONE DELIBERATE REGRESSION, stated rather than buried: the old steps passed `inputs-from: "."` to attic-action, which also pushes the flake inputs' store paths. setup-nix-cache-action does not expose that input, so slightly less is pushed per run. Reading the ci cache should more than compensate on branches, but it is a real difference and the right fix is an `inputs-from` passthrough in the shared action rather than keeping a second implementation here. The `if: env.ATTIC_ENDPOINT != ''` guards are dropped with the env block they read. They existed to skip the push on fork PRs where secrets are absent; the shared action needs no guard, because with no token it configures the substituters and skips the push -- which is what those guards approximated. This repo's ATTIC_ENDPOINT secret is consequently unused here, and the action's default endpoint is the same host this file already named. Verified: actionlint clean, and all five jobs keep their build/test steps unchanged -- only the preamble moved. NOT verified: this has not run. It touches the release path, so it wants a real CI run on a branch before merge. |
||
|
|
fb64b23144 |
feat(ci): change s3 storage from DO to Hetzner
Referenced issue: * https://github.com/status-im/infra-ci/issues/282 Signed-off-by: markoburcul <marko@status.im> |
||
|
|
525b43cd1d | feat: support icons available with packages (#321) | ||
|
|
03e054fb08 |
test: module unload doctest (#329)
- disable unload/load buttons for core modules |
||
|
|
72c440fc77 |
fix(windows): bundle Basecamp with the real bundler, not the identity bypass (#324)
flake.nix carried `winBundler = drv: drv` behind `bundleFor`, placed on an
explicit condition: "the real fix is a PE branch in nix-bundle-dir's bundle.sh
that skips relocation and keeps Qt staging; when that lands, DELETE bundleFor
and go back to dirBundler on every platform."
It landed. No re-pin was needed to pick it up, contrary to expectation: this
flake's root nix-bundle-dir input already resolves to f843b8ec, which IS the
current tip of nix-bundle-dir main (checked against the API, not a local ref).
The unsuffixed `nix-bundle-dir` node in flake.lock is NOT the root's -- root
resolves to nix-bundle-dir_1061 -- and both happen to be f843b8ec anyway.
WHAT THE BYPASS ACTUALLY PRODUCED
Both trees were realised on x86_64-linux from ONE tree, so the only variable is
whether dirBundler is applied; verified by store path, the bundle derivation's
only basecamp input is exactly the un-bundled derivation.
bypass(drv:drv) dirBundler
entries 85 1740
regular files 59 1657
symlinks 12 0
bytes 259 MB 479 MB
*.dll in bin/ 33 88
bin/qt.conf MISSING present
.../platforms/qwindows.dll MISSING present
lib/qt-6 (plugins + qml) MISSING 1533 files
nix closure refs 12 1
The 12 symlinks are the part that matters. bin/Qt6Core.dll, Qt6Gui.dll,
Qt6Widgets.dll, Qt6Network.dll, Qt6RemoteObjects.dll, libssl/libcrypto,
libpng16, libzstd, libb2, pcre2 and double-conversion were symlinks into
/nix/store. Copy that tree to a Windows box -- the whole point of a portable
bundle -- and every one dangles: 0xC0000135, no output, before main(). So the
comment claiming a PE "needs no relocation" was only half right; rpath
REWRITING is indeed moot, but Phase 1's `cp -aL` is not, and the bypass skipped
it. The bundled tree has zero symlinks and one remaining nix reference, an
inert /nix string in a PE's data section that imports nothing.
55 DLLs exist only in the bundled bin/: the Qt Quick / Controls / Labs set the
fixpoint sweep pulls in once QML modules are staged, plus libcurl and its
TLS/HTTP2 chain mirrored beside the package_downloader module importing them.
Not a strict superset: README.txt and share/ (a .desktop file and a hicolor
icon, 9 paths) are dropped. Not a Windows regression -- bundle.sh Phase 1
copies bin/, lib/ and extraDirs on every platform, so the shipping Linux and
macOS bundles never had them either; the bypass "kept" them only by doing
nothing. Both are dead weight off-store: README.txt is a build-info file whose
every line is a /nix/store path, and a .desktop file does nothing on Windows.
THREE extraClosurePaths ADDITIONS (nix/app.nix)
Removing the bypass surfaced three real gaps, each a build the bundler FAILED
loudly rather than shipping a tree that dies before main():
qtdeclarative staged 17 Qt plugin files, then "QtQuick/QtQml DLLs are in
bin/, so this bundle renders QML, but no QML module directory
for this target was found in the closure". With it: 1651 QML
files staged and 40 more Qt DLLs swept in.
libjpeg.bin "libjpeg-62.dll -- imported by .../imageformats/qjpeg.dll"
sqlite.bin "libsqlite3-0.dll -- imported by .../sqldrivers/qsqlite.dll"
Same root cause each time: an ELF/Mach-O binary records where its dependencies
live, so Nix scans out a reference and closureInfo gets them for free; a PE
import table carries base names only and embeds no store path, so Nix records
nothing and the bundler cannot stage what is not in the closure. `.bin` rather
than the default output was checked, not assumed -- both DLLs are installed
into the `bin` output.
Windows-gated, and that gating is proven rather than asserted:
packages.aarch64-darwin.app.extraClosurePaths evaluates to the identical two
derivations before and after this commit, while x86_64-windows gains exactly
the three above.
COVERAGE, STATED PLAINLY
Nothing here has been RUN on Windows, by this commit or by CI, which has never
executed a Windows binary. Every claim is build-time and tree-shape only.
The measured trees came from a harness that drops ONE entry from
installedDistributed, packageManagerUIPlugin, because logos-package-manager-ui
does not cross-compile at the rev this flake pins: its generated logos_sdk.h
includes package_manager_api.h, which is not produced for the Windows target.
Pre-existing and independent -- the identical derivation
(b0i9ilm4phszkk0az96z3zhn15nkb9c5) fails when built straight from the pinned
rev 39a5fa5 with no basecamp involved. That plugin takes no part in Qt staging
or the DLL closure, but the numbers above are from a bundle without it.
For the same reason `nix build .#packages.x86_64-windows.*` cannot succeed on
this branch as pinned, and a second unrelated blocker sits in front of it at
EVAL time: logos-package-downloader-module has no x86_64-windows target, so the
attribute does not evaluate. Both blockers predate this change and behave
identically with the bypass in place. The measurement got past the eval blocker
with --override-input onto a local package-downloader-module carrying its
Windows target, and past the build blocker with the one-plugin harness above.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
aa5884c673 |
feat(windows): cross-build Basecamp — plus startup and window fixes for every platform (#323)
* feat(windows): wire Basecamp for the x86_64-windows target
First slice of Stage 4. Basecamp now evaluates for the Windows
pseudo-system up to its first genuinely unported dependency
(logos-design-system); native aarch64-darwin and x86_64-linux
evaluation is unchanged.
Four blockers cleared, found by evaluating and fixing in turn:
1. flake.nix used a hardcoded `systems` list, so packages.x86_64-windows
did not exist. Now routes through logos-nix.lib.forAllTargets, which
keys the cross target as a pseudo-system — that is what lets the 34
`dep.packages.${system}.x` interpolations stay untouched. Bundlers
(nix-bundle-logos-module-install, nix-bundle-dir) are keyed by
buildSystem instead: they RUN on the builder, so taking them from the
target set would hand it a PE it cannot execute.
2. meta.platforms was platforms.unix.
3. krb5 does not cross-evaluate: it carries a host-platform bash, so the
splice fails with "Refusing to evaluate package 'bash-5.3p9'" — an
error naming bash, several levels from the cause. Guarded in both
common.buildInputs and app.nix's qtLibPath.
4. qtwebview propagates qtwebengine — a full Chromium — which fails to
cross-evaluate on cups, three levels from the cause. It is also
entirely unused: no C++ include, no `import QtWebView` in any QML,
and app/CMakeLists.txt's find_package COMPONENTS list omits WebView.
The only surviving mention is a stale line in docs/project.md.
krb5 and qtwebview are GUARDED rather than deleted, so this stays a
Windows-only change. Both look like dead weight on Unix as well, and
dropping them there would shrink every bundle — worth doing separately,
with its own testing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(windows): drop the QML inspector on Windows
logos-qt-mcp is the QML inspector behind the UI test harness. It has no
Windows target and is not needed to run the app -- nix/app.nix already
accepts `logosQtMcp ? null` and gates the inspector on it -- so Windows
builds go without it, and the inspector-dependent outputs are simply
absent from the Windows package set.
With this, logos-basecamp EVALUATES for x86_64-windows end to end; native
aarch64-darwin and x86_64-linux are unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(windows): cross-build LogosBasecamp.exe
Basecamp now produces a real Windows executable. Five blockers, each
found by building and each of a kind already seen in this port.
1. abseil-cpp does not cross-compile (absl/base/internal/
thread_identity.cc includes <pthread.h>, absent on mingw+mcfgthread).
It is dead weight -- "absl" appears nowhere in the sources or CMake,
only in nix files and the README. Guarded here; also removed from
logos-package-manager-ui's metadata, which pulled it in separately.
2. installedModules cut to [] on Windows. The install path runs each
module through nix-bundle-logos-module-install -> nix-bundle-lgx ->
nix-bundle-dir, and nix-bundle-dir is ELF/Mach-O only. This keeps the
bundler chain, lgpm and four module repos off the critical path.
3. The hand-rolled configurePhase never passed $cmakeFlags, so
-DCMAKE_SYSTEM_NAME=Windows was dropped. The symptom was nowhere near
the cause: FindThreads probed for pthreads instead of Win32 threads and
Qt6Config reported "Qt6 could not be found because dependency Threads
could not be found".
4. Qt's host-TOOL packages were unreachable. app.nix builds its own cmake
line and never inherited common.cmakeFlags, so adding the flags there
was not enough -- they had to go on the invocation too. Symptom again
misleading: 'Failed to find required Qt component "RemoteObjects"',
when the TARGET Qt6RemoteObjects is present and it is
Qt6RemoteObjectsTools, the host package, that is missing.
5. Two unsuffixed-name defects, the class that has now appeared eleven
times. app/CMakeLists.txt copied `logos_host` without
CMAKE_EXECUTABLE_SUFFIX, and because it is a POST_BUILD step the
failure read "FAILED: LogosBasecamp.exe" with no undefined references
-- the real message was the "Error copying file" line above it. Then
the installPhase's `if [ -f build/LogosBasecamp ]` had no else-branch,
so the mingw build installed NOTHING, exited 0, and produced an output
whose bin/, lib/, modules/ and plugins/ were all empty. It now probes
both spellings and hard-fails with a directory listing.
Also: ENABLE_QML_INSPECTOR is now gated on logosQtMcp != null rather than
on enableInspector alone -- the inspector cannot be on without it -- and
Windows installs the binary directly instead of behind a /bin/sh wrapper
that could not run there.
NOT YET RUN on Windows; that needs the Qt plugin/QML staging proven in
the design-system milestone.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(windows): main_ui configures and compiles under cross
Two fixes, both of kinds already seen in this port.
The code generator must RUN on the builder, so it now comes from the
BUILD system via a new logosSdkBuild binding. This is a SPLIT, not a
swap: the same cpp-sdk output carries both the generator binary AND the
target headers/CMake package, so logosSdk stays for
-DLOGOS_CPP_SDK_ROOT. Getting it backwards succeeds natively and, under
cross, puts a PE on the builder's PATH -- the symptom was
"logos-cpp-generator: command not found".
main-ui.nix has its own hand-rolled configurePhase which, like app.nix's,
never passed $cmakeFlags -- so -DCMAKE_SYSTEM_NAME=Windows was dropped
and FindThreads probed for pthreads, surfacing three levels away as
"Qt6 could not be found because dependency Threads could not be found".
It now passes $cmakeFlags and Qt's host-TOOL package paths.
main_ui now configures and compiles. It does NOT yet link: the qt-sdk
static library is pulled in twice, giving "multiple definition of
LogosAPI::LogosAPI". That is a link-graph question, not a Windows
spelling issue, and is the next thing to solve.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(windows): name the main_ui plugin .dll, and link it
src/CMakeLists.txt set the plugin SUFFIX with `if(APPLE) ".dylib"
else() ".so"` and no Windows branch, so the cross build emitted
main_ui.so -- a genuine PE carrying a Unix extension. app/window.cpp
looks for ".dll" under Q_OS_WIN, so it would never have been found and
Basecamp would have gone on reporting "No main UI" with nothing
obviously wrong. Windows is now tested before the else-branch, and
nix/main-ui.nix accepts the .dll spelling too.
With the liblogos_core export fix, main_ui links and installs as
main_ui.dll.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix: don't block startup on modules that are not loaded
PackageCoordinator guarded its startup IPC with
`if (!client || !client->isConnected()) return;`, which never fired --
see the logos-protocol fix. Past that dead guard it made ~19 blocking
calls (4 directory setters, 8 package_manager subscriptions, 1
package_downloader) against modules that were absent, each waiting 20 s
twice over. All of it runs on the GUI thread inside the Window
constructor (window.cpp invokes createWidget with Qt::DirectConnection),
and main.cpp calls show() only afterwards -- so the window simply did not
appear for ~7 minutes.
Ask the question in-process instead: logos_core_get_loaded_modules, via
the CoreModuleManager this class already holds. No IPC, no timeout. The
same guard already ships in logos-logoscore-cli's daemon, which skips the
identical setEmbeddedModulesDirectory block and reports that package
commands are unavailable. Basecamp already logged "Failed to load
package_manager module by default" roughly 40 s before the first block --
it knew, and ignored it.
The subscriptions are re-armed on CoreModuleManager::coreModulesChanged,
so a module installed later in the session still gets wired up; without
that this would trade a 7-minute stall for a silently non-functional
Modules view, which is the worse bug. Both subscribe paths are
idempotent, and the "not loaded" warning is emitted once rather than on
every stats-timer tick.
Verified on Windows: the guard now fires, the transport warns before the
doomed wait, and Basecamp's real UI renders -- sidebar, Logos mark and
Welcome page -- instead of a blank window.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix: guard the remaining package_manager startup calls
fetchUiPluginMetadata() and refreshDependencyInfo() checked only
`!m_logosAPI`, so they still blocked ~20 s acquiring a token for an
absent package_manager after the subscribe paths were guarded. That was
the last blocking wait at startup, and the transport's new
"will block up to 20000 ms" warning is what pointed at it.
fetchUiPluginMetadata reuses its existing metadata-unavailable branch
rather than inventing a new one -- it clears m_appsLoading and emits
uiModulesChanged, so the UI settles instead of sitting in a loading state
for the timeout. refreshRepositories needed no change: it guards on
isConnected(), which now tells the truth.
Measured on Windows with no modules installed: "will block up to" 0,
"Timeout waiting for replica" 0, "Requesting object" 0. The UI renders
identically -- sidebar, Logos mark, Welcome page.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(windows): resolve UI plugins' private DLLs from their own directory
The logos-module chokepoint fix covers LogosModule::loadFromPath, which
is every process-isolated module. Basecamp constructs its own
QPluginLoaders and was not covered: PluginLoader's background pre-load
and finishCppPluginLoad for UI plugins, and window.cpp for main_ui.
Each lives in its own directory (plugins/<name>/), so anything vendored
beside it is invisible to Windows' loader, which searches the
EXECUTABLE's directory rather than the importing DLL's. This works today
only because every DLL happens to be staged next to the exe; a plugin
with a private dependency would fail with "The specified module could not
be found", naming the plugin rather than the dependency.
The references are deliberately not released: these plugins stay resident
for the process lifetime (QPluginLoader's destructor does not unload).
No-op off Windows, so no platform guards at the call sites.
Cross-builds and native build both green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* chore(windows): key the install bundlers by target
nix-bundle-logos-module-install now does its own host/target split
internally, so keying the whole bundler by buildSystem is no longer
right -- that is what made it derive the .lgx variant name and library
extension from the build platform.
installedModules stays [] on Windows for now: with the labelling fixed,
lgpm correctly refuses to install a windows-x86_64 package on a Linux
builder, and cross-installation needs a platform override that lgpm does
not have yet.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(windows): ship the pre-installed modules
installedModules is no longer cut on Windows. The cross-install path now
works end to end: capability_module, package_manager and
package_downloader are laid out under modules/, main_ui and
package_manager_ui under plugins/, each a real PE with its own DLL
closure and a variant file reading windows-x86_64-dev.
It took a host/target split at every layer, and each failed somewhere
unrelated to itself: the .lgx bundler keyed by target (it decides the
variant name and library extension), lgpm from the build system (it runs
there), a --platform override for cross-installation, and a Nix-double
to lgpm-variant translation.
Verified on real Windows: capability_module and package_manager load,
spawn their hosts, and answer IPC over named pipes -- the log shows
ModuleProxy rejecting unauthorized calls, which is the token flow
talking, not a Windows failure.
KNOWN GAP, and it fails loudly: package_downloader does not load --
"Cannot load library ... package_downloader_plugin.dll: The specified
module could not be found". Its external library
libpackage_downloader_lib.dll needs libcurl-4.dll, which is in neither
the module directory nor bin/. LogosModule.cmake copies an external
library's DLL but not that DLL's own dependencies, so the closure must be
walked to a fixpoint there too -- the same lesson as the Qt plugin and
bundle closures. Strictly better than shipping no modules at all, and the
failure names the file.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(windows): import the shared runtime instead of linking it
LogosBasecamp.exe and main_ui.dll now compile with LOGOS_SHARED_USE_DLL
and link an emptied logos-qt-sdk archive, so TokenManager, LogosAPI and
LogosAPIClient come from liblogos_core.dll. Before this each image linked
its own copy and therefore its own function-local statics: the host saved
a capability token into its TokenManager, main_ui read its own empty one,
and every cross-module call was refused.
Measured on real Windows across three runs: "rejecting unauthorized call"
29 -> 0, and "No token found" 29 -> 0 replaced by nine "Found token for
module". All modules still load; no missing-symbol or duplicate-symbol
diagnostics.
NOT fixed by this, and not caused by it: the package_manager_ui icon
still does not appear in the sidebar. The pre-change run had the same
zero ui-host spawns. Sidebar entries come from
PackageCoordinator::getInstalledUiPluginsAsync, which reads
package_manager's installed-package registry -- empty in this payload, so
the list is empty regardless of what is on disk in plugins/. That is a
separate unmet precondition.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix: never open a window larger than the screen
setupUi asked for a fixed 1600x900 and nothing anywhere in this repo consulted
the screen -- the <QScreen> include at the top of window.cpp had been unused.
On a display smaller than that the window opens with its right and bottom edges
off-screen, and on Windows it cannot be dragged back into view, so whatever
hangs off is permanently unreachable: in practice the sidebar's bottom-anchored
system buttons and the version label.
Two halves, because one is not enough:
* setupUi clamps the requested size to availableGeometry, so an oversized
window is never mapped in the first place.
* showEvent re-checks once. resize() sets the CLIENT size while
availableGeometry() bounds the FRAME, and the decoration margins are not
knowable until the platform window exists -- measured, the clamped client
filled the work area exactly and the frame still hung 72px below it.
Both are skipped under the offscreen platform (QOffscreenScreen hardcodes its
geometry) so headless UI doctests keep their full-size window and qt-mcp's
scene-position clicks still land.
Three things that look like they could be simpler and cannot, each measured:
* The showEvent check compares SIZES, not rectangles. QRect::contains() is the
obvious formulation and misfires on both platforms: on Windows
frameGeometry() comes from GetWindowRect, which includes DWM's invisible
resize border (~13px/side at 192dpi), so a correctly placed window never
reports as contained and this would run on every launch; on macOS Qt reports
a frame whose title bar sits above availableGeometry's origin, and acting on
that moved the window 66px down at launch on a platform where nothing is
broken.
* The shrink is bounded by the current size. Without boundedTo, a window that
is merely too TALL is also stretched to the full available WIDTH -- observed
on this path: 1600 logical wide silently became 1715.
* The clamp is to availableGeometry exactly, not to a fraction of it. Anything
less is not a no-op on displays that fit the design size, and would undo the
widening documented at the resize() call (1600 was chosen so the Package
Manager's Action and Description columns are not clipped).
Measured on Windows (real hardware, DPI-aware capture, visible frame via
DWMWA_EXTENDED_FRAME_BOUNDS against a 3456x1826 work area):
normal session bottom overflow 45px -> 2px (rounding), width still 1600
small desktop ~550px of width off-screen -> right 14px, bottom 2px
below app minimum bottom 126px -> 33px (Basecamp's own 800x600 floor)
Verified a no-op elsewhere by running the identical logic against Qt 6.11.1 on
macOS: constructor clamp no-op, showEvent reports FITS, size and position
unchanged; and under QT_QPA_PLATFORM=offscreen both paths are skipped.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix: re-fit the window when the screen shrinks under it
The launch clamp (76e0f6d) only runs once. An RDP session with
/dynamic-resolution, or a taskbar appearing, shrinks the desktop under a running
window and nothing reacted -- the window was left overflowing, with the
sidebar's bottom-anchored system buttons under the taskbar and unreachable.
Watches availableGeometryChanged on the screen the window is on, coalesced
through a 150ms settle timer, and re-fits. Measured on real Windows, same
binary pair and run conditions, work area shrinking 1922 -> 1826 physical px:
shipped overflow (3,3) -> (4,111) window did NOT shrink NO-REFIT
re-fit overflow (3,3) -> (3,2) window shrank REFIT-WORKS
An 8-agent design pass was REFUTED 3/3 by its own reviewers and this is the
corrected version, not that design. Three of its defects were real:
* THE RATCHET. It fitted to min(previous, available) on every event -- a
monotone non-increasing map, so the window converges on the smallest work area
seen all session and never grows back. A taskbar that auto-hides and
reappears, or an RDP window dragged smaller and back, would have shrunk
Basecamp permanently. Fixed by bounding against m_desiredSize: the launch
design size, replaced by any resize the USER performs, so the window grows
back but never past what was asked for.
* NO isVisible() GATE. closeEvent hides to the tray on EVERY platform and hide()
does not set Qt::WindowMinimized, so a window-state gate does not cover it.
The fit would have run and latched on a hidden window whose frame geometry is
meaningless.
* A QRect LATCH that could not distinguish "the screen shrank under the window"
from "the window moved to another monitor". Dropped entirely: the
`target == size()` early return is both the idempotence and the
non-recursion, so there is no latch to fall out of sync -- and no need for the
design's re-entrancy flag, which a reviewer showed could never be true on
entry.
Three things kept from that design because they beat the obvious choice:
screenChanged re-points the watch but never fits (Qt migrates windows onto a
1024x768 lock screen on RDP disconnect, and fitting there would shrink with no
way back); the settle timer, because the fit shrinks and chasing a drag's
intermediates would latch its smallest frame; and connections held by handle
with a QPointer<QScreen>, because a session lock adds and then DELETES a screen.
Every guard lives inside fitFrameToAvailableGeometry() rather than in the
callers, so all three entry points are covered by construction -- a reviewer
caught that the design left the scheduler unguarded while changeEvent called it
on every state change.
Two bugs found in this implementation while writing it: QPointer<QScreen> needs
QScreen COMPLETE (QPointer static_asserts QObject derivation), caught by the
cross build; and resizeEvent recorded the MAXIMIZED size as the user's intent,
so a later un-maximize would try to grow the window to full-screen size. Also
replaced `if (!m_screenChangedConnection)` with an explicit QPointer<QWindow>
comparison: Qt documents Connection::operator bool only as "the connect() call
succeeded" and says nothing about sender lifetime, so a stale-but-truthy
connection would have silently stopped re-pointing the watch.
Verified a no-op elsewhere by running the shipped function body against Qt
6.11.1 on macOS: constructor clamp no-op, fit reports FITS, size and position
unchanged; and under QT_QPA_PLATFORM=offscreen both paths are skipped, so
headless UI doctests keep their full-size window.
NOT verified: whether an RDP resolution change arrives as
availableGeometryChanged on the same screen rather than a screen remove/add,
which is deliberately not fitted on. The work-area path is proven; that one is
inferred.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(windows): ship the portable Basecamp with its modules pre-installed
The distributed build cut installedModules to [] on Windows, and skipped
nix-bundle-dir was never the reason -- the comment claimed bundle.sh
would emit an empty payload, which is true, but the conclusion should
have been "don't run bundle.sh on Windows", not "ship no modules".
A PE needs no relocation: its import table carries DLL base names only
and Windows searches the executable's own directory first, so the build
output is already position-independent, and win-dll-link.sh has staged
the closure into bin/. nix-bundle-lgx reached the same conclusion for
module payloads in d539a3f; this applies it to the app bundle.
Without this there is no portable Windows Basecamp with a working
package manager, and therefore no way to install anything from a
catalog: platformVariantsToTry() appends "-dev" to every variant unless
LGPM_PORTABLE_BUILD is set, so the dev build asks for
`windows-x86_64-dev` while a published package is `windows-x86_64` and
every catalog row reports Not Available.
Native builds are unchanged by construction -- bundleFor is dirBundler
off Windows.
* fix(windows): app.nix copied no shared libraries at all
The liblogos shared-library loop globbed *.dylib and *.so only. On Windows
it matched nothing, and because each candidate is guarded by `[ -f "$f" ]`
with a trailing `|| true`, it copied zero files and exited 0.
Thirteen DLLs sit in that same lib/ -- liblogos_core, libpackage_manager_lib,
liblgx, icuuc76, icudt76, libsodium-26 and the mingw runtime -- and
LogosBasecamp.exe imports liblogos_core.dll DIRECTLY. The result is
0xC0000135 (STATUS_DLL_NOT_FOUND) before main(): no Qt error, no stderr, no
output whatsoever. It has only ever run on Windows because those DLLs were
hand-staged into the payload.
Adds *.dll to the glob, sends DLLs to bin/ rather than lib/ (Windows searches
the executable's own directory, has no rpath, and win-dll-link.sh only
processes $out/bin), and makes copying zero libraries a hard error -- this
loop exiting 0 having done nothing is the whole defect.
Also corrects the comment on bundleFor. It claimed skipping nix-bundle-dir on
Windows was right because a PE needs no relocation. That conflates the
bundler's two jobs: relocation (genuinely unnecessary on PE) and Qt plugin /
QML / qt.conf staging (required on every platform, since those DLLs are
LoadLibrary'd and appear in no import table). Skipping it yields a bundle with
no qt.conf and no QPA plugin. The comment now says so and names the real fix.
* fix(windows): stage liblogos' own dependency DLLs, and prove the closure
|
||
|
|
cee212ffa8 |
ci: fix job paths after move under logos folder
Jenkins jobs moved from /logos-basecamp/* to /logos/logos-basecamp/*, which broke everything referencing the old paths: the combined build could no longer resolve child package jobs, getPublishDefault never matched the new JOB_NAME so release builds lost their PUBLISH=true default, and copyArtifactPermission no longer covered the combined job's new location. |
||
|
|
24dfadf3ed |
Test/persistence portability (#317)
* test: persistence portability doctest * fix: harden persistence doctest fixture and paths |
||
|
|
484da77c45 |
Test/missing dependencies (#312)
* test: missing dependencies * fix: add on push trigger * fix: comments timeout * fix: remove on push trigger |
||
|
|
5be4f4f1c8 |
Test/basecamp package lifecycle (#298)
* test: basecamp package lifecycle * fix: accommodate changes from master * fix: comment - Basecamp fetches its default repository catalog in the background Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * fix: comment - hermetic — no catalog packages are downloaded or installed Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> |
||
|
|
6bb899bb9a |
Test/basecamp package lifecycle (#298)
* test: basecamp package lifecycle * fix: accommodate changes from master * fix: comment - Basecamp fetches its default repository catalog in the background Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * fix: comment - hermetic — no catalog packages are downloaded or installed Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>pre-release-aa23776-688 pre-release-87fb0d6-683 |
||
|
|
25300f0e90 | fix: fix the show/hide behavior for basecamp after its minimized | ||
|
|
67f3244e96 | chore: make texts in basecamp selectable pre-release-c7505d9-667 | ||
|
|
631494a32f | fix: fix the breaking tests | ||
|
|
efb48100b9 | fix: fix review comments from copilot | ||
|
|
4ed06ed95e | feat: update modules settings and move them into their own App Inspector and Module Inspector parts | ||
|
|
a55a029f16 | fix: fix for appmanager shows an uninstalled module from pmui | ||
|
|
2757b5fee5 | chore: remove install lgx from modules settings | ||
|
|
f450ac26b4 | chore: use LogosScrollView instead of raw ScrollView | ||
|
|
a34216abf0 | fix: fix the scrollbar position to right of the container with 4px gap and simplify AppManagerView.qml | ||
|
|
3a73d33824 | chore: revert x on linux closes app | ||
|
|
601b173899 | fix: pin-release-test-modules | ||
|
|
e39cd4060c |
chore: fix stale text=auto comment — rules are explicit text on .nix/.sh (per @jzaki review)
pre-release-d41a72b-629
|
||
|
|
3e3dc894b9 |
Apply suggestions from code review
Co-authored-by: James Zaki <james.zaki@proton.me> |
||
|
|
6ce4804e0a |
build: add .gitattributes enforcing LF to fix CRLF-on-Windows/WSL build breaks
Windows/WSL checkouts (default core.autocrlf=true) rewrite text files to CRLF, and a CRLF nix/*.nix breaks the configurePhase shell scripts — the node build then fails on WSL with no obvious cause. A root .gitattributes with 'text=auto eol=lf' forces LF on checkout for all text files while leaving binaries untouched, so clones build identically on every platform. Fixes logos-co/logos-basecamp#288 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
e2d60cf019 |
chore: add coverage report to flake and CI (#293)
Squashed: - chore: add coverage report to flake and CI - fix: add report upload Adds a .#coverage nix output that runs unit tests under gcov, drops the coverage.txt summary into GITHUB_STEP_SUMMARY, and uploads the full result/ tree as a workflow artifact. Non-blocking (continue-on-error) for now. |