breadpad's CI used to rebuild libadwaita from source in an uncached
Fedora container on every push and broke repeatedly on version drift.
The fix there was a pinned Arch container (current gtk4/libadwaita/
gtk4-layer-shell/graphene are prebuilt pacman packages, no source
build needed) — this centralizes that image/script here so every
GTK4 layer-shell product in the ecosystem can share it instead of
each repo carrying its own copy.
ci/build.sh takes a product repo root + cargo command, and reads an
optional ci/deps.txt from that repo for product-specific extra pacman
packages (EXTRA_PKGS build-arg) without forking the Containerfile.
Product repos should pin this to a commit sha, not track main — an
unrelated change here would otherwise silently affect every product's
next release build.
Replaces the dev/beta branch split with one trunk (main): dev-track
builds still publish on every push, but the beta track now publishes
from a vX.Y.Z-rc.N prerelease tag instead of a separately-maintained
beta branch. Removes the branch nobody reliably kept in sync.
pywal derives @red from the wallpaper and can hand it any hue - on a
blue-toned wallpaper the "red" slot is itself blue, making destructive
buttons indistinguishable from normal accent/confirm buttons. GNOME's
own destructive-action style is a fixed red for the same reason; this
is now the one button in the shared stylesheet that intentionally
ignores the palette.
Two bare class selectors (.title, .subtitle) were colliding with
libadwaita's own internal row/window-title label classes of the same
name, causing every AdwActionRow/AdwSwitchRow/AdwSpinRow title to
inherit the 1.4em heading size meant for app view-titles - the root
cause of breadman settings' ~24px row-title bug found in design review.
Renamed to .page-title/.page-subtitle (breadhelp, the only caller,
updated separately).
Also scoped a .boxed-list override so AdwPreferencesGroup's boxed-list
GtkListBox gets its surface fill + radius back - the shared
`list, listbox { background-color: transparent }` rule (needed for
plain GTK4 sidebars) was stripping it with equal specificity.
New `adw` feature (gated separately from `gtk`, since AdwApplicationWindow
isn't compatible with gtk4-layer-shell — the five panel/launcher apps stay
on plain `gtk`, only breadman/breadhelp-style plain-window apps want this):
preferences_group/toggle_row/spin_row/action_row/preferences_page, wrapping
libadwaita's PreferencesGroup/SwitchRow/SpinRow/ActionRow/PreferencesPage.
adw::init() also forces dark color-scheme, since bread-theme's whole design
is a fixed dark base regardless of system GTK preference.
These directly target defects a design critique found: hand-rolled
switch+label rows with no intrinsic width (breadman/settings' ~1400px
stretched toggles) and spinners stranded far from their label — both just
don't happen when the row is a real AdwSwitchRow/AdwSpinRow instead of a
box assembled from scratch.
Also, two shared-stylesheet fixes usable by every app immediately, gtk
feature only:
- `scale` (slider) had no rule at all, so every volume/brightness slider
showed GTK's own default blue instead of the palette accent — the same
critique flagged breadbar's control-panel sliders contradicting its own
on-brand OSD fill two clicks away.
- A new `chip()`/`set_chip_active()` helper in gtk.rs uses the existing
(already-tokenized, already-defined) `.chip`/`.pill` stylesheet rule
instead of each app hand-rolling its own filter-chip CSS — which is how
breadclip/breadpad/breadman ended up with three different, mutually
disagreeing pill fills for what's supposed to be one shared component.
Drops the app-name filename prefix (redundant with the folder name) —
output is now <out-dir>/<app>/<view>.png instead of a flat
<out-dir>/<app>-<view>.png. Makes browsing a multi-app run's output
directory clearer, and is what a real screenshots-folder deliverable
should look like.
Plain `bread-capture` with no flags now captures every known app's every
view in one run — each binary resolved by its own bare name via $PATH,
same as invoking it directly by name would (so an installed bread
ecosystem needs nothing but `bread-capture` to regenerate every
screenshot). Previously --app-path was required, so there was no way to
run more than one app per invocation.
--app <name> restricts to a single app (resolved via $PATH, no path
needed); --app-path still works alone too, inferring which app by its
file stem exactly as before. --view <name> further restricts to one
view — apps without a matching view are silently skipped rather than
treated as an error, since view names naturally don't overlap across
apps in a multi-app run, but an unmatched --view in a single-app run (or
one that matches nothing across every selected app) is still a real
error.
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.