From 1a3043a0e69524da23268874b7b5b902b69e40a8 Mon Sep 17 00:00:00 2001 From: Dan Essig Date: Sun, 16 Aug 2026 17:34:56 -0700 Subject: [PATCH 1/9] fix(ci): publish the snap to GitHub so latest-linux.yml is written electron-builder falls back to { provider: snapStore } for snap artifacts whenever no snap-specific publish config names a different provider (SnapTarget.findSnapPublishConfig). The Snap Store upload needs snapcraft on the build machine, which CI does not have, so the linux job died with 'snapcraft is not installed' *after* uploading the AppImage and deb but *before* electron-builder wrote the updater metadata. That is why the v1.8.8 and v1.8.9 drafts carry Linux artifacts but no latest-linux.yml, leaving Linux auto-update non-functional. Point the snap at the same GitHub draft release as every other artifact. Verified locally: the snap now resolves to 'provider=github', and a full linux build emits latest-linux.yml listing the AppImage and the deb. --- electron-builder.yml | 12 ++++++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/electron-builder.yml b/electron-builder.yml index 17b736f..f1e5406 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -78,6 +78,18 @@ linux: icon: build/icons appImage: artifactName: ${name}-${version}.${ext} +snap: + # 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. Publish the snap to the GitHub release like everything + # else; the Snap Store is not somewhere we release today. + publish: + provider: github + owner: dantheuber + repo: clipless + releaseType: draft npmRebuild: false publish: provider: github diff --git a/package-lock.json b/package-lock.json index b46c290..771522e 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": { diff --git a/package.json b/package.json index 61006d9..f86091b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "clipless", - "version": "1.8.9", + "version": "1.8.10", "description": "An Electron application with React and TypeScript", "main": "./out/main/index.js", "author": "Daniel Essig", From 70b1cb6cfb12d1ebe6494a38c24ec9f321a7eb41 Mon Sep 17 00:00:00 2001 From: Dan Essig Date: Sun, 16 Aug 2026 18:35:36 -0700 Subject: [PATCH 2/9] fix(packaging): replace the placeholder app description package.json still carried the electron-vite template description, which electron-builder propagates into the Snap Store listing, the deb control file and the desktop entry Comment. Set it to the real tagline before the snap is published to the store. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f86091b..9cf16e5 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "clipless", "version": "1.8.10", - "description": "An Electron application with React and TypeScript", + "description": "A Clipboard manager for busy people", "main": "./out/main/index.js", "author": "Daniel Essig", "license": "MIT", From 0c0ae0d943a557126da20d22b9135d638cd9e7c0 Mon Sep 17 00:00:00 2001 From: Dan Essig Date: Sun, 16 Aug 2026 18:40:55 -0700 Subject: [PATCH 3/9] ci(snap): upload the snap to the Snap Store as a non-fatal step Publishing from inside the electron-builder run makes a store outage or a rejected review abort the linux job before latest-linux.yml is written, silently breaking auto-update for the deb and AppImage. Upload separately with continue-on-error, gated on the SNAPCRAFT_STORE_CREDENTIALS secret so forks and credential-less runs skip it cleanly. Lands on edge; promotion to stable stays manual, matching the draft-release flow. --- .github/workflows/manual-tag-release.yml | 22 ++++++++++++++++++ .github/workflows/merge-to-main.yml | 29 ++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/.github/workflows/manual-tag-release.yml b/.github/workflows/manual-tag-release.yml index e116178..9fbffb2 100644 --- a/.github/workflows/manual-tag-release.yml +++ b/.github/workflows/manual-tag-release.yml @@ -57,3 +57,25 @@ jobs: CSC_IDENTITY_AUTO_DISCOVERY: false CSC_LINK: '' CSC_KEY_PASSWORD: '' + + # Kept out of the electron-builder run on purpose - see the matching step + # in merge-to-main.yml for why a store failure must not fail the release. + - name: Publish snap to the Snap Store + if: matrix.platform == 'linux' + continue-on-error: true + env: + SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }} + run: | + if [ -z "$SNAPCRAFT_STORE_CREDENTIALS" ]; then + echo "SNAPCRAFT_STORE_CREDENTIALS is not set - skipping Snap Store upload." + exit 0 + fi + snap_count=$(ls -1 dist/*.snap 2>/dev/null | wc -l) + if [ "$snap_count" -ne 1 ]; then + echo "Expected exactly one snap in dist/, found $snap_count - skipping upload." + exit 0 + fi + snap_file=$(ls -1 dist/*.snap) + sudo snap install snapcraft --classic + echo "Uploading $snap_file to the edge channel" + snapcraft upload "$snap_file" --release=edge diff --git a/.github/workflows/merge-to-main.yml b/.github/workflows/merge-to-main.yml index 60f3619..3972f08 100644 --- a/.github/workflows/merge-to-main.yml +++ b/.github/workflows/merge-to-main.yml @@ -103,3 +103,32 @@ jobs: CSC_IDENTITY_AUTO_DISCOVERY: false CSC_LINK: '' CSC_KEY_PASSWORD: '' + + # Snap Store publishing is deliberately kept out of the electron-builder + # run. Uploading from inside the build makes a store outage or a rejected + # review abort the whole linux job, and it aborts *before* the updater + # metadata (latest-linux.yml) is written — which silently breaks + # auto-update for the deb and AppImage. Keep it a separate, non-fatal + # step: the GitHub release is the source of truth, the store is a mirror. + # Uploads land on the edge channel; promote to stable by hand alongside + # publishing the GitHub draft: + # snapcraft release clipless stable + - name: Publish snap to the Snap Store + if: matrix.platform == 'linux' + continue-on-error: true + env: + SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }} + run: | + if [ -z "$SNAPCRAFT_STORE_CREDENTIALS" ]; then + echo "SNAPCRAFT_STORE_CREDENTIALS is not set - skipping Snap Store upload." + exit 0 + fi + snap_count=$(ls -1 dist/*.snap 2>/dev/null | wc -l) + if [ "$snap_count" -ne 1 ]; then + echo "Expected exactly one snap in dist/, found $snap_count - skipping upload." + exit 0 + fi + snap_file=$(ls -1 dist/*.snap) + sudo snap install snapcraft --classic + echo "Uploading $snap_file to the edge channel" + snapcraft upload "$snap_file" --release=edge From e0561d5ef96e2ad750545a86698b216d30226a78 Mon Sep 17 00:00:00 2001 From: Dan Essig Date: Sun, 16 Aug 2026 18:56:09 -0700 Subject: [PATCH 4/9] fix(snap): bump electron-builder to 26.15.7 to fix a broken snap electron-builder 26.15.3 downloads the snap template as a .tar.7z and only un-7zips it, leaving the raw .tar in the directory it hands to mksquashfs. The template's desktop-init.sh, desktop-common.sh and desktop-gnome-specific.sh therefore never make it into the snap, while the tar itself gets packed at the snap root. command.sh execs desktop-init.sh, so the app dies instantly: /snap/clipless/x2/command.sh: line 2: /snap/clipless/x2/desktop-init.sh: No such file or directory The 1.8.7 snap (built on electron-builder 25) has the scripts, so this is a regression from the 26.x bump. 26.15.7 extracts the template correctly. Verified on 26.15.7: all three desktop scripts are present, no stray tar is packed, a full --linux build still emits latest-linux.yml listing the AppImage and the deb, and the snap still routes to provider=github. --- package-lock.json | 38 +++++++++++++++++++------------------- package.json | 2 +- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/package-lock.json b/package-lock.json index 771522e..1fdaea0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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 9cf16e5..7d1842c 100644 --- a/package.json +++ b/package.json @@ -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", From 929bdea055b6ab54dac9ef02db000abf34569514 Mon Sep 17 00:00:00 2001 From: Dan Essig Date: Sun, 16 Aug 2026 19:06:35 -0700 Subject: [PATCH 5/9] Revert "ci(snap): upload the snap to the Snap Store as a non-fatal step" This reverts commit 0c0ae0d943a557126da20d22b9135d638cd9e7c0. --- .github/workflows/manual-tag-release.yml | 22 ------------------ .github/workflows/merge-to-main.yml | 29 ------------------------ 2 files changed, 51 deletions(-) diff --git a/.github/workflows/manual-tag-release.yml b/.github/workflows/manual-tag-release.yml index 9fbffb2..e116178 100644 --- a/.github/workflows/manual-tag-release.yml +++ b/.github/workflows/manual-tag-release.yml @@ -57,25 +57,3 @@ jobs: CSC_IDENTITY_AUTO_DISCOVERY: false CSC_LINK: '' CSC_KEY_PASSWORD: '' - - # Kept out of the electron-builder run on purpose - see the matching step - # in merge-to-main.yml for why a store failure must not fail the release. - - name: Publish snap to the Snap Store - if: matrix.platform == 'linux' - continue-on-error: true - env: - SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }} - run: | - if [ -z "$SNAPCRAFT_STORE_CREDENTIALS" ]; then - echo "SNAPCRAFT_STORE_CREDENTIALS is not set - skipping Snap Store upload." - exit 0 - fi - snap_count=$(ls -1 dist/*.snap 2>/dev/null | wc -l) - if [ "$snap_count" -ne 1 ]; then - echo "Expected exactly one snap in dist/, found $snap_count - skipping upload." - exit 0 - fi - snap_file=$(ls -1 dist/*.snap) - sudo snap install snapcraft --classic - echo "Uploading $snap_file to the edge channel" - snapcraft upload "$snap_file" --release=edge diff --git a/.github/workflows/merge-to-main.yml b/.github/workflows/merge-to-main.yml index 3972f08..60f3619 100644 --- a/.github/workflows/merge-to-main.yml +++ b/.github/workflows/merge-to-main.yml @@ -103,32 +103,3 @@ jobs: CSC_IDENTITY_AUTO_DISCOVERY: false CSC_LINK: '' CSC_KEY_PASSWORD: '' - - # Snap Store publishing is deliberately kept out of the electron-builder - # run. Uploading from inside the build makes a store outage or a rejected - # review abort the whole linux job, and it aborts *before* the updater - # metadata (latest-linux.yml) is written — which silently breaks - # auto-update for the deb and AppImage. Keep it a separate, non-fatal - # step: the GitHub release is the source of truth, the store is a mirror. - # Uploads land on the edge channel; promote to stable by hand alongside - # publishing the GitHub draft: - # snapcraft release clipless stable - - name: Publish snap to the Snap Store - if: matrix.platform == 'linux' - continue-on-error: true - env: - SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }} - run: | - if [ -z "$SNAPCRAFT_STORE_CREDENTIALS" ]; then - echo "SNAPCRAFT_STORE_CREDENTIALS is not set - skipping Snap Store upload." - exit 0 - fi - snap_count=$(ls -1 dist/*.snap 2>/dev/null | wc -l) - if [ "$snap_count" -ne 1 ]; then - echo "Expected exactly one snap in dist/, found $snap_count - skipping upload." - exit 0 - fi - snap_file=$(ls -1 dist/*.snap) - sudo snap install snapcraft --classic - echo "Uploading $snap_file to the edge channel" - snapcraft upload "$snap_file" --release=edge From 15fef5cd666e29afd35d499eab32af2ee1cf06c0 Mon Sep 17 00:00:00 2001 From: Dan Essig Date: Sun, 16 Aug 2026 19:08:52 -0700 Subject: [PATCH 6/9] build(linux): stop shipping the snap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 (verified on an AMD Strix Halo / Radeon 8060S) every DRI driver in it fails to load — radeonsi, kms_swrast and swrast alike — so EGL init fails and the app segfaults before a window appears. --disable-gpu, --use-gl=disabled and --disable-software-rasterizer all still segfault, because the loader failures come from GTK in the main process, not just the GPU process. Reviving it means base: core24 with gnome-46-2404 and the mesa-2404 content snap, built through the real snapcraft toolchain instead of the prebuilt template — its own piece of work. Until then, don't ship a binary that crashes on modern hardware. AppImage and deb both work and both auto-update. --- README.md | 11 +++++------ electron-builder.yml | 20 +++++++++++++------- 2 files changed, 18 insertions(+), 13 deletions(-) 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 f1e5406..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 @@ -79,12 +84,13 @@ linux: appImage: artifactName: ${name}-${version}.${ext} snap: - # 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. Publish the snap to the GitHub release like everything - # else; the Snap Store is not somewhere we release today. + # 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 From 446f0e003fee03dadf80fbae60c76bc961cf923c Mon Sep 17 00:00:00 2001 From: Dan Essig Date: Sun, 16 Aug 2026 21:08:02 -0700 Subject: [PATCH 7/9] fix(dev): give dev builds their own userData profile Dev builds shared the installed app's userData directory, and with it the single-instance lock. With an installed Clipless running in the tray, `npm run dev` lost the lock, quit immediately, and the installed copy handled 'second-instance' by focusing its own window -- so a window appeared, the freshly built code never ran, and nothing indicated which build was on screen. Rebuilding and reinstalling could not break the loop, because the stale process was never restarted. Dev now runs against -dev, which also stops dev runs from mutating real clip history. The redirect is a side-effect module imported first in index.ts rather than a call in its body: the storage singleton derives its path from userData in a constructor that runs at import time, and ES module imports are evaluated before the importing module's statements. --- src/main/dev-profile.ts | 18 ++++++++++++++++++ src/main/index.ts | 4 ++++ 2 files changed, 22 insertions(+) create mode 100644 src/main/dev-profile.ts 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'; From c358cd6e1e30f581cf816dc8294b2aa03838de4e Mon Sep 17 00:00:00 2001 From: Dan Essig Date: Sun, 16 Aug 2026 21:08:07 -0700 Subject: [PATCH 8/9] chore(renderer): drop dead settings.css link settings.html linked ./settings.css, which does not exist -- the real stylesheet is imported by settings-main.tsx and emitted into assets/. The tag only produced a failed request. --- src/renderer/settings.html | 1 - 1 file changed, 1 deletion(-) 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 -
From e258a78a6207a85e32a7b328fb62dceb39226211 Mon Sep 17 00:00:00 2001 From: Dan Essig Date: Sun, 16 Aug 2026 21:08:07 -0700 Subject: [PATCH 9/9] [ci skip] okf bundle - document dev builds deferring to installed instances --- ...v-builds-defer-to-an-installed-instance.md | 48 +++++++++++++++++++ okf-bundle/gotchas/index.md | 1 + okf-bundle/log.md | 3 ++ 3 files changed, 52 insertions(+) create mode 100644 okf-bundle/gotchas/dev-builds-defer-to-an-installed-instance.md 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