Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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_<version>_amd64.deb` | ✅ in-app (prompts for your password) |
| `.AppImage` | `chmod +x clipless-<version>.AppImage` and run it | ✅ in-app |
| `.snap` | `sudo snap install --dangerous clipless_<version>*.snap` | ❌ (sideloaded snaps don't refresh) |
| Format | Install | Auto-update |
| ----------- | ------------------------------------------------- | ------------------------------------- |
| `.deb` | `sudo apt install ./clipless_<version>_amd64.deb` | ✅ in-app (prompts for your password) |
| `.AppImage` | `chmod +x clipless-<version>.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:
Expand Down
20 changes: 19 additions & 1 deletion electron-builder.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment thread
dantheuber marked this conversation as resolved.
maintainer: electronjs.org
category: Utility
Expand All @@ -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
Expand Down
48 changes: 48 additions & 0 deletions okf-bundle/gotchas/dev-builds-defer-to-an-installed-instance.md
Original file line number Diff line number Diff line change
@@ -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.
1 change: 1 addition & 0 deletions okf-bundle/gotchas/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
3 changes: 3 additions & 0 deletions okf-bundle/log.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
42 changes: 21 additions & 21 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down Expand Up @@ -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",
Expand Down
18 changes: 18 additions & 0 deletions src/main/dev-profile.ts
Original file line number Diff line number Diff line change
@@ -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`);
}
4 changes: 4 additions & 0 deletions src/main/index.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down
1 change: 0 additions & 1 deletion src/renderer/settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Clipless Settings</title>
<link rel="stylesheet" href="./settings.css" />
</head>
<body>
<div id="root"></div>
Expand Down