diff --git a/README.md b/README.md index f33c898..0597028 100644 --- a/README.md +++ b/README.md @@ -259,13 +259,12 @@ or the **[GitHub releases](https://github.com/dantheuber/clipless/releases)**. ### 🐧 Installing on Linux -Releases ship three Linux formats — pick whichever suits your setup: +Releases ship two Linux formats — pick whichever suits your setup: -| Format | Install | Auto-update | -| ----------- | -------------------------------------------------------- | ------------------------------------- | -| `.deb` | `sudo apt install ./clipless__amd64.deb` | ✅ in-app (prompts for your password) | -| `.AppImage` | `chmod +x clipless-.AppImage` and run it | ✅ in-app | -| `.snap` | `sudo snap install --dangerous clipless_*.snap` | ❌ (sideloaded snaps don't refresh) | +| Format | Install | Auto-update | +| ----------- | ------------------------------------------------- | ------------------------------------- | +| `.deb` | `sudo apt install ./clipless__amd64.deb` | ✅ in-app (prompts for your password) | +| `.AppImage` | `chmod +x clipless-.AppImage` and run it | ✅ in-app | **Debian / Ubuntu (`.deb`)** — the recommended option. Installing with `apt` resolves dependencies and registers the desktop entry and icon, so Clipless appears in your app grid and taskbar: diff --git a/electron-builder.yml b/electron-builder.yml index 17b736f..dfcef45 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -65,9 +65,14 @@ dmg: format: UDZO internetEnabled: false linux: + # snap is deliberately not built. electron-builder's snap template pins + # base: core20 with the gnome-3-28-1804 platform, whose Mesa is 20.0.8 (2020). + # On current GPUs every DRI driver in it fails to load — including swrast — so + # the app segfaults during GPU init and no runtime flag avoids it. Reviving the + # snap means base: core24 with gnome-46-2404 and the mesa-2404 content snap, + # which builds through the real snapcraft toolchain rather than the template. target: - AppImage - - snap - deb maintainer: electronjs.org category: Utility @@ -78,6 +83,19 @@ linux: icon: build/icons appImage: artifactName: ${name}-${version}.${ext} +snap: + # Inert while snap is off the target list, but keep it: electron-builder + # defaults snap artifacts to the Snap Store, which needs snapcraft (and store + # credentials) on the build machine. CI has neither, and the failed store + # upload aborted the whole linux build *before* the updater metadata was + # written — which is why v1.8.8 shipped an AppImage and a deb but no + # latest-linux.yml. Re-adding the snap target without this would silently + # break Linux auto-update again. + publish: + provider: github + owner: dantheuber + repo: clipless + releaseType: draft npmRebuild: false publish: provider: github diff --git a/okf-bundle/gotchas/dev-builds-defer-to-an-installed-instance.md b/okf-bundle/gotchas/dev-builds-defer-to-an-installed-instance.md new file mode 100644 index 0000000..8540f55 --- /dev/null +++ b/okf-bundle/gotchas/dev-builds-defer-to-an-installed-instance.md @@ -0,0 +1,48 @@ +--- +type: gotcha +title: Dev Builds Silently Defer to an Installed Instance +tags: + - development + - electron + - linux +timestamp: 2026-08-17T04:02:57.964Z +status: stable +--- + +`npm run dev` uses the same `userData` directory as an installed Clipless +(`~/.config/clipless` on Linux). The app takes a single-instance lock in +`src/main/index.ts`, so when an installed copy is already running the dev +instance **fails the lock and calls `app.quit()` immediately** — and the +*installed* process receives `second-instance` and shows its own window +(`src/main/app/index.ts`). + +The failure mode is that this looks like success: a Clipless window pops to the +front the moment dev starts. Nothing indicates the window belongs to a different +build. Symptoms: + +- Code changes never appear, no matter how many times you rebuild. +- Bugs you already fixed keep reproducing "in dev". +- `electron-vite dev` prints `start electron app...` and then exits 0 within seconds. + +Clipless minimizes to tray, so closing its window does not quit it — an installed +copy can hold the lock for weeks without being noticed. + +Diagnosis: `ps -o pid,lstart,cmd -C clipless`. If a process predates your last +build, that is what you are looking at. + +Two ways out: + +- `pkill -f '/opt/Clipless/clipless'` before starting dev. Clips are persisted + under `~/.config/clipless/clipless-data` and survive. +- Give dev its own profile: `npx electron-vite dev -- --user-data-dir=/tmp/clipless-dev`. + This also keeps dev runs from mutating real clip history — related: + [E2E Tests Touch the Real System Clipboard](/gotchas/e2e-tests-touch-system-clipboard.md). + +The same shadowing hits packaged installs: `apt install` of a new .deb does not +restart a running app, and replacing `app.asar` under a live process invalidates +the archive offsets it cached — reads of `settings.html` then return bytes from +the middle of another packed file, and the settings window renders that JS as +plain text. It looks like a renderer bug; it is a stale process. + +See also [Non-Blocking Startup](/decisions/non-blocking-startup.md) for the +single-instance lock in its intended role. diff --git a/okf-bundle/gotchas/index.md b/okf-bundle/gotchas/index.md index 3ca83e2..557f1e4 100644 --- a/okf-bundle/gotchas/index.md +++ b/okf-bundle/gotchas/index.md @@ -2,5 +2,6 @@ # Concepts +* [Dev Builds Silently Defer to an Installed Instance](dev-builds-defer-to-an-installed-instance.md) * [E2E Tests Touch the Real System Clipboard](e2e-tests-touch-system-clipboard.md) * [macOS Builds Are Unsigned](macos-unsigned-builds.md) diff --git a/okf-bundle/log.md b/okf-bundle/log.md index f67dc3c..c722561 100644 --- a/okf-bundle/log.md +++ b/okf-bundle/log.md @@ -1,5 +1,8 @@ # Update Log +## 2026-08-17 +* document how the single-instance lock makes npm run dev hand off to an installed copy + ## 2026-07-10 * Documented the manual gh-pages deploy process for clipless.app and the GA tracking added 2026-07-09 * Re-verified; added CI CSC skip and swallowed update-check errors diff --git a/package-lock.json b/package-lock.json index b46c290..1fdaea0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "clipless", - "version": "1.8.9", + "version": "1.8.10", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "clipless", - "version": "1.8.9", + "version": "1.8.10", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -39,7 +39,7 @@ "@vitest/coverage-v8": "^4.1.8", "@vitest/ui": "^4.1.8", "electron": "^42.3.3", - "electron-builder": "^26.8.1", + "electron-builder": "^26.15.7", "electron-vite": "^3.1.0", "eslint": "^9.24.0", "eslint-plugin-react": "^7.37.5", @@ -3454,9 +3454,9 @@ } }, "node_modules/app-builder-lib": { - "version": "26.15.3", - "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-26.15.3.tgz", - "integrity": "sha512-2VnyWkqsP5v5XbBhL3tD5Syx8iNPBYsoU7kY4S2fz7wg8Rj/nztWKCUzGKaFRTv0Xwf3/H058CR1Kvtd/3lRow==", + "version": "26.15.7", + "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-26.15.7.tgz", + "integrity": "sha512-C7APoYISPExUmrEntNhDpz9Tccb4uWuEDfLaC0WPPc7/pwzz0WZGznCz/ycPfkkzw6tKOalceD8g6TgHmVz1QA==", "dev": true, "license": "MIT", "dependencies": { @@ -3506,8 +3506,8 @@ "node": ">=14.0.0" }, "peerDependencies": { - "dmg-builder": "26.15.3", - "electron-builder-squirrel-windows": "26.15.3" + "dmg-builder": "26.15.7", + "electron-builder-squirrel-windows": "26.15.7" } }, "node_modules/app-builder-lib/node_modules/@electron/get": { @@ -4805,13 +4805,13 @@ } }, "node_modules/dmg-builder": { - "version": "26.15.3", - "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-26.15.3.tgz", - "integrity": "sha512-O3zJUFUYHJKgzPqioHxfxzBzlSC1eXCSr79gMSBKBP5AgjjpmrydMsMLotEg9fAJF36vdUncb+4ndRNxoPdlSQ==", + "version": "26.15.7", + "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-26.15.7.tgz", + "integrity": "sha512-rfo1YyAWO0L3cZLKCqKQiLYbW6ZXebRUfK0kWp4oXxO7dDFLrf7alRkWImNuXvZVQhs6Idzy++cwOk8I+xPDhw==", "dev": true, "license": "MIT", "dependencies": { - "app-builder-lib": "26.15.3", + "app-builder-lib": "26.15.7", "builder-util": "26.15.3", "fs-extra": "^10.1.0", "js-yaml": "^4.1.0" @@ -4938,18 +4938,18 @@ } }, "node_modules/electron-builder": { - "version": "26.15.3", - "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-26.15.3.tgz", - "integrity": "sha512-a1KM5heqS3gQCZzizXEI8RjJy3QVogULPdeSknt76uLDpBIW/HDGsMg/XgP0riP6PI9COsRvFITKKGDqA8fJxA==", + "version": "26.15.7", + "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-26.15.7.tgz", + "integrity": "sha512-DBpaNzxsPs1BvEblzFoNriSbzsBqDCy/gseIngeEhYzQG1IxfB7Hvc2tBBVmpWE2BTQGP9J1RrAvDT+Vc/uAxg==", "dev": true, "license": "MIT", "dependencies": { - "app-builder-lib": "26.15.3", + "app-builder-lib": "26.15.7", "builder-util": "26.15.3", "builder-util-runtime": "9.7.0", "chalk": "^4.1.2", "ci-info": "^4.2.0", - "dmg-builder": "26.15.3", + "dmg-builder": "26.15.7", "fs-extra": "^10.1.0", "lazy-val": "^1.0.5", "simple-update-notifier": "2.0.0", @@ -4964,14 +4964,14 @@ } }, "node_modules/electron-builder-squirrel-windows": { - "version": "26.15.3", - "resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-26.15.3.tgz", - "integrity": "sha512-Jc19XPV9y9+2bAdZPkXuVNGNIEFBq9poHC61l8Kv6FdK7DRG3+Ic0rerC0DXOaeHNz8yW0fg/JnF8GQROOF5MA==", + "version": "26.15.7", + "resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-26.15.7.tgz", + "integrity": "sha512-B4uvn2NzFSuf084udWqugludFull6CRJiWe2dLzMnZLl6G5hdAGk0fsBMGlBSpKjvQCJn8IPc+S7OnJ+GXqwLA==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "app-builder-lib": "26.15.3", + "app-builder-lib": "26.15.7", "builder-util": "26.15.3", "electron-winstaller": "5.4.0" } diff --git a/package.json b/package.json index 61006d9..7d1842c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "clipless", - "version": "1.8.9", - "description": "An Electron application with React and TypeScript", + "version": "1.8.10", + "description": "A Clipboard manager for busy people", "main": "./out/main/index.js", "author": "Daniel Essig", "license": "MIT", @@ -63,7 +63,7 @@ "@vitest/coverage-v8": "^4.1.8", "@vitest/ui": "^4.1.8", "electron": "^42.3.3", - "electron-builder": "^26.8.1", + "electron-builder": "^26.15.7", "electron-vite": "^3.1.0", "eslint": "^9.24.0", "eslint-plugin-react": "^7.37.5", diff --git a/src/main/dev-profile.ts b/src/main/dev-profile.ts new file mode 100644 index 0000000..4891ba3 --- /dev/null +++ b/src/main/dev-profile.ts @@ -0,0 +1,18 @@ +import { app } from 'electron'; +import { is } from '@electron-toolkit/utils'; + +// Dev builds otherwise share the installed app's userData directory, and with it +// the single-instance lock. When an installed Clipless is running in the tray, +// `npm run dev` loses that lock, quits immediately, and the installed copy +// handles 'second-instance' by focusing its own window — so a window appears, +// the freshly built code never runs, and nothing indicates which build you are +// looking at. A separate profile also keeps dev runs from mutating real clips. +// +// This has to happen before any module derives a path from userData, and the +// storage singleton does so in its constructor at import time. Hence a +// side-effect module imported first in src/main/index.ts rather than a call in +// that file's body: ES module imports are evaluated before the importing +// module's statements. +if (is.dev) { + app.setPath('userData', `${app.getPath('userData')}-dev`); +} diff --git a/src/main/index.ts b/src/main/index.ts index a8862fa..33bb8ec 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -1,3 +1,7 @@ +// Must stay first: it redirects userData for dev builds before any other module +// captures a path derived from it. See src/main/dev-profile.ts. +import './dev-profile'; + import { app } from 'electron'; import { initializeApp, setupAppEvents, initializeServices } from './app'; diff --git a/src/renderer/settings.html b/src/renderer/settings.html index aeade2d..22248d6 100644 --- a/src/renderer/settings.html +++ b/src/renderer/settings.html @@ -4,7 +4,6 @@ Clipless Settings -