- EVENTS.md: `bread.command.clip.pin` and `bread.clip.pinned` /
`.pin.failed` are now implemented; `select` stays explicitly not
implemented with the reason. AGENTS.md follows.
- README: Configuration section, Ctrl+P bind, updated privacy notes
(CLIPBOARD_STATE + ignore rules), JPEG image entries, primary badge.
- check.yml also runs on pushes to `main` — a push to main triggers a
dev-track release build, so it should be linted/tested first.
- release.yml uses `${GITHUB_REPOSITORY}` instead of a hard-coded
`Breadway/breadclip` for the GitHub mirror release upload.
- Ctrl+P pins/unpins the selected row, re-reads history (pinned rows
sort first), rebuilds the list and re-selects the toggled row.
- ★ badge on pinned rows, "primary" badge on primary-selection rows.
- Panel width comes from `config.panel_width`; positioning now runs
after the panel is realised and measures its natural height instead
of always assuming the worst-case estimate, so a short history gets
a correctly-anchored short panel.
- Delete in the search box edits the query again instead of deleting
the selected history row.
- Re-copy offers an image row as its stored MIME type (JPEG stays
JPEG). History fetch is a flat cap since pinned rows can exceed
retention.
- Capture now keys off wl-paste's `CLIPBOARD_STATE`: `sensitive`
(password manager via `wl-copy --sensitive`, covers the old
x-kde-passwordManagerHint case) and `nil`/`clear` persist nothing;
`data` reads the content off stdin in the same event, so the
sensitive check and the read can't straddle a clipboard change.
A manual `--capture-once` with no CLIPBOARD_STATE falls back to
querying wl-paste directly, now wrapped in `timeout -k 2 5` so a
stalled offer can't block a capture process forever.
- Single-instance guard moves to `bread_utils::singleton` (flock) —
kernel-atomic, auto-released on death, no stale pid file.
- `ignore_rules::is_sensitive`: conservative best-effort heuristics
that skip copies which look like secrets even when unflagged —
PEM/OpenSSH private key blocks, `password:`-style lines, labelled
one-time codes, Luhn-valid card numbers, well-known token prefixes.
A convenience, not a security boundary.
- `bread.command.clip.pin` verb (payload `{id, pin?}`, pin defaults
true) → `set_pinned`, emits `bread.clip.pinned` / `.pin.failed`.
- Two independent `wl-paste --watch` loops (regular clipboard always
on; primary selection when `capture.primary`), each restarting with
2s→30s capped backoff and state-change-only journal logging.
- Image type is sniffed from magic bytes (PNG/JPEG) on the stdin path
and requested by actual offered type on the fallback path — a JPEG
is stored as `.jpg` / `image/jpeg`, not relabelled PNG.
- content_kind: a single line of prose that merely contains a keyword
("class is a concept") no longer classifies as code — it must look
like a statement.
Persistence layer changes, all backward-compatible with existing
databases via in-place column migration:
- WAL journal mode + a 5s busy timeout so overlapping `--capture-once`
writers and the popup reader stop dropping captures on SQLITE_BUSY.
- `HistoryError` replaces bare `rusqlite::Error` so a filesystem failure
while writing an image file surfaces instead of leaving a row that
points at a file that was never written.
- Image files are created 0600 from the first syscall (O_CREAT|O_EXCL,
mode 0600) — no world-readable window before a chmod. Data and images
dirs are forced to 0700 on every open. `gc_orphaned_images` sweeps
image files no row references (older than 1h, to spare in-flight
writes).
- `pinned` and `is_primary` columns. Pinned rows are exempt from trim
and sort first; `list_entries` breaks timestamp ties by `id DESC` so
ordering (and which rows trim keeps) is deterministic within a second.
- `Retention` caps are now a field on `HistoryDb` (`open_with`), and
`0` is a legal value ("keep no unpinned entries of this kind").
New `config` module: optional TOML at
`$XDG_CONFIG_HOME/breadclip/config.toml`, every key defaulted and
clamped, unparseable file backed up once (bread-utils tomlcfg
discipline). Keys: retention.text/images, panel.width, capture.primary.