breadbar re-execs itself on a shell.toml 'active' change and breadbox
re-reads the theme per launcher open, so a switch applies without the
user doing anything. Reword the Apply card to say so and demote the
'Restart bar & launcher' button to a fallback.
Wallpaper (Breadpaper.svelte) becomes monitor-aware: with more than one
output connected it shows a 'use the same wallpaper on every monitor'
switch; turning it off reveals per-output chips and routes 'Choose image'
/ library picks through 'breadpaper --output <NAME> set', which
regenerates only that monitor's pywal palette. Single-monitor behaviour
is unchanged. Backend: get_wallpapers_by_output (reads breadpaper's
current.json) + set_wallpaper_on.
New 'Shell theme' tab: lists bread_theme:🐚:list() (liquid-motion,
glass-workbench, spotlight, daylight) and writes ~/.config/bread/shell.toml
'active =' non-destructively via toml_edit. This is a single global
selector — per-monitor differences come from the wallpaper/palette, not
the shell theme. breadbar/breadbox don't hot-reload the change today, so
the tab is explicit that it applies on restart/login and offers a
'Restart bar & launcher' button (setsid -f, so it leaves no zombies).
bread-theme bumped v0.7.4 -> v0.7.5 for shell::list().
The only CI here was dev-release.yml / rc-release.yml / release.yml — the
publish workflows. Nothing ran clippy or the test suite, so a PR (or a push
straight to main, which publishes a dev build) could ship with lint errors
or failing tests. This runs `cargo clippy --all-targets -D warnings` +
`cargo test` on feature/**, fix/**, and main.
The Tauri crate is at ./src; clippy and tests don't need the built frontend.
fmt not gated yet (tree isn't fmt-clean — separate change).
Audit findings #1 and #4.
#1 (accent mapping): render_theme_css hand-rolled the `:root { --name }`
block and mapped --accent, --red, --on-accent and --on-red all to
pywal's color1 (ANSI red — the slot bread-theme reserves for errors),
so the settings webview accented in a different hue from every GTK app
on the same monitor, and destructive-action styling was indistinguishable
from accent styling. Replace it with a direct call to
bread_theme::css_custom_properties / css_tokens (both exported in the
pinned v0.7.4), which format the canonical color_pairs list: --accent =
color4, --red = color1. As a bonus css_tokens emits a correct
font-family list instead of one over-quoted family.
#4 (live updates): watch_and_emit watched only the shared theme.css, so
`breadpaper set_on` on a non-focused monitor (which writes only
palettes/<out>.json + themes/<out>.css) never reached a settings window
parked there, and dragging the window between monitors never re-rendered.
Watch the generated-theme dir recursively and re-render when the shared
sheet or *this monitor's* per-output files change; add a
WindowEvent::Moved handler that re-renders when the window lands on a
different monitor.
The redesign branch was cut before "Harden privileged command operands
against injection" landed on main; the rebase conflicts in users.rs,
power.rs, firewall.rs and capabilities/default.json were resolved to
keep the stricter side of each:
- users.rs: keep `chpasswd_input` / `valid_chpasswd_password` (validates
the *password* for `:` / newline, which the redesign's inline checks
missed) and `may_delete_user` (refuses root / the current user);
fold in the redesign's `--` argv separators and GECOS control-char
stripping on top.
- power.rs: keep `charge_threshold_write` (validates `which` ∈
{start,end} and clamps) + `util::run_with_stdin` for the tee pipe,
plus its test; take the redesign's brightness clamp.
- firewall.rs: keep the port-only `valid_firewall_rule` /
`valid_rule_number` allowlist from f7b114f. NOTE: the redesign's
Firewall UI hint advertises service names ("OpenSSH") which this
validator rejects — either loosen it to `util::valid_cli_value` or
drop the service-name hint. Left strict pending that call.
- capabilities/default.json: keep `opener:default` removed (nothing in
the frontend or backend uses the opener plugin); add the four
`core🪟*` perms the redesign's custom Titlebar needs.
Drops `util::valid_username` / `util::valid_cli_value` (added by the
redesign, now unused — every call site uses the stricter f7b114f local
validator) and the unused `use super::util` in firewall.rs.
Rework the settings app around hub pages (home, network, displays, input,
apps, privacy, system) with a redesigned sidebar, shared nav state, and new
hub view components. Alongside the redesign: validate webview inputs into
root commands (users, firewall, snapshots, wifi), fix set_charge_threshold
writing the percentage via tee stdin, prevent streaming installs from
hanging on inherited stdin, add frontend type fixes, sync versions to
0.8.2, and clean up clippy/svelte-check warnings.
Validate chpasswd user:password fields, refuse deleting root or the
current user, and pipe charge-threshold writes through tee stdin.
Allowlist firewall rules, systemd units, AUR names, hostname, and
timezone. Keep CalDAV passwords write-only like restic.
The Install driver button runs the real setup instead of only
pacman_install. Prefers pkexec bos-nvidia-setup; falls back to the
allowlisted packages plus the same hyprland.lua drop-in. Tells the
user to reboot. Still click-only — first boot does not install.
- find_block: elide the unnecessary 'a lifetime
- scan_wifi: use sort_by_key(Reverse) instead of sort_by for the signal-descending sort
- WatcherHandle: mark the RecommendedWatcher field as intentionally unread —
it's held only to keep the watcher alive for the process lifetime, never
accessed directly
Load and save Wi-Fi networks in networks.toml (0600) instead of writing
PSKs back into breadcrumbs.toml. The password field is write-only.
Replace the generic argv runner with typed bakery/pacman/fwupd commands
and set a real Tauri CSP. Add Lock, Screenshots, Monitors, and Help
panels. Pin bread-theme and bread-utils to bread-ecosystem v0.7.1.
bread-screenshots is not on that tag, so --screenshot calls grim
locally. bakery.toml lists webkitgtk-4.1 deps; README/CLAUDE.md match
Tauri 2 + Svelte 5 and single-trunk main.
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.
The first pass only proved the Tauri capture pipeline against the
default landing view ("About"). All 24 real sidebar sections are wired
up now (every id in lib/sidebar.ts has a real component in
lib/views/registry.ts — no Placeholder fallbacks to skip).
Since there's no URL/route per section (activePage is plain Svelte
$state, switched by the sidebar's own click handler), the frontend now
also listens for a screenshot-set-view Tauri event in +page.svelte's
onMount and sets activePage from its payload — the same effect a real
sidebar click has, just driven from the Rust side. screenshot.rs emits
that event after the initial page-load settle delay, then waits a
second settle delay for the newly-active view's own Tauri-command data
fetch to land before capturing (each section fetches its own state on
mount, same as the About page's system-info fetch needed 2s in the
first pass).
First bread-ecosystem app on the Tauri/webview stack rather than raw
GTK4 — proves the isolated-headless-Sway capture pipeline works against
a plain (non-layer-shell) tao/wry window with no gtk4-rs-specific signal
to hook. No connect_map here: since the window isn't owned by gtk4-rs
directly, this instead waits a fixed settle delay (2s — longer than the
native apps need, since a webview's first paint means a full page load
plus an async Tauri-command round-trip for real content, not just GTK
widget layout — confirmed by an initial 800ms attempt capturing the
sidebar correctly but the About panel's data still blank) on Tauri's own
async runtime, then captures the known-size canvas and exits.
Only one view ("default", the initial landing section) is wired up.
Capturing a specific sidebar section would mean routing the Svelte
frontend to a URL/hash on window creation — real, separate frontend
work, deferred past this first pass.
bread-screenshots is pulled from bread-ecosystem's dev branch, unlike
this repo's existing bread-theme/bread-utils deps which point at main —
it doesn't exist on main yet.
The path dependency on ../../bread-ecosystem/bread-theme (and bread-utils)
assumed a sibling checkout that doesn't exist on the CI runner — this was
the actual cause of release.yml never having succeeded, on top of the
Tauri build-path issue fixed separately. Switched to a git dependency
pinned to branch = "main" rather than a tag, since the functions this
migration needs (css_custom_properties/css_tokens) aren't in a tagged
bread-theme release yet. Verified with a full local build.
bakery.toml gains license_file/desktop_file (closing the same gap found
across the rest of the ecosystem) plus hicolor-icon-theme as an optional
dep. Removes packaging/PKGBUILD and .forgejo/workflows/package.yml.
Also fixes release.yml, which has never actually succeeded since the
GTK4->Tauri migration (93c3b88): Cargo.toml moved to src/src/Cargo.toml
(the checkout dir is conventionally named "src", and this repo's own
Rust backend directory is *also* named "src", not the usual src-tauri),
and the Rust build needs frontend/build to already exist (tauri.conf.json's
frontendDist) — that's normally handled by `cargo tauri build`'s
beforeBuildCommand hook, which a plain `cargo build` never runs, so it's
now a separate explicit "build frontend" step. Adds dev-release.yml/
beta-release.yml for the same three-track policy used ecosystem-wide.
cargo build --release alone doesn't produce a working standalone binary in
Tauri v2 — embedding the frontend vs. hitting devUrl at runtime is gated by
the tauri/custom-protocol Cargo feature, not the debug/release profile.
The scaffold never declared it, so even release builds tried to connect to
localhost:1420. Added the standard default-on custom-protocol feature
(cargo tauri dev strips it via --no-default-features for dev builds); a
plain `cargo build --release --locked` in src/ now produces a real
standalone binary with no dev-server dependency.
Also fixes vite.config.js's file-watch ignore pattern, left stale by the
src-tauri -> src rename: a bare "**/src/**" glob would have also matched
frontend/src itself, so it's now an absolute path scoped to the Rust
project dir specifically.
TagsField now takes an optional suggestions list, rendered as a native
<datalist> on the tag input. Wired the Daemon page's "Disabled modules"
field to breadd's real built-in module names (bread.monitors/devices/
workspaces/binds, from bread/breadd/src/lua/mod.rs's BUILTIN_* registry)
so blocking one is a pick, not free-typed guesswork.
Ports every remaining view (Keybinds, the last one — dual Flat/MultiLayout
schema detection, per-action dedicated fields with a raw-JSON fallback for
anything not modeled) and removes the now-fully-dead GTK4 crate (root
Cargo.toml + src/), leaving src-tauri/ as the single Rust binary crate.
Also folds in the UX pass done alongside the migration: responsive
multi-column layout instead of a single centered column, chip/dropdown
pickers replacing free-typed fields (Wi-Fi networks, Bar style, keyboard
layout, tag lists), consistent pill-switch toggles and boxed-list card
styling across every page, and a sidebar scroll-chaining fix.
bos-settings has had a bakery.toml, a PKGBUILD, and package.yml
(pacman channel) for a while, but never got the release.yml needed to
actually publish to dl.breadway.dev / GitHub releases for the bakery
channel — despite bos/DESIGN.md documenting the intent to make it
bakery-installable standalone. Modeled on breadmon/release.yml (same
shape: single binary, no systemd service). Paired with a new registry
entry in bread-ecosystem-fix-worktree.
Note for whoever runs this for real: bos-settings' Cargo.toml currently
has bread-utils pinned via a path dependency
(../bread-ecosystem-fix-worktree/bread-utils) rather than the tag-pinned
git dependency bread-theme uses — there's already a TODO on that line.
This release.yml (and the existing package.yml) will fail to build in a
clean CI checkout until that's resolved; pre-existing issue, not
something this pass touched.
get_live_monitors used a bare Command::new("hyprctl").output() with no
timeout. Switched to bread_utils::proc::run_json, now that this crate
already depends on bread-utils (added in the config-migration commit).
load_doc/save_doc/atomic_write's bodies now delegate to
bread_utils::tomlcfg and bread_utils::atomic (path dependency for now, see
the TODO in Cargo.toml) instead of owning the temp-then-rename +
.bak-before-overwrite logic locally. Public function names/signatures in
config/mod.rs are unchanged, so none of the ~10 call sites across
ui/views/*.rs needed touching.
This is the other half of tonight's earlier BOS fix pass: that pass gave
breadhelp/src/config.rs its own byte-for-byte copy of this exact logic
(its own doc comment says "same discipline as bos-settings/src/config/
mod.rs::atomic_write") rather than sharing it — breadhelp's migration
follows in the next commit.
Builds clean; all 11 existing tests pass, including the atomic-write
backup/no-leftover-tmp-file test that now exercises the delegated code.
keybinds.rs was built only against a personal multi-layout binds.json schema
(globals/common/layouts). Loading the real BOS-shipped flat schema
(default_mods/bindings, with per-bind label/category/demo_cmd breadhelp
depends on) into that model and saving silently dropped the bindings key --
still valid JSON, so the Lua pcall failsafes never caught it.
- Add SchemaKind (Flat/MultiLayout/Unknown), detected from the file's
top-level keys at load time and pinned for the session so save() always
emits the same shape it read.
- Flat mode hides the layout-switching UI (there's nothing to switch) and
edits a single Bindings list; MultiLayout keeps today's UI unchanged.
- Bind's #[serde(flatten)] extra map already preserved label/category/
demo_cmd/options across a round trip; changed `mods` from Vec<String> to
Option<Vec<String>> so an explicit "mods": [] (media keys pinning "no
modifiers") isn't collapsed into "field omitted -> falls back to
default_mods" -- a real behavior-changing loss the old round trip had.
- Unknown schema refuses to save (visible status-label error) instead of
guessing a shape and overwriting the file.
- New round-trip tests in keybinds.rs load a real BOS-shaped binds.json
fixture through load -> save and assert the bindings key and every
per-bind extra field survive untouched; this is the regression test that
would have caught the original bug.
Also: config writes across the app (TOML via config::save_doc, and the
JSON views -- autostart, appearance/settings.json, hyprland.rs/
monitors.json, breadbar's CSS) now go through a shared
config::atomic_write: write to a temp file in the same directory, rename
over the target, and back up whatever was there first to <path>.bak. A
crash or disk-full mid-write can no longer leave a config truncated or
corrupted with no way back.
Wallpaper: file picker moved off the deprecated FileChooserDialog to
gtk4::FileDialog, plus a "Browse ~/Pictures/Backgrounds" thumbnail
grid (recursive scan, decoded previews) so picking a wallpaper isn't
guessing blind from filenames.
Fixed the app becoming unusable at Hyprland scale > 1.0: the sidebar's
~30-row nav list had no ScrolledWindow, so its unscrollable minimum
height exceeded the logical screen size once scale shrank it below
~1000px, clipping the window (Save buttons included) with no way to
reach the rest. Sidebar now scrolls independently; content column's
minimum width also dropped 760->560 for the same reason at scale 2.0.
Wi-Fi/saved-network password fields had no way to unhide what you
typed. Swapped every masked Entry (network.rs, breadcrumbs.rs, and the
shared password_row widget) to gtk4::PasswordEntry, which has a native
reveal-icon toggle.
Added a Keybinds editor for hypr/binds.json -- the one JSON Hyprland
config (globals/common/per-layout binds) with no GUI anywhere in the
app, despite Display/Appearance/Startup Apps all having one for their
own JSON config. Action-specific fields (command/direction/workspace/
options/...) round-trip through a compact inline-JSON column rather
than hardcoding a widget per action shape.
Adapter power switch, paired devices (connect/disconnect/forget), and
scan-for-new-devices with pairing -- BOS had bluez/bluez-utils packaged
and bread's own daemon already monitors Bluetooth events, but there was
no GUI to actually manage devices anywhere in the settings app.
Shells out to bluetoothctl's non-interactive mode, same "standard CLI,
no new dependency" choice as Network (nmcli) and Snapshots (snapper).
Pairing only covers "just works" SSP (most audio/HID devices) -- a
device requiring a PIN would need this app to register its own
bluetoothd agent, out of scope for now; it just fails with the reason
shown, same honest-failure pattern the Firewall fix added.
Firewall's fetch_status() discarded stderr/exit code on failure, so a
failed pkexec call (e.g. no polkit agent in the session) left the page
silently stuck on "Status not loaded" with no indication anything went
wrong. Now surfaces the actual failure reason, and attaches the
previously-invisible per-action log buffer to a visible TextView
(matching the Packages page's existing pattern).
Snapshots' "No snapshots yet" empty state was shown identically whether
snapper was genuinely unconfigured or the user just lacked permissions
(a "No permissions." stderr) -- now distinguishes the two.