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.
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.