Compare commits

...

3 commits
v0.2.6 ... main

Author SHA1 Message Date
Breadway
0d540c6bb9 Skip building the help window on silent every-login autostart
All checks were successful
dev release / build (push) Successful in 49s
Keep a warm GApplication with hold() so Super+/ still hits a resident
process, without paying for tabs that may never open. Ignore graphify-out.
2026-08-23 15:08:58 +08:00
Breadway
e27c497df8 clippy: fix dead-code and question-mark warnings
All checks were successful
dev release / build (push) Successful in 2m0s
- meta.rs/tour.rs/troubleshoot.rs: annotate content-schema fields that
  real TOML content already populates but no UI reads yet, instead of
  dropping them
- hyprland.rs: drop Monitor's x/y/width/height, genuinely unused
  anywhere and not part of any authored content schema
- tour/mod.rs: collapse a let-else into ? in the Option-returning
  on_tour_event closure

(cherry picked from commit 4497eca4661112f808d33ef477265d39e0e0a601)
2026-08-23 14:58:07 +08:00
Breadway
03517d9b0f Match help copy to live BOS binds and semantic search
Some checks failed
check / check (push) Failing after 55s
dev release / build (push) Successful in 2m10s
Keep Super+U / Super+Backspace as in iso binds.json; document Super+, for
settings. Describe breadsearch as meaning search, not filename letters.
2026-08-23 14:42:57 +08:00
9 changed files with 52 additions and 19 deletions

3
.gitignore vendored
View file

@ -40,3 +40,6 @@ logs/
.claude/ .claude/
# Local hygiene notes (not for commit) # Local hygiene notes (not for commit)
# graphify knowledge-graph output (local tool cache, not for commit)
graphify-out/

View file

@ -4,6 +4,6 @@ breadsearch indexes your files so you can find things instantly, without
digging through folders. digging through folders.
- Open it from BOS Settings, or run it directly. - Open it from BOS Settings, or run it directly.
- Type a few letters of a filename — results update as you type. - Describe what you are looking for in plain language — breadsearch ranks files by meaning, not by letters in a filename. Results update as you type.
- [Run]breadsearch|Open File Search[/Run] - [Run]breadsearch|Open File Search[/Run]

View file

@ -15,7 +15,8 @@ nothing here is mandatory either, click around if you'd rather.
- [Show Keybind]super + space[/Show Keybind] opens the launcher (breadbox) - [Show Keybind]super + space[/Show Keybind] opens the launcher (breadbox)
- [Show Keybind]super + e[/Show Keybind] opens files (nautilus) - [Show Keybind]super + e[/Show Keybind] opens files (nautilus)
- [Show Keybind]super + b[/Show Keybind] opens the browser - [Show Keybind]super + b[/Show Keybind] opens the browser
- [Show Keybind]super + u[/Show Keybind] opens notes - [Show Keybind]super + u[/Show Keybind] opens notes (breadpad)
- [Show Keybind]super + ,[/Show Keybind] opens BOS Settings
- [Show Keybind]super + l[/Show Keybind] locks the screen - [Show Keybind]super + l[/Show Keybind] locks the screen
## Screenshots ## Screenshots

View file

@ -12,14 +12,23 @@ pub enum Difficulty {
#[derive(serde::Deserialize, Clone, Default)] #[derive(serde::Deserialize, Clone, Default)]
pub struct GuideMeta { pub struct GuideMeta {
pub title: String, pub title: String,
/// Not read by any UI yet — reserved for a future difficulty badge.
/// Real content already sets it, so kept rather than dropped.
#[allow(dead_code)]
#[serde(default)] #[serde(default)]
pub difficulty: Difficulty, pub difficulty: Difficulty,
#[serde(default)] #[serde(default)]
pub tags: Vec<String>, pub tags: Vec<String>,
/// Guide dir-names in the same or another category. Unresolvable entries /// Guide dir-names in the same or another category. Unresolvable entries
/// are silently dropped when rendering "related guides" — never a crash. /// are silently dropped when rendering "related guides" — never a crash.
/// Not read by any UI yet — the "related guides" render path doesn't
/// exist. Real content already sets it, so kept rather than dropped.
#[allow(dead_code)]
#[serde(default)] #[serde(default)]
pub related: Vec<String>, pub related: Vec<String>,
/// Not read by any UI yet — reserved for a future "N min read" hint.
/// Real content already sets it, so kept rather than dropped.
#[allow(dead_code)]
#[serde(default)] #[serde(default)]
pub estimated_minutes: u32, pub estimated_minutes: u32,
/// Overrides the parent directory name as the guide's category, if set. /// Overrides the parent directory name as the guide's category, if set.

View file

@ -29,6 +29,10 @@ pub enum Success {
#[derive(serde::Deserialize, Clone)] #[derive(serde::Deserialize, Clone)]
pub struct Step { pub struct Step {
/// Not read by any Rust code yet — steps are matched by index, not id.
/// Real tour content already sets it (content authors use it to keep
/// track of steps), so kept rather than dropped.
#[allow(dead_code)]
pub id: String, pub id: String,
pub title: String, pub title: String,
pub body: String, pub body: String,

View file

@ -15,6 +15,11 @@ pub struct SymptomOption {
pub struct Fix { pub struct Fix {
pub description: String, pub description: String,
pub command: String, pub command: String,
/// Not read by `ui::troubleshoot_wizard` yet — the "Run fix" button
/// currently executes unconditionally regardless of this flag. Real
/// symptom content already sets it, so kept rather than dropped; the
/// wizard should gate on it before this is treated as safe.
#[allow(dead_code)]
#[serde(default)] #[serde(default)]
pub requires_confirm: bool, pub requires_confirm: bool,
} }

View file

@ -28,10 +28,6 @@ pub struct Client {
pub struct Monitor { pub struct Monitor {
pub id: i32, pub id: i32,
pub name: String, pub name: String,
pub x: i32,
pub y: i32,
pub width: i32,
pub height: i32,
pub focused: bool, pub focused: bool,
} }

View file

@ -110,7 +110,7 @@ pub fn self_heal() {
pub fn on_tour_event(id: &str) { pub fn on_tour_event(id: &str) {
let hit = STATE.with(|cell| { let hit = STATE.with(|cell| {
let mut borrow = cell.borrow_mut(); let mut borrow = cell.borrow_mut();
let Some(state) = borrow.as_mut() else { return None }; let state = borrow.as_mut()?;
// Only a *visibly* confirmed step blocks a re-fire. `confirmed` gets // Only a *visibly* confirmed step blocks a re-fire. `confirmed` gets
// set below, before `render_step` runs, so that render actually // set below, before `render_step` runs, so that render actually
// paints the "done" state instead of the stale pre-confirmation one // paints the "done" state instead of the stale pre-confirmation one

View file

@ -30,13 +30,33 @@ thread_local! {
/// over D-Bus to this primary instance by a second `breadhelp` launch — so /// over D-Bus to this primary instance by a second `breadhelp` launch — so
/// it must build the window at most once and reuse it thereafter. /// it must build the window at most once and reuse it thereafter.
pub fn present(app: &Application, action: Action) { pub fn present(app: &Application, action: Action) {
// Crash-recover keybinds even if this invocation never builds a window
// (silent every-login autostart).
tour::self_heal();
// Once onboarding is done, autostart only needs a warm process so
// Super+/ is instant. GTK/theme/font load is most of that cost and is
// paid by staying resident (~14 MiB PSS, no window). Building tabs
// adds ~2 MiB of dead weight for a session that may never open help.
let is_bare_silent_autostart = action.autostart
&& action.screenshot.is_none()
&& !action.force_onboard
&& action.tour_event.is_none()
&& action.suggest.is_none()
&& State::load().onboarding_completed();
if is_bare_silent_autostart {
// No window means nothing holds GApplication open. Forget the hold
// guard so the primary instance lives for the session; logout is
// what ends it. Without this, the next Super+/ would cold-start.
std::mem::forget(app.hold());
return;
}
HANDLE.with(|cell| { HANDLE.with(|cell| {
let mut just_built = false;
{ {
let mut cell_ref = cell.borrow_mut(); let mut cell_ref = cell.borrow_mut();
if cell_ref.is_none() { if cell_ref.is_none() {
*cell_ref = Some(build(app)); *cell_ref = Some(build(app));
just_built = true;
} }
} }
let cell_ref = cell.borrow(); let cell_ref = cell.borrow();
@ -62,19 +82,14 @@ pub fn present(app: &Application, action: Action) {
if let Some(s) = crate::services::breadd::resolve(id) { if let Some(s) = crate::services::breadd::resolve(id) {
handle.home.set_suggestion(Some(&s.text)); handle.home.set_suggestion(Some(&s.text));
} }
// Only focus the window the first time this process builds it // Background daemon event — never steal focus. A `--suggest`
// (i.e. breadhelp wasn't already running) — a background daemon // can now be the first invocation of the session because
// event shouldn't steal focus from whatever the user is doing. // silent autostart no longer builds a window.
if just_built {
show_window(&handle.window, action.autostart);
}
return; return;
} }
// Every-login autostart builds the window (so the app is ready to // Bare silent autostart (onboarding done) already returned above.
// respond to SUPER+/ instantly) but only shows UI on a genuine // Autostart that reaches here is a genuine first run.
// first run — never on later logins. First run is now the in-window
// day-zero wizard; the live tour starts when that finishes.
let onboarded = State::load().onboarding_completed(); let onboarded = State::load().onboarding_completed();
if action.autostart && onboarded { if action.autostart && onboarded {
return; return;