bos/bos-settings/src/ui/views/breadpad.rs
Claude d5913da277 Fix all issues from code/UX review
ISO structural:
- Move post-install.sh → airootfs/etc/calamares/ (it was never in the squashfs)
- Create airootfs/etc/skel/.config/ with all dotfiles (deploy path now works)
- Add iso/pacman.conf with [breadway] custom repo stub for calamares + bakery
- Add Calamares branding component (bos/branding.desc + show.qml)
- Add missing unpackfs.conf and mount.conf modules
- Add live-session autostart: getty autologin → bash_profile → Hyprland → calamares
- Add polkit rule for wheel-group snapper rollback (pkexec path)
- Remove wlroots from packages (bundled with Hyprland); add bakery to package list
- Fix modules-search path in settings.conf

Dotfiles:
- Rename dotfiles/hyprland/ → dotfiles/hypr/ (Hyprland reads ~/.config/hypr/)
- Fix deprecated shadow options: drop_shadow/shadow_range → shadow { } block

bos-settings Rust:
- Replace glib::MainContext::channel (removed in glib 0.19) with async_channel
- Stream bakery update output line-by-line instead of buffering all at once
- Fix zombie processes: per-package update buttons now wait() in a thread
- Fix sidebar/stack mismatch at startup: select snapshots row to match default view
- Replace deprecated MessageDialog with AlertDialog (GTK 4.10+) throughout
- Use pkexec for snapper rollback so polkit handles privilege escalation
- Add confirmation dialog before delete snapshot (was missing, rollback had one)
- Add refresh button + repopulate after delete in snapshots view
- Add "Saved" / "Error: …" status label to every config view save button
- Add "Remove" buttons to breadbox contexts and breadcrumbs profiles
- Remove hardcoded model string from breadpad defaults
- Drop unused state mod; fix config_dir HOME fallback; fix zombie in editor launches

https://claude.ai/code/session_01WszGHvCmxgcyTwNSkfLF9P
2026-06-12 13:45:00 +00:00

122 lines
3.4 KiB
Rust

use gtk4::prelude::*;
use gtk4::{Box as GBox, Button, Entry, Label, Orientation, Switch};
use serde::{Deserialize, Serialize};
use std::cell::RefCell;
use std::rc::Rc;
use crate::config;
#[derive(Deserialize, Serialize, Clone)]
pub struct BreadpadConfig {
#[serde(default)]
pub model: String,
#[serde(default = "default_true")]
pub reminders: bool,
#[serde(default = "default_true")]
pub calendar: bool,
}
fn default_true() -> bool { true }
impl Default for BreadpadConfig {
fn default() -> Self {
Self { model: String::new(), reminders: true, calendar: true }
}
}
fn config_path() -> std::path::PathBuf {
config::config_dir().join("breadpad/breadpad.toml")
}
pub fn build() -> GBox {
let path = config_path();
let cfg: BreadpadConfig = config::load(&path).unwrap_or_default();
let cfg = Rc::new(RefCell::new(cfg));
let vbox = GBox::new(Orientation::Vertical, 12);
vbox.add_css_class("view-content");
let title = Label::new(Some("breadpad"));
title.add_css_class("title");
title.set_xalign(0.0);
vbox.append(&title);
// Model entry
let row = GBox::new(Orientation::Horizontal, 16);
let lbl = Label::new(Some("Model"));
lbl.set_hexpand(true);
lbl.set_xalign(0.0);
let model_entry = Entry::new();
model_entry.set_text(&cfg.borrow().model);
model_entry.set_placeholder_text(Some("e.g. claude-sonnet-4-6"));
{
let cfg = cfg.clone();
model_entry.connect_changed(move |e| {
cfg.borrow_mut().model = e.text().to_string();
});
}
row.append(&lbl);
row.append(&model_entry);
vbox.append(&row);
// Reminders
let row = GBox::new(Orientation::Horizontal, 16);
let lbl = Label::new(Some("Reminders"));
lbl.set_hexpand(true);
lbl.set_xalign(0.0);
let sw = Switch::new();
sw.set_active(cfg.borrow().reminders);
{
let cfg = cfg.clone();
sw.connect_active_notify(move |s| { cfg.borrow_mut().reminders = s.is_active(); });
}
row.append(&lbl);
row.append(&sw);
vbox.append(&row);
// Calendar
let row = GBox::new(Orientation::Horizontal, 16);
let lbl = Label::new(Some("Calendar integration"));
lbl.set_hexpand(true);
lbl.set_xalign(0.0);
let sw = Switch::new();
sw.set_active(cfg.borrow().calendar);
{
let cfg = cfg.clone();
sw.connect_active_notify(move |s| { cfg.borrow_mut().calendar = s.is_active(); });
}
row.append(&lbl);
row.append(&sw);
vbox.append(&row);
let btn_row = GBox::new(Orientation::Horizontal, 12);
btn_row.set_margin_top(16);
let save_btn = Button::with_label("Save");
let status_lbl = Label::new(None);
status_lbl.add_css_class("dim-label");
{
let cfg = cfg.clone();
let status_lbl = status_lbl.clone();
save_btn.connect_clicked(move |_| {
match config::save(&path, &*cfg.borrow()) {
Ok(()) => {
status_lbl.set_text("Saved");
let lbl = status_lbl.clone();
glib::timeout_add_seconds_local(3, move || {
lbl.set_text("");
glib::ControlFlow::Break
});
}
Err(e) => status_lbl.set_text(&format!("Error: {e}")),
}
});
}
btn_row.append(&save_btn);
btn_row.append(&status_lbl);
vbox.append(&btn_row);
vbox
}