A solid background can't reveal whether a surface that's supposed to be
translucent (breadbox/breadclip/breadsearch's full-screen overlay
windows, breadbar's alpha-blended notification/OSD surfaces) is actually
compositing as translucent — a flat color showing through a flat color
still just looks flat. Generates a diagonal rainbow gradient (full hue
sweep, via the `image` crate) sized exactly to the capture canvas and
sets it as Sway's output background instead.
Needed installing swaybg (the external helper Sway's `output ... bg`
config directive shells out to) — without it the bg command silently
no-ops and the canvas stays black, which is why the first attempt at
this looked identical to the old flat-color version.
Replaces the breadbar-only hardcoded view list with a registry keyed by
app name (--app-name, defaulting to --app-path's file stem) so wiring up
each new app just means adding one entry, not touching the CLI shape.
Also fixes a real leak found while testing breadbox against this:
std::process::exit() on the failure path skipped every destructor,
including Isolation's Drop — so a failed capture run (or, more subtly,
*any* run against an app whose view list didn't match yet, which is
exactly what happened testing this) permanently orphaned the headless
Sway process and its wayland-N/.lock socket pair. main() now returns
ExitCode instead of calling process::exit directly, so Drop always runs.
Nested Hyprland worked but had real limits: the outer compositor decided
the nested window's pixel size (needing an outer-session float+resize
dispatch per capture), occluded surfaces got no frame callbacks (so grim
hung unless the nested window was also focused/raised), and there was no
way to fully suppress a brief real, visible flash of that window on the
operator's desktop.
wlroots' WLR_BACKENDS=headless (Sway, not Hyprland, is built on wlroots
directly) has a genuine headless backend: no seat/DRM-master claim, no
window anywhere, ever. Confirmed empirically: zero visible footprint,
both zwlr_layer_shell_v1 and zwlr_screencopy_manager_v1 present, grim
completes instantly with no focus dance needed.
This drops the Hyprland-specific plumbing that no longer applies:
- bread-screenshots now exposes one compositor-agnostic capture_region
primitive instead of capture_layer/capture_output, since the isolated
canvas size is always known up front rather than queried via hyprctl.
- bread-utils::hypr loses the Monitor scale/transform/logical_size and
Layer/find_layer additions that only existed to support that querying.
- bread-capture's isolation module spawns headless Sway instead of a
nested Hyprland instance, and passes --width/--height through to the
target app so it knows the canvas size without asking anyone.
Also fixes a socket leak in isolation teardown: killing the compositor
(Hyprland or Sway) doesn't unlink the wayland-N/.lock files it created,
so every capture run was orphaning a socket pair in the runtime dir.
Drop now removes them explicitly.
Captures now run inside a dedicated nested Hyprland session by default
(--no-isolate to opt out), so nothing on the operator's live desktop can
leak into a screenshot and the capture never flashes across their screen
either. The nested instance nests as a Wayland client of the outer session
(true headless was ruled out empirically: this machine's real GPU/output is
already claimed by the live session, and only one process can hold logind's
seat at a time), gets floated/exact-resized/focused via one-shot outer-session
hyprctl dispatches targeted by pid, and has Hyprland's default background/
logo and startup warning overlays disabled via config so captures come out
clean. Focusing turned out to be load-bearing, not cosmetic: an occluded
nested window never gets frame callbacks from the outer compositor, so grim
run inside it hangs forever waiting on a ready event that never comes.
New bread-screenshots crate captures a layer-shell surface (by namespace+pid,
to disambiguate from an already-running instance) or the whole focused
output via grim, using bread-utils::hypr/proc. bread-utils::Monitor gains a
scale field and logical_size() so output geometry accounts for HiDPI/
transform, matching breadshot's proven math. bread-utils::hypr gains
find_layer() over hyprctl layers -j.
bread-capture is a small orchestrator that drives an app's --screenshot mode
and collects the resulting PNGs; hardcoded to breadbar's two views for now.
Closes the packaging gap found while moving bread-ecosystem apps off
pacman onto bakery-only distribution: pacman's package() typically installs
a LICENSE file and, for GUI/onboarding apps, a .desktop entry and sometimes
a data directory (e.g. breadhelp's guide content). All three follow the
same download-verify-place pattern ConfigScaffold.example already
established:
- license_file -> ~/.local/share/licenses/<name>/LICENSE
- desktop_file -> ~/.local/share/applications/<name>.desktop
- data_archive -> a .tar.gz extracted to ~/.local/share/<name>/ (for
arbitrary data too big/structured for a single file, via `tar`)
gen-index.sh parses all three from bakery.toml, hashes the artifact, and
now excludes them from the binaries-collection loop (previously undetected
gap: they'd have been swept in as fake "binaries" with no checksum, same
class of bug the existing .toml/.service/etc exclusions guard against).
Also registers breadhelp as a bakery-channel product.
CLAUDE.md now points to CONTRIBUTING.md as the canonical workflow doc
instead of duplicating the release lifecycle inline. docs/release-channels.md's
current-state table was stale — it still said beta/dev wasn't rolled out
to the sibling repos, which is now done.
CLAUDE.md and docs/release-channels.md now describe the full cycle: work
lands on feature/fix branches merged into dev, dev auto-publishes on every
push, beta is cut from dev as a frozen stabilization branch (also
auto-publishing on every push, fixes forwarded from fix/<issue> branches),
and after a quiet freeze period beta merges to main and gets tagged for
the actual stable release. Also fixes README's stale reference to
.github/workflows (actual CI lives under .forgejo/workflows) and links
out to the new CONTRIBUTING.md.
Beta is now a real stabilization branch: publishes on every push to
`beta` (mirroring dev's model, auto-versioned X.Y.Z-beta.<ts>+<sha>,
base version from the latest published tag) instead of a manual
beta-v* tag. Fixes made during the freeze land via fix/<issue> branches
merged into `beta` directly. The gen-index.sh clone for beta pulls
bread-ecosystem's default branch (main) rather than pinning to dev,
since beta is the more stable track and main now carries the
TRACK-aware script.
Cargo.toml can drift stale relative to the actual last release (observed
on breadbox/breadpad/breadcrumbs/breadpaper), which made the auto-bumped
dev version sort as OLDER than what's already installed — bakery's semver
check correctly refused those "updates". Deriving the base version from
git ls-remote --tags instead is self-healing regardless of Cargo.toml
drift, with a Cargo.toml fallback only for a repo with no tags yet.
Matches the same guard just added to bread's dev/beta workflows: a missing
BAKERY_MINISIGN_SEC_KEY_PATH secret should fail the job loudly rather than
silently publish an unsigned index.
Adds a track concept to bakery (separate from the existing bakery/pacman
distribution channel): stable (unchanged tag-triggered releases), beta
(deliberate beta-v* tag promotion), and dev (published on every push to
dev). Each track gets its own signed index + artifact tree under
dl.breadway.dev so stable's paths and existing installs are untouched.
- bakery: new Track type, a global track preference in installed.json
(defaults to stable via serde, no migration needed), `bakery track
show`/`set`, a BAKERY_INDEX_BASE_URL override for testing, and a real
semver comparison in `update` (was a plain string-equality check before).
ANSI-colored/aligned CLI output (TTY + NO_COLOR aware).
- gen-index.sh: TRACK env var selects which subtree to read/write.
- CI: dev-bakery.yml/beta-bakery.yml/dev-bread-theme.yml/beta-bread-theme.yml
publish those two products on the new tracks; dev/beta skip the GitHub
Release upload step (no per-commit release spam).
- docs/release-channels.md documents the three-track policy.
Signed release binaries ship a bakery-x86_64.minisig alongside the
binary. The binaries-collection loop already filtered out .sha256/
.toml/.service/.css/.txt sidecars but not .minisig, so the signature
file itself got listed as an installable binary with no sha256 —
bakery then refused to install it (checksum mismatch) whenever it
tried to update a package with a signed binary.
The old bakery-signing-key.minisign-sec on hestia was password-encrypted
and the password was lost, so scripts/gen-index.sh never actually signed
index.json (silent no-op warning). bakery/src/manifest.rs (0.3.0+) hard-
requires that signature, so every bakery command has been failing with
'fetching index.json.minisig — the index must be signed before it can be
trusted' since the signing enforcement shipped.
Generated a new no-password minisign keypair on hestia
(~/.secrets/bakery-signing-key-2.minisign-sec), updated the hardcoded
PUBKEY in manifest.rs and get.sh to match, wired
BAKERY_MINISIGN_SEC_KEY_PATH as a Forgejo Actions secret so future CI
releases sign automatically, and manually signed+published the current
index.json on hestia so bakery works immediately.
Replaces the per-repo .forgejo/workflows/mirror.yml + MIRROR_TOKEN
pattern with Forgejo's native Push Mirror feature, provisioned
centrally instead of per-repo:
- setup-push-mirrors.sh: reads the repo list live from the Forgejo API
(GET /users/Breadway/repos — confirmed Breadway is a user account,
not an org, so the /orgs/ endpoint 404s and this falls back
correctly) instead of a hardcoded repo list, checks each repo's
existing push_mirrors for idempotency, and POSTs a new one
(sync_on_commit + 8h interval) for any repo missing one. Private
repos are skipped by default (found novacana-engine on the live
account) since mirroring one to a public GitHub repo is a
disclosure decision this script should never make silently — pass
--include-private to override per-run. --dry-run prints every
request (GH token redacted) without POSTing.
- cleanup-old-mirror-workflows.sh: deletes mirror.yml from each repo's
default branch (live commit via the contents API, not a local
change) and removes the MIRROR_TOKEN secret. Refuses to run at all
— dry-run included — without an explicit
--i-have-verified-push-mirrors-work flag, since it should only ever
run after confirming the new push mirrors are actually syncing.
Neither script has been run for real. setup-push-mirrors.sh has only
been run with --dry-run against the live Forgejo API (read-only GETs);
cleanup-old-mirror-workflows.sh has not been run at all beyond
confirming its guardrail refuses to execute.
bos-settings has carried a bakery.toml since it was split out (per
bos/DESIGN.md: "bos-settings gets a bakery.toml and is added to the
bread-ecosystem registry"), but the registry-side half of that was
never done, so gen-index.sh has never actually picked it up. Paired
with the release.yml added in bos-settings-fix-worktree tonight.
.github/workflows/release.yml built and published the bakery binary
itself, but it lived under .github/ and targeted runs-on:
[self-hosted, hestia] — a runner label only registered against
Forgejo, never against GitHub Actions. It has therefore never run;
get.sh has been pointing at dl.breadway.dev/bakery/... this whole time
with nothing actually publishing there.
Recreated the same logic as .forgejo/workflows/release-bakery.yml,
matching the sibling release-bread-theme.yml in this repo (manual
clone instead of actions/checkout, GH_RELEASE_TOKEN instead of the
GitHub-provided GITHUB_TOKEN, same dormant-until-provisioned minisign
signing step). Removed the dead .github copy.
home_dir() was already computed internally (home_or_root) but not exposed
— breadarr-shared's own expand_home() helper needs it to fix the same bug
class documented in this module: its own fallback (when HOME itself isn't
set) returned the literal unexpanded "~/..." input string instead of a
real path. Also documented breadmon/src/profile.rs's profiles_dir(),
fixed in that repo's own commit.
Found while investigating why breadpad's test suite went from fast (silent
CPU-only ROCm no-op) to multi-minutes slow after fixing the ROCm->MIGraphX
bug in the previous commit: MIGraphX has no Rust-level cache-directory
builder (unlike OpenVINO/Vitis) — it's controlled purely by the
ORT_MIGRAPHX_MODEL_CACHE_PATH env var, read by the underlying MIGraphX
library. Left unset, every session build recompiles every kernel from
scratch with no persistence at all, visible in breadpad's test output as
repeated `migraphx_save: Error: ... write_buffer: Failure opening file:
""/<hash>.mxr` lines (empty path prefix = var never set).
breadmill's own packaged systemd unit already sets this explicitly
(packaging/breadmill.service: ORT_MIGRAPHX_MODEL_CACHE_PATH=%h/.cache/
breadsearch/migraphx-cache) but nothing enforced any other consumer doing
the same — breadpad had no systemd unit or cache path at all. Provider::
MiGraphX's to_dispatch now defaults ORT_MIGRAPHX_MODEL_CACHE_PATH to
~/.cache/bread-onnx/migraphx if unset, so every consumer gets kernel-cache
persistence for free instead of only the ones that remembered to
configure it themselves.
Neither original implementation this replaces (breadbox's
get_active_workspace, breadclip's hyprctl_json) set a read/write timeout
on the Hyprland IPC socket — a wedged or mid-reload Hyprland instance
could hang the call indefinitely, and every current caller runs it on the
GTK main thread, so a hang here freezes the whole UI. Found while
reviewing the crate for this pass's "any existing bugs" sweep; same class
of bug as everything else bread_utils::proc/hypr exists to fix, just one
I'd introduced myself by porting the original code faithfully without
also porting the missing safety net.
singleton.rs's tests used to call the real kill() on the current test
process itself (the recorded "other instance" PID was our own, since
tests run single-process) — split try_acquire (side-effect-free) out from
toggle_or_kill (sends the signal) so the concurrency assertions no longer
risk SIGTERM-ing the test binary.
Separately, hypr.rs's env-var test mutates HYPRLAND_INSTANCE_SIGNATURE/
XDG_RUNTIME_DIR process-globally; cargo runs tests in parallel threads by
default, so it could race a concurrently-running singleton or xdg test
expecting the real XDG_RUNTIME_DIR, intermittently failing them with
ENOENT. Added a shared env_test_lock() all env-var-touching tests now
acquire for their duration.
Verified via 5 repeated full test runs with zero flakes (28/28 passing
each time).
bread-utils extracts genuinely duplicated logic found across breadbox,
breadclip, breadmon, breadcrumbs, bos-settings, and breadhelp:
- hypr: Hyprland socket1 request/response client (breadbox's
get_active_workspace + breadclip's position.rs hyprctl_json were
near-identical), socket2 path resolution (breadmon), and a
version-tolerant `fullscreen` field parser (Hyprland has shipped both
bool and int representations across versions).
- singleton: correct flock-based single-instance toggle, replacing the
TOCTOU-prone read-pid/check-proc/kill/write-pid pattern duplicated
verbatim between breadbox and breadclip (breadclip's own comment says
"matches breadbox pattern").
- proc: breadcrumbs' timeout-guarded subprocess runner, promoted verbatim
as the one implementation in the ecosystem that already got this right.
- atomic + xdg: atomic (temp-then-rename) file writes with an optional
.bak-before-overwrite variant, and XDG path helpers that never fall back
to a literal "~/..." string (the exact breadclip-core and
breadpad-shared bug: PathBuf never expands `~`).
- tomlcfg (feature "toml"): the load_doc/save_doc TOML-editing discipline
bos-settings and breadhelp both implemented byte-for-byte identically in
the same fix pass that introduced it.
- gtk_popup (feature "gtk"): layer-shell overlay window setup, visible-row
navigation, and click-outside-close, deduplicated from breadbox and
breadclip (~150 duplicated lines, per both apps' own "same as breadbox"
comments).
bread-onnx extracts the embedding pipeline (tokenize -> tensor build ->
mean-pool -> L2-normalize) duplicated near-verbatim between breadarr and
breadsearch, a shared execution-provider session builder with loud EP-
registration logging, and a model download+integrity helper. Defaults AMD
iGPU acceleration to ort::ep::MIGraphX (not ROCm) per this machine's own
breadsearch-gpu-backends lesson: ROCMExecutionProvider silently no-ops to
CPU on distro ROCm onnxruntime builds compiled with --use_migraphx.
Both crates build and pass their own test suites standalone. Consumer
migrations follow in subsequent commits.
- Add minisign-based signing/verification for the bakery index:
scripts/gen-index.sh signs index.json (MINISIGN_SEC_KEY env var, dormant
no-op with a loud warning until a key is provisioned); bakery/src/manifest.rs
fetches index.json.minisig and verifies it with minisign-verify against a
hardcoded PUBKEY before parsing/caching, and re-verifies the cached copy
on every load (falls back to one re-fetch if the cache predates signing
or fails verification; a fresh fetch that fails verification is a hard
error).
- Close the previously-unchecksummed config-example and systemd-unit
downloads in bakery/src/install.rs (scaffold_config, install_service):
index.json now carries `sha256`/`example_sha256` for these artifacts
(computed in gen-index.sh), verified via the same download::verify_sha256
used for binaries. Downloads without a matching sha256 in the index are
refused rather than installed unverified.
- scripts/get.sh now verifies the bakery release binary itself against a
pinned minisign public key before installing it (falls back to the
existing sha256-only check with a loud warning if no .minisig is
published yet or minisign isn't installed; a present-but-invalid
signature is a hard failure).
- Add dormant "sign release binary" steps to the bakery and bread-theme
release workflows (.github/workflows/release.yml,
.forgejo/workflows/release-bread-theme.yml), gated on secrets that are
not yet configured — binaries ship unsigned exactly as before until the
owner wires up the secret.
- .gitignore: add *.minisign-sec / minisign.key so the signing key can
never be committed by accident.
- bread-theme: fix stale docs describing a "Catppuccin Mocha fallback"
(BREAD_DESIGN_SYSTEM.md, README.md, Cargo.toml/bakery.toml/registry
descriptions) — the actual implementation (palette.rs) uses a fixed BOS
dark base with only accent colors from pywal.
- bread-theme: fix the legacy css_vars() path, which had its own
hand-written @define-color block that predated the `accent` and computed
`on-*` ink colors used by the rest of the stylesheet — any caller whose
CSS referenced those names against css_vars()'s output would hit
undefined colors (the illegible-text bug). css_vars() now delegates to
the same define_colors() the full stylesheet uses, so the two can't
drift apart again.
Was previously an unmanaged binary baked into /etc/skel — never updated by
bakery or pacman, which is why fresh BOS installs (and existing ones) kept
regenerating the shared theme.css with the pre-fix pywal-driven background
even after bos-settings itself shipped the fix.
A light or muddy-toned wallpaper (e.g. an all-beige photo) makes pywal hand
back a light or off-hue background/surface slot, and every bread GUI's
panels inherited it directly — the app stopped looking like a dark BOS tool
and started looking like whatever colour the wallpaper happened to be.
background/foreground/color0(surface)/color7(overlay) are now fixed BOS
dark-theme constants, never read from pywal's special/colors blocks. Only
color1-6 (the actual accent slots) still track the wallpaper via pywal, which
is the feature breadpaper is for.
The stylesheet is written with write-tmp-then-rename (atomic), which replaces the
inode. A monitor on the file itself caught the first replace then went deaf
(inotify reports DELETE_SELF and never re-arms), so `bread-theme reload` updated
the file but no running GUI ever recoloured. Monitor the parent directory and
filter for the stylesheet filename instead — that fires on every reload. Verified
against a real atomic-rename write (event arrives as Renamed with the new name in
other_file, so match both file and other_file).
Readability: pywal can emit a light value in any palette slot, and the shared
sheet assumed dark backgrounds (white text), so text vanished on light surfaces/
accents. Add ink_on() — a WCAG-luminance pick of near-black/near-white per
background — exposed as @on-bg/@on-surface/@on-accent/@on-red/@on-overlay. The
component sheet now sets colour on containers and lets labels inherit (de-emphasis
via opacity), dropping the blanket `label { color }` rule that overrode
coloured-background text. pywal hues are untouched.
Hot reload: add gtk::apply_app_css(closure) — applies an app's own CSS now and
re-runs the closure whenever the shared theme file is rewritten, so apps recolour
in place. New `bread-theme reload` verb rewrites the file (atomic rename trips
every running GUI's monitor) — the command to run after changing pywal colours.
README: add recommended keybinds and a Theming section documenting the
bread-theme generator + live-reloaded shared stylesheet. Add
BREAD_DESIGN_SYSTEM.md to the repo (the README links it) and update it to
describe the single-source-of-truth architecture and the migrated apps
(incl. bos-settings).
Adds the single source of truth for bread GUI styling so the apps stop
each re-implementing (and drifting on) component CSS:
- stylesheet(&Palette): full component sheet (buttons, entries, switches,
dropdowns, lists/rows/sidebars, cards, chips, scrollbars, headings) built
from the design tokens + a canonical @define-color block (surface=color0,
overlay=color7, accent=color4).
- render() / shared_css_path() / write_shared_css(): render for the current
pywal palette and write to $XDG_RUNTIME_DIR/bread/theme.css.
- gtk::apply_shared(): load that file (or a rendered fallback) at APPLICATION
priority and watch it, so every app recolours live with no rebuild.
- new `bread-theme` CLI (generate|path|print) — gtk-free, light. Run at
session start and on palette change; apps pick it up via the file watch.
The contract is a CSS *file*, so apps stay decoupled from this crate's gtk4
version. Tests cover the stylesheet, path, and render helpers.