Open
Conversation
New sub-package that bundles lively.next as a standalone desktop
application via NW.js, so end users can launch a running lively session
without cloning the repo, installing deps, or managing a server.
Architecture:
- package.json doubles as NW.js manifest: `main` points at a loading
screen (boot.html), `node-main` points at start-server.cjs which
runs in Node context before any window opens
- node-main spawns lively.server as a managed child process with
--experimental-loader for flatn's ESM resolver (ESM loader hooks
crash NW.js's Blink renderer, so in-process loading isn't viable)
- watchdog.cjs preload polls the parent PID every second; the server
dies whether NW.js exits cleanly, crashes, or is force-killed — no
orphans
- server-config.js excludes puppeteer-dependent plugins (test-runner,
lively.headless) since NW.js provides its own Chromium
- setup.sh downloads the NW.js SDK binary directly (flatn installs
the `nw` npm package but its postinstall can't decompress through
flatn's flat layout — yauzl-promise → @node-rs/crc32 native addon)
- start.sh sources lively-next-env.sh, clears NODE_OPTIONS (ESM
loaders via NODE_OPTIONS also crash Blink), and launches NW.js
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
So flatn picks it up as a dev package and its dependencies (nw SDK) get installed into lively.next-node_modules during install.sh. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Registering lively.app in packages-config.json makes flatn install the `nw` package source, but its postinstall fails to decompress the binary (yauzl-promise → @node-rs/crc32 native addon can't resolve through flatn's flat layout). Call lively.app/setup.sh to pull the SDK tarball directly after the freezer build. Opt out with `./install.sh --no-desktop`. Skipped automatically if lively.app/ is absent, and idempotent — setup.sh short-circuits when the binary is already in place. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Google Closure was the minifier used by the legacy Terser branch of
lively.freezer/src/bundler.js, gated by useTerser which defaults to
false. Minification now flows through SWC in-process (bundler-swc.js)
and the Closure path is only reachable from the freezer UI's explicit
"Terser + Babel" selection.
The two google-closure-compiler-{linux,osx} packages weigh ~571MB — a
big win for any fresh install and even more so for distribution bundles.
If someone opts into useTerser=true after this change, compileOnServer
will fail fast with MODULE_NOT_FOUND instead of silently succeeding
with whatever Closure happens to do. Cleaning up the legacy code path
itself (helpers.js compileOnServer, ui.cp.js compiler selector) is
left for a follow-up.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Teach start-server.cjs to work in both modes:
- Dev: lively.app/ inside the monorepo (rootDir = <root>)
- Bundled: standalone distribution where this script lives at
<bundle>/desktop/ and the monorepo content is at <bundle>/app/
Detection compares __dirname vs rootDir: in dev __dirname is under
rootDir (lively.app/desktop/); in bundled mode it isn't
(<bundle>/desktop/ vs <bundle>/app/).
Bundled mode additionally:
- Logs to ~/.local/share/lively.next/boot.log (user-writable,
not alongside read-only source)
- Sets FLATN_* env vars itself since there's no launcher script
- Uses <bundle>/node/bin/node (bundled) instead of PATH
- Creates the runtime dirs (esm_cache, snapshots, local_projects,
custom-npm-modules) that the server's library-snapshot step tars
Gitignore dist/ at both the repo root and inside lively.app/ so the
build artifact tree doesn't show up in git status.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Produces dist/lively.next-<platform>-<arch>/ — a self-contained
distribution that runs by double-clicking its launcher on any machine,
no monorepo, no nvm, no PATH setup. Verified end-to-end: launched with
bare env (env -i HOME=... PATH=/usr/bin:/bin DISPLAY=...), the server
boots in-bundle and serves lively.next on a random localhost port.
Pieces:
- NW.js Normal flavor binary at bundle root (launcher)
- desktop/ with start-server.cjs + watchdog.cjs + server-config.js
- app/ with the full lively monorepo content
- node/bin/node — standalone Node.js (NW.js's embedded node can't
be invoked as a plain node subprocess)
- launch.sh (cross-platform shim) + lively-next.desktop (Linux)
Size trimming versus a naive rsync:
- Anchored root-level excludes (/.git/ not .git/) so legit nested
dirs like systemjs/0.21.6/dist/ survive
- Cross-platform native bindings filtered per target
(@swc/core-darwin-* stripped from a Linux bundle, etc.)
- Chromium locales pruned to en-US only by default; override with
LOCALES="en-US fr de" or LOCALES=all
- Tests, examples, docs, source maps stripped from deps
- lively.headless/chrome-data-dir stripped (kept sources — tar step
needs the package directory to exist)
- swc-plugin Rust build artifacts excluded (the .wasm is prebuilt)
- Puppeteer excluded — desktop config already omits the plugins
that need it
Result for Linux x64: ~1.7–1.9 GB uncompressed. Pack to tar.gz with
PACK=1 for distribution.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Runs lively.app/scripts/build.sh on ubuntu-latest at 04:00 UTC daily
and uploads dist/lively.next-linux-x64.tar.gz as a GitHub artifact
downloadable from the Actions run page. Retention 30 days.
Mirrors the install recipe from check-pr.yml / daily-ci-checks.yml:
- Node 24, bun, Rust (wasm32-wasip1) toolchains
- lively.next-node_modules/ cache keyed on package.json hashes
- ./install.sh with retry on transient failures
- --no-desktop skip on install since build.sh fetches its own
NW.js (Normal flavor) directly
Also triggerable on-demand via workflow_dispatch for pre-release
smoke tests.
Linux-only for now; matrix expansion to macOS (osx-arm64) and
Windows (win-x64) is the next step once build.sh is verified on
those platforms.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the Linux-only bash build.sh with a Node.js-based build.mjs
that runs identically on Linux, macOS, and Windows. The nightly
workflow now builds a bundle per OS into separate GitHub artifacts:
lively.next-linux-x64.tar.gz, lively.next-osx-arm64.tar.gz,
lively.next-win-x64.zip.
Why Node instead of bash:
- rsync isn't on Windows; reimplementing with fs.cp or a filter
function works on all three
- Git Bash on Windows CI is fragile for anything beyond trivial
- Modern tar ships with Linux/macOS and Windows 10+ (bsdtar) so
extraction via child_process.execFileSync('tar', ...) works
across the board
Per-platform finalizers:
- Linux: writes launch.sh + lively-next.desktop (double-click target
in most file managers)
- macOS: renames nwjs.app → lively.next.app so the whole bundle is a
native .app; patches Info.plist CFBundleIdentifier/Name/DisplayName
so the OS sees it as our app (not a generic NW.js)
- Windows: renames nw.exe → lively.next.exe + writes launch.bat
Cross-platform extras:
- Auto-detects target platform/arch with --platform=X --arch=Y
override (lets you cross-target in local testing even if full
cross-compile only works from matching platforms)
- Cross-platform native binding stripping expanded — @swc/core-*
and @rollup/rollup-* are kept only for the target's
platform-arch tag, rest filtered
- Downloads NW.js + standalone Node.js into dist/.cache/ with a
.extracted flag file so rerunning skips the fetch/extract
- Pure-Node recursive-copy with a tiny rsync-style pattern matcher
(anchored / vs anywhere, ** glob, *, ?)
CI matrix (.github/workflows/build-desktop-app.yml):
- ubuntu-latest → lively.next-linux-x64.tar.gz
- macos-latest → lively.next-osx-arm64.tar.gz
- windows-latest → lively.next-win-x64.zip
fail-fast: false so one broken OS doesn't drop the others.
shell: bash default so install.sh keeps running via Git Bash on
Windows runners.
Verified on Linux end-to-end (server boots, dashboard serves at
127.0.0.1). macOS / Windows first-time builds will surface from the
first nightly run; finalizers are straightforward renames + plist
tweaks and match the layouts nw-builder and other NW.js packagers use.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
workflow_dispatch only works once the workflow file is on main, and this repo's default branch doesn't have it yet. Add a pull_request trigger scoped to paths that actually affect the bundle (lively.app/**, install.sh, flatn/**, lively.installer/packages-config.json, and the workflow file itself) so reviewers can download artifacts from the PR Checks tab before merge. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Native Windows isn't a supported lively.next platform — maintainers and users run lively via WSL on Windows, which means the Linux bundle is the Windows distribution too. The windows-latest runner's install.sh failure (Git Bash passes MSYS paths like /d/a/... to Node's --experimental-loader, which Windows Node interprets as D:\d\a\... i.e. a drive-letter-prefixed literal) isn't worth chasing for a platform the project doesn't support. build.mjs keeps its Windows branches for anyone who wants to cross-target a Windows bundle from a Linux or macOS host. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
End users on Windows need a double-clickable .exe, not "use WSL first." But a windows-latest CI runner can't run install.sh cleanly: Git Bash hands MSYS paths (/d/a/...) to Node's --experimental-loader, which Windows Node interprets as drive-letter-prefixed literals (D:\d\a\...). That's a path-mangling fight we don't need to have. Instead, cross-compile: build.mjs is a pure Node script that downloads the target-platform NW.js and Node.js binaries and stages them. None of the copying or renaming cares what host OS it's running on — only the platform finalizers differ, and those are just fs.rename + string replacement. Matrix change: both linux-x64 and win-x64 bundles now build on ubuntu-latest via --platform=X --arch=Y; macOS stays on macos-latest (native install.sh works fine there). Added build.mjs fallback for zip packing on Linux hosts (`zip -r` since GNU tar doesn't do `-a`). Verified locally: cross-compiled win-x64 bundle, 1.6 GB unpacked, 692 MB .zip, lively.next.exe is a genuine PE32+ x86-64 binary, node.exe and launch.bat in place. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
macOS runners ship Python 3.14 now, which removed the distutils stdlib module. node-gyp@9 (dragged in by leveldown's native addon build via flatn's bun-install step) still does `from distutils.version import StrictVersion`, so every dep install blows up with ModuleNotFoundError: No module named 'distutils'. Pin Python 3.11 via actions/setup-python — it still has distutils, so bun install / node-gyp succeed. Applies to both runners so Ubuntu stays green when it eventually bumps past 3.11 as its default. Upstream fix is to bump node-gyp to 10.x in the relevant package trees (leveldown, whatever else); out of scope for this PR. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Before, the macOS bundle was a folder containing lively.next.app
alongside boot.html, desktop/, app/, node/, and NW.js tarball leftovers
(credits.html etc.). Users who downloaded the archive saw junk next to
"the app".
Move everything into lively.next.app/Contents/Resources/app.nw/ (the
NW.js-convention location for an app payload) and drop the stray
files at the bundle root. Result: the bundle IS just the .app. One
double-click target, nothing else to see.
Layout now:
lively.next.app/
Contents/
MacOS/nwjs
Resources/
Info.plist ← patched CFBundle* metadata
app.nw/ ← our payload
package.json ← NW.js manifest
boot.html
desktop/{start-server,watchdog,server-config}
app/ ← monorepo content
node/bin/node ← standalone Node for the server subprocess
Relative paths in start-server.cjs still work: __dirname resolves to
app.nw/desktop/, so __dirname/../app → app.nw/app (still finds
lively.installer/packages-config.json and wins the findRootDir loop).
Note on "damaged" Gatekeeper error: the .app is unsigned and therefore
quarantined on download. Users strip it with `xattr -cr lively.next.app`
or via Privacy & Security → "Open anyway". Proper code-signing +
notarization is follow-up work (requires Apple Developer account).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Until code-signing + notarization lands (tracked in #1795), end users downloading the unsigned .app hit "lively.next is damaged and can't be opened" — standard Gatekeeper behavior for quarantined downloads, but the error message makes it look like the archive is broken. Drop a tiny README next to the .app inside the bundle with: - why it happens (unsigned + quarantined, not damaged) - the xattr -cr one-liner fix - the GUI alternative (Privacy & Security → Open anyway) - a note that this goes away once signing is set up So the download is lively.next.app + README-macOS.txt — still one obvious double-click target, plus a text file users read exactly once. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
install.sh skips the landing-page freezer build under CI=true (the
other CI workflows — check-pr, daily-ci-checks — don't need it and
the build is ~2 min). But the desktop bundle's world-loading server
plugin opens landing-page/index.html on the first GET / and crashes
ENOENT if it's missing:
Error: ENOENT: no such file or directory, open
'.../app.nw/app/lively.freezer/landing-page/index.html'
Node.js v25.6.1
ERROR: Server crashed (exit code 1)
Add an explicit `npm --prefix lively.freezer run build-landing-page`
step in the desktop-build workflow so the artifact is present before
build.mjs rsyncs the monorepo into the bundle.
Local builds weren't hitting this because install.sh's `if [ -z $CI ]`
branch runs `build-unified` (both landing-page and loading-screen) on
dev machines.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ry snapshot
Two independent wins for cold-start time on the bundled desktop app:
1. NODE_COMPILE_CACHE for the server subprocess
Node 22+ caches V8 bytecode between runs. start-server.cjs now
points the subprocess at ~/.cache/lively.next/v8 (Linux/Win) or
~/Library/Caches/lively.next/v8 (macOS). First launch is unchanged;
subsequent launches skip most of the JS parse + compile work
(typically 20-40% faster on subsequent launches).
2. Ship a pre-built library snapshot in the bundle
LivelyDAVPlugin.compressLibraryCode normally tars + gzips ~28
lively package dirs on every server startup (3-5s of IO +
compression per launch). This is the blob served at
/compressed-sources to browser clients for fast module lookup.
New lively.server/scripts/build-library-snapshot.cjs produces the
exact same tar.gz standalone (self-sufficient: sets FLATN env
itself). The CI workflow runs it right before build.mjs, and the
resulting lively.server/.library-snapshot.tar.gz gets rsynced into
the bundle's app/lively.server/ dir.
dav.js checks process.env.LIVELY_PREBUILT_LIBRARY_SNAPSHOT at
startup: if set and the file exists, read it into memStore and
skip the tar+gzip step. start-server.cjs sets the env var pointing
at the bundled file whenever bundled=true. Dev mode path is
unchanged (env var unset → original regeneration).
Log confirms it works — 4ms to load from disk vs. the 3-5s
regeneration it replaces:
[lively.server] loading pre-built library snapshot: .../app.nw/
app/lively.server/.library-snapshot.tar.gz
[lively.server] pre-built library snapshot loaded
Staleness note: the snapshot embeds source paths / contents at build
time. If a user somehow modifies package sources inside the .app
after extraction, the snapshot will serve out-of-date code to the
browser (the file-hash map, which IS recomputed fresh each startup,
would detect this but the snapshot wouldn't update). Acceptable
trade-off for a distribution bundle — nobody's going to hand-edit
files inside Contents/Resources/app.nw/.
Out of scope: the bigger startup cost (~15s in steps 1+2 — systemjs +
lively.modules + config + plugin imports) needs pre-bundling
lively.server to a single CJS file. Separate follow-up.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Normal flavor has no Chromium DevTools, which makes diagnosing client-side issues in the bundle (blank screen, failed asset loads, JS errors) a back-and-forth guessing game. SDK flavor ships full DevTools — users hit Cmd+Opt+I / right-click → Inspect to see the console + network. Costs ~20% more bundle size (roughly +100-150MB uncompressed). Switch back to FLAVOR=normal once the desktop app is stable and end-users don't need inline debugging. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
On macOS the NW.js window stayed blank after the server finished booting. Tested the same URL directly in Safari / Chrome — works fine. So the server is healthy; the problem is specifically the NW.js window's navigation. Setting win.window.location.href from node-main context crosses the node↔DOM boundary and behaves inconsistently across NW.js versions (reliable on Linux in our testing, blank on macOS). The canonical pattern is to emit an event to the page and let its own script assign location.href from the DOM context. - boot.html subscribes to a new "lively-boot-navigate" event and does window.location.href = url itself. - start-server.cjs emits that event with the server URL instead of poking location directly. - While we're here, target /dashboard/ explicitly so the window doesn't have to follow a 301/302 redirect from / — one fewer moving part in the cross-context hop. Can't reproduce the bug locally (Linux navigates fine with the old code too), so this is verified by shape rather than repro. The DOM- side pattern is what every NW.js tutorial recommends for this flow; worst case it's a correctness cleanup with no regression. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The previous "nw.Window.get().emit()" attempt failed with TypeError on macOS because nw.Window.get() in node-main returns a handle that does NOT expose EventEmitter methods (emit is undefined). All my earlier emitStatus / emitError calls had been silently swallowed in try/catch for the same reason — that's why boot.html only ever showed the hardcoded "Starting server..." and never live status updates. Right pattern: expose helper functions on the DOM window in boot.html (window.livelyBoot.status / .error / .navigate) and call them from node-main via win.window.livelyBoot.foo(...). That's a regular method call on a regular object; works across contexts the way a bare location.href setter doesn't reliably. Navigation still targets /dashboard/ directly (avoid the / redirect). User reported blank screen on macOS with win.window.location.href assignment; that's what this approach replaces. Falls back to the direct assignment if boot.html's script somehow hasn't run yet — shouldn't happen in practice, but logged so we'd know. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
them breaks lively's client bundle
The dashboard client, when given access to Node's require() via
node-remote, takes lively's "I'm in a Node environment" code path
and calls require('socket.io-client'). Node's standard CJS loader
can't find it because flatn's flat node_modules layout doesn't match
what require() expects, and the page fails to bootstrap — blank screen
inside NW.js, despite rendering cleanly in a regular Chrome/Safari tab
from the same server.
Removing node-remote means pages loaded from the localhost server run
as pure browser pages (no require, no process, no Buffer). lively then
takes its browser-only code path (fetches socket.io-client over HTTP
via SystemJS) — same as any user hitting the server with their normal
browser.
boot.html still works because it's loaded as `main` (local file://),
not via node-remote, and NW.js always exposes `nw.Window` / `nw.App`
to pages it loads directly regardless of node-remote. So the
livelyBoot helper-call mechanism for status + navigation is unchanged.
Re-enable node-remote later when the lively.context / node:inspector
integration needs it, but with a scoped URL pattern (e.g.
http://127.0.0.1:*/lively-context/*) that only targets debugger
endpoints — not the whole dashboard.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
F12 / Cmd+Opt+I / right-click → Inspect aren't reliably bound in NW.js on macOS even in SDK flavor (can vary by build and by OS keyboard configuration). Enabling the Chrome DevTools Protocol endpoint at localhost:9222 gives a dependable fallback: open chrome://inspect in any regular Chrome while the app is running, find the NW.js window, click Inspect → full DevTools. Works in both Normal and SDK NW.js flavors, so it's a no-cost fallback independent of which flavor CI ships. Single-user desktop app so port 9222 collision isn't a concern. Leaving FLAVOR=sdk for now so in-app DevTools ALSO works when it feels like working. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two UX fixes:
1. boot.html re-styled to match the dashboard aesthetic
- Golden→orange linear-gradient background (same palette as the
landing page: #F1C40F → #F39C12)
- Two translucent triangle accents echoing the dashboard's
triangular backdrop
- IBM Plex Sans with system-font fallback
- Inlined lively-next logo (from lively.morphic/lively-next-logo.svg),
recolored white so it reads against the gradient
- Replaced the spinning circle with a slim progress bar that slides
left-to-right — looks calmer and more purposeful for a multi-second
boot than a spinner
- Error state renders as a red inline badge instead of overwriting
the status text in red (more legible)
2. nw.Menu with Dashboard / Reload / DevTools shortcuts
Once the window's open and navigated, the user has no way back from
a project view to the dashboard. Install a menubar (system menu bar
on macOS, window menu elsewhere) with:
- Dashboard (Cmd+D / Ctrl+D) — navigates location.href to
/dashboard/
- Reload (Cmd+R / Ctrl+R)
- Toggle DevTools (Cmd+Opt+I / Ctrl+Alt+I) — complements the
--remote-debugging-port chrome flag, which still works as a
fallback when DevTools isn't bound via the in-window shortcut
createMacBuiltin adds the standard app/Edit/Window menus on macOS
so Quit/Hide/etc. work as users expect.
menu setup is wrapped in try/catch + non-fatal log so a bad NW.js
version or flavor doesn't block the rest of the boot.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three polish fixes rolled into one: 1. Triangles now match the dashboard exactly Read the three Polygon morph coordinates out of lively.freezer/src/landing-page.cp.js (vertices + positions) and replicate them as SVG <polygon> elements in boot.html with preserveAspectRatio=slice. Single fill color (rgba(230,126,34,0.6)) matching the landing-page's 60%-opacity orange. Drops the two hand-drawn CSS-border triangles that were roughly in the right direction but didn't line up with the dashboard aesthetic. 2. Menu items now actually navigate The previous approach set nw.Window.get().window.location.href from the menu's click handler — same node→DOM flaky-assignment pattern that caused the initial blank-screen on macOS. Fix: every page loaded in the NW.js window now gets desktop/inject.js injected via the manifest's inject_js_end, which exposes window.livelyNav(url). Menu click handlers call THAT — plain function call across the boundary, works consistently. Bonus: inject.js also binds Cmd+Shift+D (Ctrl+Shift+D on Linux) as a keyboard shortcut for "Go to Dashboard" that works from any page, including the ones lively serves. Shortcut was changed from plain Cmd+D because Chromium reserves Cmd+D for bookmarks. Also renamed the custom submenu from "lively.next" (duplicated the macOS app-menu label) to "Go", which is clearer. Menu shortcuts also migrated: Dashboard is Cmd+Shift+D (was Cmd+D), Toggle DevTools is Cmd+Opt+I. Reload entry dropped since Chromium binds Cmd+R natively anyway. 3. Proper app icon New lively.app/assets/icon.svg source: white rounded-square with the orange "engine" glyph from the lively-next logo centered, 22% corner radius to match macOS squircle aesthetic. lively.app/scripts/generate-icons.sh rasterizes it to PNGs at 7 sizes and packs them into .icns (macOS via iconutil or Linux via png2icns), .ico (ImageMagick), and a 512px PNG (Linux). CI installs the required tools (librsvg2-bin, libicns-utils, imagemagick on Ubuntu; librsvg + imagemagick from brew on macOS) then runs the generator before bundling. finalizeMacOS places icon.icns at Contents/Resources/app.icns, strips the stock nw.icns, and patches Info.plist CFBundleIconFile to "app". finalizeLinux copies icon.png to the bundle root for the .desktop file's Icon= reference. Windows .exe icon embedding (needs rcedit) left for follow-up. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
libicns-utils / icnsutils was removed from Ubuntu 24.04 (noble); the apt install in the workflow fails with "Unable to locate package libicns-utils". Switch the Linux runner's .icns generation from png2icns to a tiny pure-Node script (scripts/build-icns.mjs) that constructs the .icns file format manually: 4-byte magic + total size, then a sequence of <type><size><png-bytes> chunks. No native deps, no system packages to install. macOS runner still prefers native iconutil — it handles Retina @2x filenames correctly and is 10 lines less script. Both paths produce bit-identical-enough .icns files (file(1) happily identifies the Node-built one as "Mac OS X icon" with correct magic and type codes). Leaves rsvg-convert (librsvg2-bin / brew librsvg) and ImageMagick as the only apt/brew dependencies, both of which are still packaged and maintained in 24.04. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
build.mjs's desktop-file copy list was ['start-server.cjs', 'watchdog.cjs', 'server-config.js'] — missing inject.js. So every bundle we've produced has had the manifest reference "inject_js_end": "desktop/inject.js" but no such file, which NW.js silently no-ops on. Net result: window.livelyNav was never defined in pages, menu click handlers fell through to the window.location.href = url fallback, and that's the flaky node→DOM assignment path that doesn't navigate in NW.js on macOS — which is why menu items appeared to do nothing. Add inject.js to the list. Verified locally: the file lands at <bundle>/desktop/inject.js and NW.js will pick it up via the inject_js_end manifest field. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
menu items still appear inert
Every theory about why the menu doesn't work is currently untestable
from the boot log because we don't know which layer is failing:
- Is the click handler firing at all?
- Is inject.js actually being injected by NW.js?
- Is window.livelyNav defined on the page at click time?
- Does navTo throw?
Add log() calls at every step:
- inject.js sets window.__LIVELY_INJECT_LOADED__ to a timestamp
as a marker that node can read, and console.log()s when it runs
- Menu click handlers log on entry
- navTo logs window presence, inject marker, livelyNav type,
current URL, and whether the livelyNav call returned
- toggleDevTools logs same
After user clicks a menu item, the boot log should show one of:
A. "Dashboard item clicked" missing entirely → click callback
never fires (NW.js bug? menu structure issue?)
B. "inject.js loaded: no" → inject_js_end isn't actually injecting
C. "typeof livelyNav: undefined" → inject ran but helper wasn't set
D. livelyNav called but page didn't navigate → something deeper
Removes the guesswork.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Recent debugging has been hampered by not knowing which CI bundle the
user is actually running — an observed boot.log could match pre- or
post-fix code, we have to guess.
Write a build-info.json into the bundle during CI (LIVELY_APP_BUILD_SHA
comes from github.sha in the workflow) and have node-main read it on
startup, logging `build: {"sha":"...","builtAt":"..."}`. Next bug report
has the commit identity right at the top of boot.log.
Locally-built bundles get sha="(local)" so the field is always present.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Research (nwjs/nw.js wiki + issues #4313 #5150 #8240 and the Transfer-
objects-between-window-and-node guide) points at the right pattern for
in-app navigation from a native NW.js menu:
1. Create the menubar only AFTER `win.on('loaded', ...)` fires. Wiring
nw.Menu up earlier from node-main is the flaky case — clicks often
silently don't fire. The recent versions' fix lands once the
window's own context is attached.
2. Click handlers DO NOT set location.href themselves. That assignment
across the node↔DOM boundary has been broken since NW.js 0.12.x
(see #4313). Post a structured message to the DOM instead —
win.window.postMessage({type:'lively-nav', url}, '*').
3. DOM-side (desktop/inject.js, injected via inject_js_end) listens
for 'message' events and does the location.href assignment in its
own context. Same listener also binds Cmd/Ctrl+Shift+D as a
keyboard fallback independent of the menu.
Revert the inject.js floating-button approach — that was a workaround
for the wrong symptom. Do it the documented way now.
Remaining click handlers just log on failure rather than explode; the
menu creation and message passing should both work on 0.110.1 given
the 'loaded' timing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
lively.app/sub-package that bundles lively.next as a standalone NW.js desktop app — users download one archive, double-click, lively runs. No clone, no terminal, no PATH setup.lively.app/scripts/build.mjs)..github/workflows/build-desktop-app.yml) produces downloadable artifacts on the Actions run page.How it works
module.register,registerHooks,NODE_OPTIONS --experimental-loader) all crash NW.js's Blink renderer, sostart-server.cjs(node-main) spawnslively.server/bin/start-server.jsas a managed subprocess with flatn's--experimental-loader. A parent-PID watchdog (watchdog.cjs) preloaded into the subprocess ensures it dies when NW.js dies — including force-kill / crash — no orphans.start-server.cjsauto-detects which mode it's in (based on whether__dirnameis insiderootDir) and logs tolively.app/boot.logvs~/.local/share/lively.next/boot.logaccordingly, sets up FLATN env itself, uses the bundled node binary when present.setup.shfetches the NW.js SDK binary directly (thenwnpm package's postinstall can't decompress through flatn's flat layout). Opt out with./install.sh --no-desktop.Bundle contents (1.9 GB uncompressed Linux)
nw/nwjs.app/nw.exe, lib/, *.pak, ...)app/with the whole monorepo minus build artifacts, tests, docs, wrong-platform native bindings, puppeteer, and runtime cachesdesktop/withstart-server.cjs,watchdog.cjs,server-config.js.desktop+launch.sh(Linux), renamed.app(macOS), renamed.exe+.bat(Windows)Test plan
./start-server.shnode lively.app/scripts/build.mjs→ producesdist/lively.next-linux-x64/env -i HOME=$HOME PATH=/usr/bin:/bin DISPLAY=:1 bash dist/lively.next-linux-x64/launch.sh→ server boots in-bundle on 127.0.0.1, full lively.next dashboard renders (verified with screenshot)./install.sh --no-desktopskips the NW.js SDK fetchbuild-desktop-app.ymlon this branch before merge to smoke-test all three platformsOpen follow-ups (not in this PR)
compileOnServer/ Terser+Closure code path inlively.freezer/src/util/helpers.js+ the "Terser + Babel" option inui.cp.js— dead code after the dep removal, clean up passlively.contextCDP hybrid (the original motivation for NW.js) — this PR is just the shell; the Smalltalk-style debugger integration using node:inspector is tracked separately inplan_nwjs_app.mdlively.next-node_modules/to further shrink the bundle🤖 Generated with Claude Code