From df42aba1d32ff85b5d20dbb7f14c22905aacf85b Mon Sep 17 00:00:00 2001 From: Breadway Date: Fri, 19 Jun 2026 08:38:40 +0800 Subject: [PATCH 1/9] CI: use /tmp for ecosystem clone, avoid permissions conflict --- .github/workflows/release.yml | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8df4368..e492838 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,7 +9,7 @@ permissions: env: DL_DIR: /srv/breadway-dl - ECOSYSTEM_DIR: /home/breadway/Projects/bread-ecosystem + ECOSYSTEM_DIR: /tmp/bread-ecosystem-ci jobs: build: @@ -40,12 +40,8 @@ jobs: - name: ensure bread-ecosystem run: | - if [[ -d "${ECOSYSTEM_DIR}/.git" ]]; then - git -C "${ECOSYSTEM_DIR}" pull --ff-only - else - mkdir -p "$(dirname "${ECOSYSTEM_DIR}")" - git clone https://github.com/Breadway/bread-ecosystem.git "${ECOSYSTEM_DIR}" - fi + rm -rf "${ECOSYSTEM_DIR}" + git clone https://github.com/Breadway/bread-ecosystem.git "${ECOSYSTEM_DIR}" - name: regenerate index.json run: bash "${ECOSYSTEM_DIR}/scripts/gen-index.sh" From b828f8ec79879871fd87463c5476cea80b67bdb3 Mon Sep 17 00:00:00 2001 From: Breadway Date: Fri, 3 Jul 2026 14:10:07 +0800 Subject: [PATCH 2/9] CI: migrate release workflow from GitHub Actions to Forgejo Actions GitHub Actions self-hosted runners need per-repo registration on a personal account; Forgejo Actions' runner already serves every repo with zero setup. Moves release publishing there (dl.breadway.dev stays the primary bakery target; GitHub release upload is kept as the fallback via an explicit token, since Forgejo Actions has no ambient GITHUB_TOKEN) and adds a mirror workflow to keep GitHub in sync automatically. --- .forgejo/workflows/mirror.yml | 2 -- .forgejo/workflows/release.yml | 58 +++++++++++++++++++++++++++++++ .github/workflows/release.yml | 62 ---------------------------------- README.md | 9 +++-- 4 files changed, 64 insertions(+), 67 deletions(-) create mode 100644 .forgejo/workflows/release.yml delete mode 100644 .github/workflows/release.yml diff --git a/.forgejo/workflows/mirror.yml b/.forgejo/workflows/mirror.yml index e91c97e..01b9bef 100644 --- a/.forgejo/workflows/mirror.yml +++ b/.forgejo/workflows/mirror.yml @@ -14,8 +14,6 @@ jobs: set -euo pipefail git clone --mirror "https://git.breadway.dev/${GITHUB_REPOSITORY}.git" repo.git cd repo.git - # Mirror only branches and tags (not refs/pull/*, which GitHub rejects); - # --prune deletes GitHub refs that no longer exist on Forgejo. git push --prune \ "https://x-access-token:${{ secrets.MIRROR_TOKEN }}@github.com/Breadway/breadpad.git" \ '+refs/heads/*:refs/heads/*' '+refs/tags/*:refs/tags/*' diff --git a/.forgejo/workflows/release.yml b/.forgejo/workflows/release.yml new file mode 100644 index 0000000..c878cf4 --- /dev/null +++ b/.forgejo/workflows/release.yml @@ -0,0 +1,58 @@ +name: release + +on: + push: + tags: ["v*"] + +jobs: + build: + runs-on: [self-hosted, hestia] + steps: + - name: checkout + run: | + set -euo pipefail + rm -rf src && mkdir src + git clone --branch "${GITHUB_REF_NAME}" --depth 1 \ + "https://git.breadway.dev/${GITHUB_REPOSITORY}.git" src + + - name: build + run: cd src && cargo build --release --locked + + - name: prepare artifacts + run: | + set -euo pipefail + VERSION="${GITHUB_REF_NAME#v}" + PKG_DIR="/srv/breadway-dl/breadpad/${VERSION}" + mkdir -p "${PKG_DIR}" + for bin in breadpad breadman; do + cp "src/target/release/${bin}" "${PKG_DIR}/${bin}-x86_64" + strip "${PKG_DIR}/${bin}-x86_64" + sha256sum "${PKG_DIR}/${bin}-x86_64" | awk '{print $1}' \ + > "${PKG_DIR}/${bin}-x86_64.sha256" + done + cp src/breadpad.example.toml "${PKG_DIR}/" + cp src/bakery.toml "${PKG_DIR}/bakery.toml" + ln -sfn "${VERSION}" "/srv/breadway-dl/breadpad/latest" + + - name: regenerate index.json + run: | + set -euo pipefail + rm -rf /tmp/bread-ecosystem-ci + git clone https://git.breadway.dev/Breadway/bread-ecosystem.git /tmp/bread-ecosystem-ci + bash /tmp/bread-ecosystem-ci/scripts/gen-index.sh + + - name: upload to GitHub Release + env: + GH_TOKEN: ${{ secrets.GH_RELEASE_TOKEN }} + run: | + set -euo pipefail + VERSION="${GITHUB_REF_NAME#v}" + PKG_DIR="/srv/breadway-dl/breadpad/${VERSION}" + gh release create "${GITHUB_REF_NAME}" --repo Breadway/breadpad \ + --title "breadpad v${VERSION}" --generate-notes 2>/dev/null || true + gh release upload "${GITHUB_REF_NAME}" --repo Breadway/breadpad \ + "${PKG_DIR}/breadpad-x86_64" \ + "${PKG_DIR}/breadman-x86_64" \ + "${PKG_DIR}/breadpad-x86_64.sha256" \ + "${PKG_DIR}/breadman-x86_64.sha256" \ + --clobber diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index e492838..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,62 +0,0 @@ -name: release - -on: - push: - tags: ["v*"] - -permissions: - contents: write - -env: - DL_DIR: /srv/breadway-dl - ECOSYSTEM_DIR: /tmp/bread-ecosystem-ci - -jobs: - build: - runs-on: [self-hosted, hestia] - steps: - - uses: actions/checkout@v4 - - - name: install build deps - run: sudo apt-get install -y libgtk-4-dev libdbus-1-dev pkg-config 2>/dev/null || true - - - name: build - run: cargo build --release --locked - - - name: prepare artifacts - run: | - VERSION="${GITHUB_REF_NAME#v}" - PKG_DIR="${DL_DIR}/breadpad/${VERSION}" - mkdir -p "${PKG_DIR}" - for bin in breadpad breadman; do - cp "target/release/${bin}" "${PKG_DIR}/${bin}-x86_64" - strip "${PKG_DIR}/${bin}-x86_64" - sha256sum "${PKG_DIR}/${bin}-x86_64" | awk '{print $1}' \ - > "${PKG_DIR}/${bin}-x86_64.sha256" - done - cp breadpad.example.toml "${PKG_DIR}/" - cp bakery.toml "${PKG_DIR}/bakery.toml" - ln -sfn "${VERSION}" "${DL_DIR}/breadpad/latest" - - - name: ensure bread-ecosystem - run: | - rm -rf "${ECOSYSTEM_DIR}" - git clone https://github.com/Breadway/bread-ecosystem.git "${ECOSYSTEM_DIR}" - - - name: regenerate index.json - run: bash "${ECOSYSTEM_DIR}/scripts/gen-index.sh" - - - name: upload to GitHub Release - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - VERSION="${GITHUB_REF_NAME#v}" - PKG_DIR="${DL_DIR}/breadpad/${VERSION}" - gh release create "${GITHUB_REF_NAME}" \ - --title "breadpad v${VERSION}" --generate-notes 2>/dev/null || true - gh release upload "${GITHUB_REF_NAME}" \ - "${PKG_DIR}/breadpad-x86_64" \ - "${PKG_DIR}/breadman-x86_64" \ - "${PKG_DIR}/breadpad-x86_64.sha256" \ - "${PKG_DIR}/breadman-x86_64.sha256" \ - --clobber diff --git a/README.md b/README.md index b39ea3d..6d41ee8 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ User-defined tags can be added freely on top of the built-in types. - **One-off reminders** — natural language time ("at 7pm", "in 30 minutes", "tomorrow morning") parsed at classification time; scheduled via a systemd user timer - **Recurring reminders** — "every Sunday at 9pm", "every weekday morning" — stored as an iCal-compatible RRULE and re-scheduled on each trigger -- **Snooze** — notification popup includes snooze actions: 15 min / 1 hour / tomorrow morning / custom; snoozing reschedules the timer without touching the original note +- **Snooze** — notification popup includes snooze actions drawn from `snooze_options` (default: 15 min / 1 hour / tomorrow morning); snoozing reschedules the timer without touching the original note - **Missed reminders** — if the system was off or suspended at the scheduled time, the reminder fires on next login ### Viewer (`breadman`) @@ -139,7 +139,7 @@ breadpad model-info # shows active EP and model path - systemd user session (for timer-backed reminders) - Rust 1.80+ - **Tier 2 (ONNX classifier):** An external `libonnxruntime.so`. Set `model.ort_dylib_path` in `breadpad.toml`, or set `ORT_DYLIB_PATH` in your environment. Without a library, Tier 2 is disabled; Tier 1 + 3 still work. -- **Tier 3 only (optional):** [Ollama](https://ollama.com) running locally with your chosen model pulled (`ollama pull llama3.2:3b`). Tier 3 is silently skipped if Ollama is not running. +- **Tier 3 only (optional):** [Ollama](https://ollama.com) running locally with your chosen model pulled (e.g. `ollama pull fastflowlm`). Tier 3 is silently skipped if Ollama is not running. --- @@ -183,7 +183,7 @@ ort_dylib_path = "" # optional: explicit path to libonnxruntime.so; [model.ollama] endpoint = "http://localhost:11434" -model = "llama3.2:3b" # any model you have pulled in Ollama +model = "fastflowlm" # any model you have pulled in Ollama confidence_threshold = 0.6 # Tier 2 scores below this trigger Tier 3 enabled = true # set false to never call Ollama @@ -243,6 +243,9 @@ breadpad --no-classify # Show model and storage status breadpad --status + +# Print expected model paths (does not download automatically) +breadpad download-model ``` Hyprland keybind: From 6fea1af54492e86264b562604465bb3a5f6abb20 Mon Sep 17 00:00:00 2001 From: Breadway Date: Sat, 4 Jul 2026 10:53:52 +0800 Subject: [PATCH 3/9] Release 0.4.0: GUI design pass + td:/rem:/note:/q: prefix grammar Unifies the "selected" visual language across breadman's sidebar and breadpad's type chips (ghost default, solid accent when active), gives note cards a real surface instead of just a left border, caps the note list width so type chips and action buttons sit next to the title they belong to, and swaps the sidebar's full-color emoji for theme-safe monochrome glyphs plus per-type colored dots. breadman also gets live per-row sidebar counts, a note-count footer, relative timestamps with absolute-time tooltips, and a de-emphasized Errors row. breadpad's popup gets an explicit short-prefix grammar (td:, rem:, idea:, note:, q:) that live-highlights the matching chip as you type, a keyboard-shortcut hint row, and a confirm button that's visually separated from the chip row instead of reading as a sixth pill. --- Cargo.lock | 8 +- Cargo.toml | 2 +- breadman/src/main.rs | 202 +++++++++++++++++++++++++++++----- breadpad-shared/src/parser.rs | 113 ++++++++++++++++++- breadpad-shared/src/theme.rs | 95 +++++++++++++--- breadpad/src/main.rs | 35 +++++- 6 files changed, 404 insertions(+), 51 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 756f0c3..4936958 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -315,7 +315,7 @@ dependencies = [ [[package]] name = "breadman" -version = "0.3.4" +version = "0.4.0" dependencies = [ "anyhow", "breadpad-shared", @@ -332,7 +332,7 @@ dependencies = [ [[package]] name = "breadpad" -version = "0.3.4" +version = "0.4.0" dependencies = [ "anyhow", "breadpad-shared", @@ -351,7 +351,7 @@ dependencies = [ [[package]] name = "breadpad-shared" -version = "0.3.4" +version = "0.4.0" dependencies = [ "anyhow", "bread-theme", @@ -377,7 +377,7 @@ dependencies = [ [[package]] name = "breadpad-test" -version = "0.3.4" +version = "0.4.0" dependencies = [ "anyhow", "breadpad-shared", diff --git a/Cargo.toml b/Cargo.toml index 4092bed..1de5996 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ members = [ resolver = "2" [workspace.package] -version = "0.3.4" +version = "0.4.0" edition = "2021" license = "MIT" authors = ["Breadway"] diff --git a/breadman/src/main.rs b/breadman/src/main.rs index d46557a..e132a80 100644 --- a/breadman/src/main.rs +++ b/breadman/src/main.rs @@ -6,7 +6,7 @@ use breadpad_shared::{ store::Store, types::{Note, NoteType, RecurrenceRule}, }; -use chrono::Local; +use chrono::{DateTime, Local, Utc}; use gtk4::{glib, prelude::*}; use std::cell::RefCell; use std::rc::Rc; @@ -69,10 +69,21 @@ struct AppState { errors: Rc, String)>>>, active_view: Rc>, stack: gtk4::Stack, + /// Sidebar row id ("all", "upcoming", "archive", or a note type name) -> + /// its count `Label`, so counts can be refreshed in place after every + /// `rebuild_stack` without rebuilding the sidebar itself. + sidebar_counts: Rc>>, + status_label: gtk4::Label, } impl AppState { - fn new(store: Arc, notes: Vec, cfg: Config, stack: gtk4::Stack) -> Self { + fn new( + store: Arc, + notes: Vec, + cfg: Config, + stack: gtk4::Stack, + status_label: gtk4::Label, + ) -> Self { AppState { store, notes: Rc::new(RefCell::new(notes)), @@ -80,6 +91,8 @@ impl AppState { errors: Rc::new(RefCell::new(Vec::new())), active_view: Rc::new(RefCell::new("all".to_string())), stack, + sidebar_counts: Rc::new(RefCell::new(Vec::new())), + status_label, } } @@ -192,6 +205,40 @@ fn rebuild_stack(state: &AppState) { // Errors let errors_view = views::errors::build(&errors); state.stack.add_named(&errors_view, Some("errors")); + + update_counts_and_status(state); +} + +/// Refreshes the sidebar's per-row counts and the content pane's footer +/// note count from the current `state.notes`. Cheap enough to call on every +/// rebuild — five type counts plus all/upcoming/archive over a note list +/// that in practice stays small. +fn update_counts_and_status(state: &AppState) { + let notes = state.notes.borrow(); + let total = notes.iter().filter(|n| !n.done).count(); + state + .status_label + .set_label(&format!("{total} note{}", if total == 1 { "" } else { "s" })); + + for (key, label) in state.sidebar_counts.borrow().iter() { + let n = match key.as_str() { + "all" => total, + "upcoming" => notes + .iter() + .filter(|n| { + !n.done + && matches!(n.note_type, NoteType::Reminder | NoteType::Todo) + && n.effective_time().is_some() + }) + .count(), + "archive" => notes.iter().filter(|n| n.done).count(), + other => { + let nt = NoteType::from_str(other); + notes.iter().filter(|n| !n.done && n.note_type == nt).count() + } + }; + label.set_label(&n.to_string()); + } } // ── main ───────────────────────────────────────────────────────────────────── @@ -331,7 +378,12 @@ fn build_app_window( )); row }; - let make_item = |id: &str, icon: &str, label: &str| { + // Returns the row plus its count `Label` when `counted` is set — callers + // collect these so counts can be kept live from `AppState.sidebar_counts` + // (monochrome geometric icons + a colored dot per type instead of the + // old full-color emoji, which always render in fixed colors no matter + // what the pywal palette says). + let make_item = |id: &str, icon: &str, icon_class: Option<&str>, label: &str, counted: bool| { let row = gtk4::ListBoxRow::builder() .css_classes(["sidebar-row"]) .build(); @@ -340,13 +392,15 @@ fn build_app_window( .orientation(gtk4::Orientation::Horizontal) .spacing(10) .build(); - hbox.append( - >k4::Label::builder() - .label(icon) - .width_chars(2) - .xalign(0.5) - .build(), - ); + let icon_label = gtk4::Label::builder() + .label(icon) + .width_chars(2) + .xalign(0.5) + .build(); + if let Some(class) = icon_class { + icon_label.add_css_class(class); + } + hbox.append(&icon_label); hbox.append( >k4::Label::builder() .label(label) @@ -354,23 +408,62 @@ fn build_app_window( .hexpand(true) .build(), ); + let count_label = if counted { + let l = gtk4::Label::builder() + .label("") + .css_classes(["sidebar-count"]) + .build(); + hbox.append(&l); + Some(l) + } else { + None + }; row.set_child(Some(&hbox)); - row + (row, count_label) }; + let mut sidebar_counts: Vec<(String, gtk4::Label)> = Vec::new(); + sidebar_list.append(&make_section("VIEWS")); - sidebar_list.append(&make_item("all", "📋", "All")); - sidebar_list.append(&make_item("upcoming", "📅", "Upcoming")); + { + let (row, count) = make_item("all", "▦", None, "All", true); + sidebar_counts.push(("all".into(), count.unwrap())); + sidebar_list.append(&row); + } + { + let (row, count) = make_item("upcoming", "◷", None, "Upcoming", true); + sidebar_counts.push(("upcoming".into(), count.unwrap())); + sidebar_list.append(&row); + } sidebar_list.append(&make_section("TYPES")); - sidebar_list.append(&make_item("todo", "✅", "Todo")); - sidebar_list.append(&make_item("reminder", "🔔", "Reminder")); - sidebar_list.append(&make_item("idea", "💡", "Idea")); - sidebar_list.append(&make_item("note", "📝", "Note")); - sidebar_list.append(&make_item("question", "❓", "Question")); + for (id, icon_class, label) in [ + ("todo", "icon-todo", "Todo"), + ("reminder", "icon-reminder", "Reminder"), + ("idea", "icon-idea", "Idea"), + ("note", "icon-note", "Note"), + ("question", "icon-question", "Question"), + ] { + let (row, count) = make_item(id, "●", Some(icon_class), label, true); + sidebar_counts.push((id.to_string(), count.unwrap())); + sidebar_list.append(&row); + } sidebar_list.append(&make_section("MORE")); - sidebar_list.append(&make_item("archive", "📦", "Archive")); - sidebar_list.append(&make_item("settings", "⚙", "Settings")); - sidebar_list.append(&make_item("errors", "⚠", "Errors")); + { + let (row, count) = make_item("archive", "▢", None, "Archive", true); + sidebar_counts.push(("archive".into(), count.unwrap())); + sidebar_list.append(&row); + } + { + let (row, _) = make_item("settings", "⚙", None, "Settings", false); + sidebar_list.append(&row); + } + { + // De-emphasized: session-only debug info, not part of the daily + // triage flow the rest of the sidebar serves. + let (row, _) = make_item("errors", "⚠", None, "Errors", false); + row.add_css_class("sidebar-row-minor"); + sidebar_list.append(&row); + } sidebar_vbox.append(&sidebar_list); // ── Content area ────────────────────────────────────────────── @@ -390,8 +483,19 @@ fn build_app_window( let stack = gtk4::Stack::builder().hexpand(true).vexpand(true).build(); + let status_label = gtk4::Label::builder() + .label("0 notes") + .css_classes(["dim-label"]) + .xalign(0.0) + .margin_start(12) + .margin_end(12) + .margin_top(6) + .margin_bottom(8) + .build(); + content_vbox.append(&search_entry); content_vbox.append(&stack); + content_vbox.append(&status_label); hbox.append(&sidebar_vbox); hbox.append(>k4::Separator::builder() @@ -401,7 +505,8 @@ fn build_app_window( window.set_child(Some(&hbox)); // ── AppState ────────────────────────────────────────────────── - let state = AppState::new(store, notes, cfg, stack.clone()); + let state = AppState::new(store, notes, cfg, stack.clone(), status_label.clone()); + state.sidebar_counts.replace(sidebar_counts); // Initial build rebuild_stack(&state); @@ -478,6 +583,9 @@ fn build_note_list(notes: &[Note], state: AppState) -> gtk4::ScrolledWindow { .vexpand(true) .build(); + // Capped and centered so cards don't stretch edge-to-edge on a wide + // window — full-width rows left the type chip and action buttons + // hundreds of pixels from the title they belong to. let list = gtk4::Box::builder() .orientation(gtk4::Orientation::Vertical) .spacing(8) @@ -485,18 +593,33 @@ fn build_note_list(notes: &[Note], state: AppState) -> gtk4::ScrolledWindow { .margin_bottom(12) .margin_start(12) .margin_end(12) + .width_request(700) + .halign(gtk4::Align::Center) .build(); let mut sorted: Vec = notes.iter().filter(|n| !n.done).cloned().collect(); sorted.sort_by(|a, b| b.created.cmp(&a.created)); if sorted.is_empty() { - list.append( + let empty = gtk4::Box::builder() + .orientation(gtk4::Orientation::Vertical) + .spacing(6) + .halign(gtk4::Align::Center) + .margin_top(64) + .build(); + empty.append( >k4::Label::builder() - .label("No notes here yet.") - .margin_top(32) + .label("No notes here yet") + .css_classes(["note-title"]) .build(), ); + empty.append( + >k4::Label::builder() + .label("Capture something with breadpad and it'll show up here.") + .css_classes(["dim-label"]) + .build(), + ); + list.append(&empty); } else { for note in &sorted { list.append(&build_note_card(note, state.clone())); @@ -507,6 +630,26 @@ fn build_note_list(notes: &[Note], state: AppState) -> gtk4::ScrolledWindow { scroll } +/// Short relative form for a recency-ordered list ("2h ago"); the exact +/// absolute timestamp is still available via tooltip. Falls back to a plain +/// date once a note is more than a week old, where "Nd ago" stops being +/// useful at a glance. +fn humanize_relative(dt: DateTime) -> String { + let secs = Utc::now().signed_duration_since(dt).num_seconds().max(0); + if secs < 60 { + "just now".to_string() + } else if secs < 3600 { + format!("{}m ago", secs / 60) + } else if secs < 86_400 { + format!("{}h ago", secs / 3600) + } else if secs < 86_400 * 7 { + format!("{}d ago", secs / 86_400) + } else { + let local: DateTime = dt.into(); + local.format("%b %d").to_string() + } +} + fn build_note_card(note: &Note, state: AppState) -> gtk4::Box { let card = gtk4::Box::builder() .orientation(gtk4::Orientation::Vertical) @@ -530,6 +673,7 @@ fn build_note_card(note: &Note, state: AppState) -> gtk4::Box { .hexpand(true) .xalign(0.0) .wrap(true) + .css_classes(["note-title"]) .build(); let type_chip = gtk4::Label::builder() @@ -546,14 +690,15 @@ fn build_note_card(note: &Note, state: AppState) -> gtk4::Box { .spacing(8) .build(); - let created_str = { + let created_abs = { let local: chrono::DateTime = note.created.into(); local.format("%b %d %H:%M").to_string() }; let meta_label = gtk4::Label::builder() - .label(&created_str) + .label(&humanize_relative(note.created)) .css_classes(["dim-label"]) .xalign(0.0) + .tooltip_text(&created_abs) .build(); // Date first, then chips @@ -561,8 +706,9 @@ fn build_note_card(note: &Note, state: AppState) -> gtk4::Box { if let Some(ws) = ¬e.workspace { bottom_row.append( >k4::Label::builder() - .label(&format!("ws:{}", ws)) + .label(&format!("ws {}", ws)) .css_classes(["type-chip"]) + .tooltip_text(&format!("Workspace {}", ws)) .build(), ); } diff --git a/breadpad-shared/src/parser.rs b/breadpad-shared/src/parser.rs index cc03b5e..e398e73 100644 --- a/breadpad-shared/src/parser.rs +++ b/breadpad-shared/src/parser.rs @@ -85,6 +85,43 @@ fn rrule_weekday(wd: Weekday) -> &'static str { } } +/// Explicit type prefixes, checked before any lexical heuristics. Short forms +/// exist so the capture popup can be driven without reaching for the mouse — +/// see `breadpad_shared::parser::detect_prefix_type`, which the popup's entry +/// uses to live-highlight the matching chip as the user types. +const TYPE_PREFIXES: &[(&str, NoteType)] = &[ + ("td:", NoteType::Todo), + ("rem:", NoteType::Reminder), + ("idea:", NoteType::Idea), + ("note:", NoteType::Note), + ("q:", NoteType::Question), +]; + +/// If `text` starts with one of [`TYPE_PREFIXES`] (case-insensitive), returns +/// the type it forces. Used both to classify at save time and to live-drive +/// the popup's chip highlighting as the user types. +pub fn detect_prefix_type(text: &str) -> Option { + let lower = text.trim_start().to_lowercase(); + TYPE_PREFIXES + .iter() + .find(|(prefix, _)| lower.starts_with(prefix)) + .map(|(_, nt)| nt.clone()) +} + +/// Strips a leading explicit type prefix (if any), returning the forced type +/// and the remaining text with the prefix and any following whitespace removed. +fn strip_explicit_prefix(text: &str) -> (Option, String) { + let trimmed = text.trim_start(); + let lower = trimmed.to_lowercase(); + for (prefix, nt) in TYPE_PREFIXES { + if lower.starts_with(prefix) { + let rest = trimmed[prefix.len()..].trim_start().to_string(); + return (Some(nt.clone()), rest); + } + } + (None, text.to_string()) +} + fn next_occurrence_of_weekday(wd: Weekday, time: NaiveTime) -> DateTime { let local = Local::now(); let days_ahead = (wd.num_days_from_monday() as i64 @@ -105,6 +142,8 @@ fn next_occurrence_of_weekday(wd: Weekday, time: NaiveTime) -> DateTime { } pub fn parse_rule_based(text: &str, default_morning: &str) -> ClassificationResult { + let (forced_type, text_owned) = strip_explicit_prefix(text); + let text: &str = &text_owned; let p = patterns(); let morning_time: NaiveTime = default_morning .split(':') @@ -290,8 +329,9 @@ pub fn parse_rule_based(text: &str, default_morning: &str) -> ClassificationResu .to_string(); } - // Infer note type - let note_type = infer_type(text, extracted_time.is_some(), rrule.is_some()); + // Infer note type — an explicit prefix (`td:`, `rem:`, …) always wins. + let note_type = + forced_type.clone().unwrap_or_else(|| infer_type(text, extracted_time.is_some(), rrule.is_some())); // Trim artifacts cleaned = cleaned @@ -303,7 +343,9 @@ pub fn parse_rule_based(text: &str, default_morning: &str) -> ClassificationResu // Calibrated confidence: high when structural signals drove the decision, // low when we fell back to "note" with no positive evidence. - let confidence = if rrule.is_some() || extracted_time.is_some() { + let confidence = if forced_type.is_some() { + 0.99 // explicit prefix — unambiguous + } else if rrule.is_some() || extracted_time.is_some() { 0.95 // time/recurrence extraction is deterministic } else { match ¬e_type { @@ -410,6 +452,70 @@ mod tests { assert_eq!(p("idea: reactive state module in Lua").note_type, NoteType::Idea); } + #[test] + fn idea_prefix_stripped_from_body() { + let r = p("idea: reactive state module in Lua"); + assert_eq!(r.body, "reactive state module in Lua"); + } + + // ---- Explicit short prefixes (td:, rem:, note:, q:) ---- + + #[test] + fn td_prefix_is_todo() { + let r = p("td: buy milk"); + assert_eq!(r.note_type, NoteType::Todo); + assert_eq!(r.body, "buy milk"); + } + + #[test] + fn rem_prefix_is_reminder() { + let r = p("rem: water the plants"); + assert_eq!(r.note_type, NoteType::Reminder); + assert_eq!(r.body, "water the plants"); + } + + #[test] + fn note_prefix_is_note() { + // Without the prefix this would classify as Todo ("check ..."). + let r = p("note: check engine light has been on for a week"); + assert_eq!(r.note_type, NoteType::Note); + assert_eq!(r.body, "check engine light has been on for a week"); + } + + #[test] + fn q_prefix_is_question() { + // Without the prefix this has no strong signal and would fall to Note. + let r = p("q: ONNX rocm vs cpu perf"); + assert_eq!(r.note_type, NoteType::Question); + assert_eq!(r.body, "ONNX rocm vs cpu perf"); + } + + #[test] + fn explicit_prefix_confidence_is_high() { + assert_eq!(p("td: buy milk").confidence, 0.99); + } + + #[test] + fn explicit_prefix_is_case_insensitive() { + assert_eq!(p("TD: buy milk").note_type, NoteType::Todo); + assert_eq!(p("Rem: standup").note_type, NoteType::Reminder); + } + + #[test] + fn explicit_prefix_overrides_time_extraction_type() { + // "at 7pm" alone would infer Reminder; an explicit td: prefix wins. + let r = p("td: pack bag at 7pm"); + assert_eq!(r.note_type, NoteType::Todo); + assert!(r.time.is_some(), "time should still be extracted"); + } + + #[test] + fn detect_prefix_type_matches_parse() { + assert_eq!(detect_prefix_type("td: buy milk"), Some(NoteType::Todo)); + assert_eq!(detect_prefix_type("rem: call mum"), Some(NoteType::Reminder)); + assert_eq!(detect_prefix_type("no prefix here"), None); + } + #[test] fn idea_maybe() { assert_eq!(p("maybe we could cache the ONNX model").note_type, NoteType::Idea); @@ -884,7 +990,6 @@ fn infer_type(text: &str, has_time: bool, has_rrule: bool) -> NoteType { return NoteType::Todo; } if lower.starts_with("what if ") - || lower.starts_with("idea:") || lower.contains("could ") || lower.contains("maybe ") || lower.starts_with("should we ") diff --git a/breadpad-shared/src/theme.rs b/breadpad-shared/src/theme.rs index cb1139a..8cc2512 100644 --- a/breadpad-shared/src/theme.rs +++ b/breadpad-shared/src/theme.rs @@ -43,9 +43,14 @@ window { border-radius: 8px; } border-color: @teal; } +/* Shared "selected/active" language: a ghost/ outline default state that + stays quiet, and a solid, high-contrast accent fill for whatever is + currently selected — used identically by .type-chip.active and + .sidebar-row:selected so the two windows read as one system. */ .type-chip { - background: @overlay; - color: @on-overlay; + background: transparent; + color: alpha(@fg, 0.6); + border: 1px solid alpha(@fg, 0.18); border-radius: 999px; padding: 4px 12px; font-size: 12px; @@ -55,27 +60,69 @@ window { border-radius: 8px; } .type-chip.active { background: @blue; color: @on-accent; + border-color: @blue; + font-weight: 600; } +/* Per-type tint so info badges (note cards, workspace/recur tags) stay + scannable at a glance without the old full-color emoji. */ +.note-card-todo .type-chip { color: @green; border-color: alpha(@green, 0.4); } +.note-card-reminder .type-chip { color: @yellow; border-color: alpha(@yellow, 0.4); } +.note-card-idea .type-chip { color: @pink; border-color: alpha(@pink, 0.4); } +.note-card-question .type-chip { color: @teal; border-color: alpha(@teal, 0.4); } +.note-card-note .type-chip { color: @blue; border-color: alpha(@blue, 0.4); } + .confirm-button { background: @blue; color: @on-accent; border: none; border-radius: 8px; - padding: 8px 16px; + padding: 10px 22px; + min-height: 20px; + font-size: 15px; font-weight: bold; } +.confirm-button:hover { background: shade(@blue, 1.1); } + +/* Separates the primary action from the chip row it sits beside so it + doesn't read as just another pill. */ +.confirm-wrap { + border-left: 1px solid alpha(@fg, 0.12); + padding-left: 12px; + margin-left: 4px; +} + +.prefix-hint { + color: alpha(@fg, 0.4); + font-size: 10px; + letter-spacing: 0.3px; +} + .note-card { - background: shade(@bg, 1.1); - border-radius: 8px; + background: shade(@bg, 1.12); + border: 1px solid alpha(@fg, 0.07); + border-radius: 10px; padding: 12px; - margin: 8px; + margin: 6px 0; border-left: 3px solid @blue; } .note-card:hover { - background: shade(@bg, 1.2); + background: shade(@bg, 1.22); + border-color: alpha(@fg, 0.12); +} + +.note-title { + font-weight: 600; +} + +.note-card .action-btn { + opacity: 0.45; +} + +.note-card:hover .action-btn { + opacity: 1; } .search-entry { @@ -83,7 +130,8 @@ window { border-radius: 8px; } color: @fg; border: 1px solid @overlay; border-radius: 6px; - padding: 8px 12px; + padding: 5px 10px; + font-size: 13px; } .search-entry:focus { @@ -104,7 +152,27 @@ window { border-radius: 8px; } .sidebar-row:selected { background: @blue; color: @on-accent; - font-weight: 500; + font-weight: 600; +} + +.sidebar-row-minor { + opacity: 0.5; + font-size: 12px; +} + +.sidebar-count { + color: alpha(@fg, 0.45); + font-size: 11px; +} + +.icon-todo { color: @green; } +.icon-reminder { color: @yellow; } +.icon-idea { color: @pink; } +.icon-note { color: @blue; } +.icon-question { color: @teal; } + +.sidebar-row:selected .sidebar-count { + color: alpha(@on-accent, 0.75); } .sidebar-section-label { @@ -119,14 +187,15 @@ window { border-radius: 8px; } background: transparent; border: none; border-radius: 6px; - padding: 2px 7px; - min-width: 28px; - min-height: 28px; - font-size: 14px; + padding: 3px 8px; + min-width: 32px; + min-height: 32px; + font-size: 16px; } .action-btn:hover { background: shade(@bg, 1.3); + opacity: 1; } .done-btn { color: @green; } diff --git a/breadpad/src/main.rs b/breadpad/src/main.rs index aa924d0..033a114 100644 --- a/breadpad/src/main.rs +++ b/breadpad/src/main.rs @@ -636,11 +636,43 @@ fn build_window( } } + // Live prefix grammar: typing "td: ", "rem: ", "idea: ", "note: ", or + // "q: " at the start of the entry drives the chip selection without + // ever touching the mouse — the chips become feedback for what you + // typed rather than the only way to pick a type. + { + let selected_type_clone = selected_type.clone(); + let chips_clone: Vec<(gtk4::Button, NoteType)> = chips.clone(); + entry.connect_changed(move |e| { + let Some(nt) = breadpad_shared::parser::detect_prefix_type(&e.text()) else { + return; + }; + *selected_type_clone.borrow_mut() = nt.clone(); + for (btn, chip_nt) in &chips_clone { + if *chip_nt == nt { + btn.add_css_class("active"); + } else { + btn.remove_css_class("active"); + } + } + }); + } + + let hint = gtk4::Label::builder() + .label("td: · rem: · idea: · note: · q:") + .css_classes(["prefix-hint"]) + .xalign(0.0) + .build(); + // Confirm button let confirm_btn = gtk4::Button::builder() .label("✓") .css_classes(["confirm-button"]) .build(); + let confirm_wrap = gtk4::Box::builder() + .css_classes(["confirm-wrap"]) + .build(); + confirm_wrap.append(&confirm_btn); let bottom_row = gtk4::Box::builder() .orientation(gtk4::Orientation::Horizontal) @@ -650,9 +682,10 @@ fn build_window( let spacer = gtk4::Box::builder().hexpand(true).build(); bottom_row.append(&spacer); - bottom_row.append(&confirm_btn); + bottom_row.append(&confirm_wrap); vbox.append(&entry); + vbox.append(&hint); vbox.append(&bottom_row); window.set_child(Some(&vbox)); From 7394e65da59cb65211630800f12883c5907ef4bf Mon Sep 17 00:00:00 2001 From: Breadway Date: Thu, 16 Jul 2026 17:52:05 +0800 Subject: [PATCH 4/9] breadpad: bump bread-theme to v0.2.10 (fixes pywal-colored window background) --- Cargo.lock | 22 +++++++++++----------- breadpad-shared/Cargo.toml | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4936958..de5654b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -76,7 +76,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -87,7 +87,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -305,7 +305,7 @@ dependencies = [ [[package]] name = "bread-theme" version = "0.2.3" -source = "git+https://github.com/Breadway/bread-ecosystem?tag=v0.2.8#77417d552130281ff787e07d52541eb25e9d533b" +source = "git+https://github.com/Breadway/bread-ecosystem?tag=v0.2.10#17d1bb85801b9a8c195b64c02d288cd662c9c780" dependencies = [ "dirs 5.0.1", "gtk4", @@ -851,7 +851,7 @@ dependencies = [ "libc", "option-ext", "redox_users 0.5.2", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -926,7 +926,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -1237,7 +1237,7 @@ dependencies = [ "gobject-sys", "libc", "system-deps", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -2079,7 +2079,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -2701,7 +2701,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -2915,7 +2915,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -3025,7 +3025,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -3393,7 +3393,7 @@ checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" dependencies = [ "memoffset", "tempfile", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] diff --git a/breadpad-shared/Cargo.toml b/breadpad-shared/Cargo.toml index 01470f9..7af8279 100644 --- a/breadpad-shared/Cargo.toml +++ b/breadpad-shared/Cargo.toml @@ -7,7 +7,7 @@ authors.workspace = true [dependencies] -bread-theme = { git = "https://github.com/Breadway/bread-ecosystem", tag = "v0.2.8", features = ["gtk"] } +bread-theme = { git = "https://github.com/Breadway/bread-ecosystem", tag = "v0.2.10", features = ["gtk"] } anyhow.workspace = true tracing.workspace = true serde.workspace = true From b7aed8a37cb943e604d1b0831997bb627eefcf3e Mon Sep 17 00:00:00 2001 From: Breadway Date: Thu, 16 Jul 2026 18:04:39 +0800 Subject: [PATCH 5/9] breadpad: bump version to 0.4.1 --- Cargo.lock | 8 ++++---- Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index de5654b..3e8ac3d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -315,7 +315,7 @@ dependencies = [ [[package]] name = "breadman" -version = "0.4.0" +version = "0.4.1" dependencies = [ "anyhow", "breadpad-shared", @@ -332,7 +332,7 @@ dependencies = [ [[package]] name = "breadpad" -version = "0.4.0" +version = "0.4.1" dependencies = [ "anyhow", "breadpad-shared", @@ -351,7 +351,7 @@ dependencies = [ [[package]] name = "breadpad-shared" -version = "0.4.0" +version = "0.4.1" dependencies = [ "anyhow", "bread-theme", @@ -377,7 +377,7 @@ dependencies = [ [[package]] name = "breadpad-test" -version = "0.4.0" +version = "0.4.1" dependencies = [ "anyhow", "breadpad-shared", diff --git a/Cargo.toml b/Cargo.toml index 1de5996..b287686 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ members = [ resolver = "2" [workspace.package] -version = "0.4.0" +version = "0.4.1" edition = "2021" license = "MIT" authors = ["Breadway"] From 6e5448e853d68aca27b0e80c299d05ae46c28697 Mon Sep 17 00:00:00 2001 From: Breadway Date: Fri, 17 Jul 2026 08:28:19 +0800 Subject: [PATCH 6/9] breadpad: save note before window can close, add network timeouts, lock the JSONL store, fix monthly recurrence, restrict config permissions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - main.rs: hold the gtk4::Application (RAII guard from app.hold()) across the idle_add_local_once save callback, so closing the capture window can no longer let the process quit before the note is actually written to disk - breadpad-shared/src/ai.rs: 10s timeout on the Ollama HTTP call (ureq had none, so a hung local endpoint could stall indefinitely) - breadpad-shared/src/calendar.rs: 15s timeout on the CalDAV reqwest client (applies to every CalDAV request made through it) - breadpad-shared/src/store.rs: exclusive flock (std::fs::File::lock, no new dependency needed at this Rust version) on a sidecar lock file around every read-modify-write span, guarding against breadpad/breadman/ reminder-fire processes racing each other on notes.jsonl - breadpad-shared/src/scheduler.rs: parse_next_from_rrule now handles FREQ=MONTHLY (previously fell into the catch-all None arm, so a monthly reminder fired once and never rescheduled); reachable today via breadman's free-text RRULE editor field - breadpad-shared/src/config.rs: Config::save() chmods breadpad.toml to 0600 after writing, since it can hold the CalDAV password in plaintext Pre-existing, unrelated test failure noted: theme::tests::css_defines_bg_color fails identically on the original commit (depends on this machine's live pywal cache) — confirmed via git stash, not touched. --- breadpad-shared/src/ai.rs | 6 +++ breadpad-shared/src/calendar.rs | 6 +++ breadpad-shared/src/config.rs | 14 +++++++ breadpad-shared/src/scheduler.rs | 71 +++++++++++++++++++++++++++++++- breadpad-shared/src/store.rs | 71 ++++++++++++++++++++++++++------ breadpad/src/main.rs | 14 ++++++- 6 files changed, 168 insertions(+), 14 deletions(-) diff --git a/breadpad-shared/src/ai.rs b/breadpad-shared/src/ai.rs index 7bb69ad..b6afa17 100644 --- a/breadpad-shared/src/ai.rs +++ b/breadpad-shared/src/ai.rs @@ -61,8 +61,14 @@ impl OllamaClient { "stream": false }); + // ureq 2's default agent has no overall request timeout, so a hung + // local Ollama endpoint would otherwise stall this call forever — + // and since classification now runs from an idle callback after the + // capture window has already closed (see `main.rs`), a hang here is + // invisible to the user, not just slow. let response = ureq::post(&url) .set("Content-Type", "application/json") + .timeout(std::time::Duration::from_secs(10)) .send_json(payload) .map_err(|e| anyhow::anyhow!("Ollama HTTP error: {}", e))?; diff --git a/breadpad-shared/src/calendar.rs b/breadpad-shared/src/calendar.rs index 479ad05..99df780 100644 --- a/breadpad-shared/src/calendar.rs +++ b/breadpad-shared/src/calendar.rs @@ -18,7 +18,13 @@ impl CalDavClient { pub fn new(config: CalendarConfig) -> Self { // `reqwest::Client::builder().build()` can only fail if the TLS backend can't be // initialised; fall back to `Client::new()` semantics rather than panicking. + // + // A request-wide timeout is set here (rather than on `Client::new()`'s + // untimed defaults) so a hung/unreachable CalDAV server can't hang + // whatever's making the request indefinitely — `reqwest::Client::new()` + // has no timeout of its own. let client = reqwest::Client::builder() + .timeout(std::time::Duration::from_secs(15)) .build() .unwrap_or_else(|e| { tracing::warn!("falling back to default HTTP client: {}", e); diff --git a/breadpad-shared/src/config.rs b/breadpad-shared/src/config.rs index 9d81e5e..2eeb3d5 100644 --- a/breadpad-shared/src/config.rs +++ b/breadpad-shared/src/config.rs @@ -193,6 +193,20 @@ impl Config { } let text = toml::to_string_pretty(self)?; fs::write(&path, text)?; + + // This file can hold the CalDAV password in plaintext (see + // `CalendarConfig`'s own doc comment) — `fs::write` creates it with + // the process's default umask, which on most setups means + // world-readable. Lock it down to owner-only rather than just + // telling the user to do it themselves. + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + if let Err(e) = fs::set_permissions(&path, fs::Permissions::from_mode(0o600)) { + tracing::warn!("failed to restrict permissions on {}: {}", path.display(), e); + } + } + Ok(()) } } diff --git a/breadpad-shared/src/scheduler.rs b/breadpad-shared/src/scheduler.rs index ff5ff26..98d9f2b 100644 --- a/breadpad-shared/src/scheduler.rs +++ b/breadpad-shared/src/scheduler.rs @@ -206,6 +206,42 @@ pub(crate) fn parse_next_from_rrule(rrule_str: &str, default_morning: &str) -> O (now.date_naive() + chrono::Duration::days(days_ahead)).and_time(fire_time); return Some(local_naive_to_utc(target_date)); } + "MONTHLY" => { + use chrono::Datelike; + // BYMONTHDAY isn't guaranteed to be present — breadman's note + // editor lets a user type an arbitrary RRULE by hand, and + // "FREQ=MONTHLY" alone is a perfectly valid (if under-specified) + // one. Fall back to today's day-of-month, mirroring how WEEKLY + // above defaults BYDAY to "MO" when absent. + let day: u32 = parts + .get("BYMONTHDAY") + .and_then(|v| v.parse().ok()) + .filter(|d: &u32| (1..=31).contains(d)) + .unwrap_or_else(|| now.day()); + + let mut year = now.year(); + let mut month = now.month(); + + // Walk forward month by month for the next calendar date that + // (a) actually has this day-of-month (a 31st skips e.g. April) + // and (b) is still in the future. Bounded to 24 months as a + // defensive cap — every valid day (1-31) recurs well within a + // year, so this should never come close to firing. + for _ in 0..24 { + if let Some(date) = chrono::NaiveDate::from_ymd_opt(year, month, day) { + let candidate = date.and_time(fire_time); + if now.naive_local() < candidate { + return Some(local_naive_to_utc(candidate)); + } + } + month += 1; + if month > 12 { + month = 1; + year += 1; + } + } + None + } _ => None, } } @@ -404,7 +440,40 @@ mod tests { #[test] fn unknown_freq_returns_none() { - assert!(parse_next_from_rrule("RRULE:FREQ=MONTHLY;BYHOUR=9;BYMINUTE=0", "08:00").is_none()); + assert!(parse_next_from_rrule("RRULE:FREQ=YEARLY;BYHOUR=9;BYMINUTE=0", "08:00").is_none()); + } + + #[test] + fn monthly_reschedules_instead_of_returning_none() { + // This used to be the exact bug: MONTHLY fell into the `_ => None` + // arm, so a monthly reminder fired once and never rescheduled. + let t = parse_next_from_rrule("RRULE:FREQ=MONTHLY;BYMONTHDAY=15;BYHOUR=9;BYMINUTE=0", "08:00"); + assert!(t.is_some(), "MONTHLY must produce a next occurrence, not None"); + let local: chrono::DateTime = t.unwrap().into(); + assert_eq!(local.day(), 15); + assert_eq!(local.hour(), 9); + assert_eq!(local.minute(), 0); + assert!(local > Local::now()); + } + + #[test] + fn monthly_without_bymonthday_uses_todays_day_of_month() { + let t = parse_next_from_rrule("RRULE:FREQ=MONTHLY;BYHOUR=23;BYMINUTE=59", "08:00").unwrap(); + let local: chrono::DateTime = t.into(); + assert_eq!(local.day(), Local::now().day()); + } + + #[test] + fn monthly_on_the_31st_skips_shorter_months() { + // Every candidate month/day combination this walks must actually + // exist (from_ymd_opt returning None for e.g. April 31 is skipped + // internally) — this mostly guards against a panic/infinite loop + // regression rather than a specific date, since "next Feb 31" et al + // must fall through to a month that really has a 31st. + let t = parse_next_from_rrule("RRULE:FREQ=MONTHLY;BYMONTHDAY=31;BYHOUR=9;BYMINUTE=0", "08:00"); + assert!(t.is_some()); + let local: chrono::DateTime = t.unwrap().into(); + assert_eq!(local.day(), 31); } #[test] diff --git a/breadpad-shared/src/store.rs b/breadpad-shared/src/store.rs index bc8b86a..653eff8 100644 --- a/breadpad-shared/src/store.rs +++ b/breadpad-shared/src/store.rs @@ -44,11 +44,43 @@ impl Store { } } + /// Path to the sidecar lock file guarding the whole note store — a + /// single lock covers both `notes.jsonl` and `archive.jsonl` since + /// `rotate_archive` moves notes between them in one logical operation. + fn lock_path(&self) -> PathBuf { + self.notes_path.with_extension("lock") + } + + /// Blocks until an exclusive advisory lock (`flock`) is held on the + /// sidecar lock file, and holds it for as long as the returned `File` + /// stays alive (the lock is released automatically when it's dropped, + /// same as it would be on process exit/crash). + /// + /// breadpad (capture/fire/snooze), breadman (edit), and multiple + /// concurrent reminder-fire processes all read and rewrite the same + /// JSONL file with no coordination otherwise: two concurrent + /// load-modify-rewrite cycles racing `write_all`'s rename can silently + /// lose one side's change. This is intentionally one lock for the + /// entire store rather than per-note or per-file locking — contention + /// is expected to be rare (a handful of short-lived CLI-ish processes, + /// not a server), so simplicity wins over fine-grained locking here. + fn acquire_lock(&self) -> Result { + let file = OpenOptions::new() + .create(true) + .write(true) + .open(self.lock_path()) + .context("failed to open notes store lock file")?; + file.lock().context("failed to acquire notes store lock")?; + Ok(file) + } + pub fn load_all(&self) -> Result> { + let _lock = self.acquire_lock()?; self.load_from(&self.notes_path) } pub fn load_archive(&self) -> Result> { + let _lock = self.acquire_lock()?; self.load_from(&self.archive_path) } @@ -74,12 +106,15 @@ impl Store { } pub fn save_note(&self, note: &Note) -> Result<()> { - let mut file = OpenOptions::new() - .create(true) - .append(true) - .open(&self.notes_path)?; - let line = serde_json::to_string(note)?; - writeln!(file, "{}", line)?; + { + let _lock = self.acquire_lock()?; + let mut file = OpenOptions::new() + .create(true) + .append(true) + .open(&self.notes_path)?; + let line = serde_json::to_string(note)?; + writeln!(file, "{}", line)?; + } if let Some(cal_cfg) = &self.calendar { if cal_cfg.enabled && (note.time.is_some() || note.rrule.is_some()) { @@ -103,13 +138,18 @@ impl Store { } pub fn delete_note(&self, id: &str) -> Result<()> { - let all = self.load_all()?; - let (to_delete, keep): (Vec, Vec) = all.into_iter().partition(|n| n.id == id); - self.write_all(&self.notes_path, &keep)?; + let to_delete_note = { + let _lock = self.acquire_lock()?; + let all = self.load_from(&self.notes_path)?; + let (to_delete, keep): (Vec, Vec) = + all.into_iter().partition(|n| n.id == id); + self.write_all(&self.notes_path, &keep)?; + to_delete.into_iter().next() + }; if let Some(cal_cfg) = &self.calendar { if cal_cfg.enabled { - if let Some(note) = to_delete.into_iter().next() { + if let Some(note) = to_delete_note { spawn_caldav_delete(caldav_uid(¬e), cal_cfg.clone()); } } @@ -118,11 +158,17 @@ impl Store { Ok(()) } + /// Holds the store lock across the whole load-modify-write span (not + /// just the write) — `load_from` is used directly here rather than the + /// public, self-locking `load_all`, since re-acquiring the same + /// process-wide advisory lock while already holding it would block + /// forever (`flock` isn't re-entrant across separate file descriptors). fn rewrite_notes(&self, mut f: F) -> Result<()> where F: FnMut(Note) -> Note, { - let notes: Vec = self.load_all()?.into_iter().map(|n| f(n)).collect(); + let _lock = self.acquire_lock()?; + let notes: Vec = self.load_from(&self.notes_path)?.into_iter().map(|n| f(n)).collect(); self.write_all(&self.notes_path, ¬es) } @@ -141,8 +187,9 @@ impl Store { } pub fn rotate_archive(&self, archive_after_days: i64) -> Result { + let _lock = self.acquire_lock()?; let cutoff = Utc::now() - Duration::days(archive_after_days); - let notes = self.load_all()?; + let notes = self.load_from(&self.notes_path)?; let (to_archive, keep): (Vec, Vec) = notes .into_iter() .partition(|n| n.done && n.completed.map_or(false, |c| c < cutoff)); diff --git a/breadpad/src/main.rs b/breadpad/src/main.rs index 033a114..e33087c 100644 --- a/breadpad/src/main.rs +++ b/breadpad/src/main.rs @@ -694,6 +694,7 @@ fn build_window( let selected_type_clone = selected_type.clone(); let cfg_clone = cfg.clone(); let workspace_clone = workspace.clone(); + let app_clone = app.clone(); let save_and_close = { let win = win_clone.clone(); @@ -701,6 +702,7 @@ fn build_window( let selected_type = selected_type_clone.clone(); let cfg = cfg_clone.clone(); let workspace = workspace_clone.clone(); + let app = app_clone.clone(); move || { let text = entry.text().to_string(); @@ -711,10 +713,20 @@ fn build_window( let note_type = selected_type.borrow().clone(); let cfg_c = cfg.clone(); let ws_c = workspace.clone(); - // Close first so the popup disappears immediately, then save. + // Close first so the popup disappears immediately, then save — + // but `hold()` the application across the gap first. Without + // this, closing the only open window can let the whole process + // (and with it, the `idle_add_local_once` callback below that + // actually writes the note) exit before that callback ever + // runs, silently losing the note the user just typed. `hold()` + // returns an RAII guard that keeps the app alive with zero + // windows open until it's dropped, right after the save + // finishes below. + let hold_guard = app.hold(); win.close(); glib::idle_add_local_once(move || { save_note_classified(&text, note_type, no_classify, cfg_c, ws_c); + drop(hold_guard); }); } }; From 369935515b166d89d8157f8bd93d4c59ebf55e3f Mon Sep 17 00:00:00 2001 From: Breadway Date: Fri, 17 Jul 2026 08:32:17 +0800 Subject: [PATCH 7/9] breadpad-shared: fix stale test expectation from the bread-theme v0.2.10 bump MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit css_defines_bg_color still expected the pre-v0.2.10 Catppuccin Mocha default (#1e1e2e). bread-theme v0.2.10 deliberately fixed background to #0c0c0c (BOS's own dark theme, no longer pywal-derived — see FIXED_BACKGROUND's doc comment in bread-theme's palette.rs) so a light or muddy wallpaper can't wash out every bread app's background. The product code was already correct; only this assertion was never updated for the bump, so it started failing the moment breadpad's Cargo.toml pin moved to v0.2.10 (confirmed via git stash: it fails identically on the original commit). --- breadpad-shared/src/theme.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/breadpad-shared/src/theme.rs b/breadpad-shared/src/theme.rs index 8cc2512..1b6cb7f 100644 --- a/breadpad-shared/src/theme.rs +++ b/breadpad-shared/src/theme.rs @@ -284,8 +284,14 @@ mod tests { #[test] fn css_defines_bg_color() { + // #1e1e2e (Catppuccin Mocha) was the default before bread-theme + // v0.2.10, which deliberately fixed background/surface/overlay to + // BOS's own dark theme (#0c0c0c etc.) instead of tracking pywal — + // see bread-theme's `palette.rs` `FIXED_BACKGROUND` doc comment. + // This assertion was never updated for that bump, so it started + // failing the moment breadpad's Cargo.toml pin moved to v0.2.10. let css = build_css(&Palette::default(), None); - assert!(css.contains("@define-color bg #1e1e2e"), "css missing bg: {}", &css[..300]); + assert!(css.contains("@define-color bg #0c0c0c"), "css missing bg: {}", &css[..300]); } #[test] From 6a06872f09b7376d3387d415e5cd3af2bc1ac44f Mon Sep 17 00:00:00 2001 From: Breadway Date: Fri, 17 Jul 2026 13:53:00 +0800 Subject: [PATCH 8/9] Fix silent ROCm-to-CPU fallback: switch to MIGraphX; fix XDG tilde bugs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is the actual bug the night's ecosystem-utils audit was looking for. classifier.rs::try_load_session requested ort::ep::ROCm (the classic ROCMExecutionProvider) first. Per this machine's own breadsearch-gpu- backends operator notes, that EP silently no-ops on this class of system: distro ROCm onnxruntime builds (Arch's onnxruntime-rocm) are commonly compiled with --use_migraphx, not --use_rocm, so ROCMExecutionProvider never actually registers — active_provider could report "ROCm (iGPU)" while every real inference secretly ran on CPU, with nothing surfacing that fact anywhere. Switched to ort::ep::MIGraphX via bread_onnx::build_session (path dependency for now, see the TODO in Cargo.toml), matching breadsearch's own already-correct embed.rs. Also fixed the same literal-tilde XDG fallback bug found across this pass (breadclip-core, breadmon, breadarr-shared) in three more places: classifier.rs::model_dir, config.rs::config_path, config.rs::style_css_path. Bumped the workspace's tokenizers pin 0.21 -> 0.23 to unify with bread-onnx's own requirement (breadarr already pins 0.23); verified via a full workspace build + test pass, no API changes needed at any call site. Validation, and an important finding: breadpad-shared's full test suite (unit: 181/181, config: 26/26, classifier integration: 15/15) passes clean. The pipeline.rs integration suite (16 tests, each building a real classifier session) surfaced ONE genuine, 100%-reproducible failure: plain_note_appears_in_store expects "retro went well today" to classify as Note, but MIGraphX execution classifies it as Question (CPU execution returns Note). This is not a regression from this change — it's proof the fix works: the GPU path was never actually running before, so this CPU-vs-GPU floating-point divergence on a borderline NLI classification was always latent and simply never observable. Left the test as-is (its failure now accurately reflects reality) rather than "fixing" it by reverting to the broken EP or silently forcing a test-only CPU path — that's a product/test-fixture decision for the owner, not something a duplication-extraction pass should decide unilaterally. See bread-onnx's companion fix (ORT_MIGRAPHX_MODEL_CACHE_PATH default) which cut this suite's wall time from 905s to 112s by letting compiled kernels persist across runs. --- Cargo.lock | 135 ++++++++++++++++++++++++++---- Cargo.toml | 6 +- breadpad-shared/Cargo.toml | 3 + breadpad-shared/src/classifier.rs | 65 ++++++++------ breadpad-shared/src/config.rs | 14 ++-- breadpad/Cargo.toml | 2 + breadpad/src/main.rs | 15 ++-- 7 files changed, 183 insertions(+), 57 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3e8ac3d..01a39f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -76,7 +76,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -87,7 +87,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -302,6 +302,20 @@ dependencies = [ "piper", ] +[[package]] +name = "bread-onnx" +version = "0.2.3" +dependencies = [ + "anyhow", + "bread-utils", + "hex", + "ort", + "sha2", + "tokenizers", + "tracing", + "ureq", +] + [[package]] name = "bread-theme" version = "0.2.3" @@ -313,6 +327,15 @@ dependencies = [ "serde_json", ] +[[package]] +name = "bread-utils" +version = "0.2.3" +dependencies = [ + "dirs 5.0.1", + "serde", + "serde_json", +] + [[package]] name = "breadman" version = "0.4.1" @@ -335,6 +358,7 @@ name = "breadpad" version = "0.4.1" dependencies = [ "anyhow", + "bread-utils", "breadpad-shared", "chrono", "dirs 5.0.1", @@ -354,7 +378,9 @@ name = "breadpad-shared" version = "0.4.1" dependencies = [ "anyhow", + "bread-onnx", "bread-theme", + "bread-utils", "chrono", "dirs 5.0.1", "ical", @@ -613,6 +639,18 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "console" +version = "0.16.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fe5f465a4f6fee88fad41b85d990f84c835335e85b5d9e6e63e0d06d28cba7c" +dependencies = [ + "encode_unicode", + "libc", + "unicode-width", + "windows-sys 0.61.2", +] + [[package]] name = "convert_case" version = "0.10.0" @@ -704,6 +742,12 @@ dependencies = [ "typenum", ] +[[package]] +name = "daachorse" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f55d7153ba3b507595872a3874803f07a8a81d1e888abed8e5db7da0597d6e2" + [[package]] name = "darling" version = "0.20.11" @@ -851,7 +895,7 @@ dependencies = [ "libc", "option-ext", "redox_users 0.5.2", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -926,7 +970,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -934,6 +978,9 @@ name = "esaxx-rs" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d817e038c30374a4bcb22f94d0a8a0e216958d4c3dcde369b1439fec4bdda6e6" +dependencies = [ + "cc", +] [[package]] name = "event-listener" @@ -958,9 +1005,9 @@ dependencies = [ [[package]] name = "fancy-regex" -version = "0.14.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298" +checksum = "72cf461f865c862bb7dc573f643dd6a2b6842f7c30b07882b56bd148cc2761b8" dependencies = [ "bit-set", "regex-automata", @@ -1237,7 +1284,7 @@ dependencies = [ "gobject-sys", "libc", "system-deps", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -1489,7 +1536,7 @@ checksum = "629d8f3bbeda9d148036d6b0de0a3ab947abd08ce90626327fc3547a49d59d97" dependencies = [ "dirs 6.0.0", "http", - "indicatif", + "indicatif 0.17.11", "libc", "log", "rand 0.9.4", @@ -1793,13 +1840,26 @@ version = "0.17.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" dependencies = [ - "console", + "console 0.15.11", "number_prefix", "portable-atomic", "unicode-width", "web-time", ] +[[package]] +name = "indicatif" +version = "0.18.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9433806cd6b4ec1aba79c021c7e4c58fb4c3b9977c085062e611ac929998fb0c" +dependencies = [ + "console 0.16.4", + "portable-atomic", + "unicode-width", + "unit-prefix", + "web-time", +] + [[package]] name = "ipnet" version = "2.12.0" @@ -2079,7 +2139,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -2127,6 +2187,28 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "onig" +version = "6.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc3cbf698f9438986c11a880c90a6d04b9de27575afd28bbf45b154b6c709e2" +dependencies = [ + "bitflags", + "libc", + "once_cell", + "onig_sys", +] + +[[package]] +name = "onig_sys" +version = "69.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e68317604e77e53b85896388e1a803c1d21b74c899ec9e5e1112db90735edd7" +dependencies = [ + "cc", + "pkg-config", +] + [[package]] name = "option-ext" version = "0.2.0" @@ -2701,7 +2783,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -2859,6 +2941,17 @@ dependencies = [ "digest", ] +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -2915,7 +3008,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -3025,7 +3118,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -3104,23 +3197,25 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokenizers" -version = "0.21.4" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a620b996116a59e184c2fa2dfd8251ea34a36d0a514758c6f966386bd2e03476" +checksum = "44e5bea67576e04b6ff8564c5d9e09c2ef0cf476502245f2f120e497769d3112" dependencies = [ "ahash", - "aho-corasick", "compact_str", + "daachorse", "dary_heap", "derive_builder", "esaxx-rs", "fancy-regex", "getrandom 0.3.4", "hf-hub", + "indicatif 0.18.6", "itertools", "log", "macro_rules_attribute", "monostate", + "onig", "paste", "rand 0.9.4", "rayon", @@ -3393,7 +3488,7 @@ checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" dependencies = [ "memoffset", "tempfile", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -3435,6 +3530,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" +[[package]] +name = "unit-prefix" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3" + [[package]] name = "untrusted" version = "0.9.0" diff --git a/Cargo.toml b/Cargo.toml index b287686..53daebd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,11 @@ tokio = { version = "1", features = ["full"] } zbus = { version = "4", default-features = false, features = ["tokio"] } ort = { version = "2.0.0-rc.12", default-features = false, features = ["std", "ndarray", "tracing", "api-24", "rocm", "load-dynamic"] } ndarray = "0.16" -tokenizers = { version = "0.21", default-features = false, features = ["http", "fancy-regex"] } +# Bumped 0.21 -> 0.23 to unify with bread-onnx's own tokenizers requirement +# (breadarr already pins 0.23) — the APIs this crate actually calls +# (Tokenizer::from_file, encode, get_ids/get_attention_mask) are unchanged +# between the two; verified via a full workspace build + test pass. +tokenizers = { version = "0.23", default-features = false, features = ["http", "fancy-regex"] } gtk4 = { version = "0.11", features = ["v4_12"] } gtk4-layer-shell = "0.8" hyprland = "0.4.0-beta.3" diff --git a/breadpad-shared/Cargo.toml b/breadpad-shared/Cargo.toml index 7af8279..cc2e2f5 100644 --- a/breadpad-shared/Cargo.toml +++ b/breadpad-shared/Cargo.toml @@ -26,6 +26,9 @@ regex.workspace = true ureq.workspace = true reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "json"] } ical = "0.11" +# TODO(owner): switch to tag-pinned git dependency once bread-onnx/bread-utils are merged and tagged, matching the bread-theme pattern +bread-onnx = { path = "../../bread-ecosystem-fix-worktree/bread-onnx" } +bread-utils = { path = "../../bread-ecosystem-fix-worktree/bread-utils" } [dev-dependencies] tempfile = "3" diff --git a/breadpad-shared/src/classifier.rs b/breadpad-shared/src/classifier.rs index e752a31..18efb83 100644 --- a/breadpad-shared/src/classifier.rs +++ b/breadpad-shared/src/classifier.rs @@ -16,7 +16,7 @@ pub enum ExecutionProvider { impl ExecutionProvider { pub fn as_str(&self) -> &str { match self { - ExecutionProvider::Gpu => "ROCm (iGPU)", + ExecutionProvider::Gpu => "MIGraphX (iGPU)", ExecutionProvider::Cpu => "CPU", } } @@ -32,10 +32,13 @@ pub struct Classifier { } fn model_dir() -> PathBuf { - dirs::data_local_dir() - .unwrap_or_else(|| PathBuf::from("~/.local/share")) - .join("breadpad") - .join("model") + // Was `dirs::data_local_dir().unwrap_or_else(|| PathBuf::from("~/.local/share"))` + // — the same literal-tilde-fallback bug flagged (but not fixed) in + // breadclip-core tonight: PathBuf/std::fs never expand `~`, so on the + // rare box where `dirs` can't resolve a home directory this silently + // resolved to a directory literally named `~` under the current working + // directory instead of the user's actual home. + bread_utils::xdg::data_dir("breadpad").join("model") } impl Classifier { @@ -246,21 +249,46 @@ fn softmax_single(logits: &[f32], idx: usize) -> f32 { fn try_load_session( path: &std::path::Path, ) -> (Option, ExecutionProvider) { - // Try ROCm (iGPU) first, fall back to CPU. - let rocm_available = { + // AMD iGPU via MIGraphX, falling back to CPU. This used to request the + // classic `ort::ep::ROCm` (ROCMExecutionProvider) first — per this + // machine's own breadsearch-gpu-backends operator notes, that EP + // silently no-ops back to CPU on this class of system (distro ROCm + // onnxruntime builds, e.g. Arch's onnxruntime-rocm, are commonly + // compiled with `--use_migraphx`, not `--use_rocm`), so "ROCm (iGPU)" + // could report as active in this struct's own `active_provider` while + // every inference actually ran on CPU. See bread_onnx::provider's doc + // comment for the full history — breadsearch's own embed.rs already + // got this right. + // + // The `is_available()` gate (kept from the original implementation) + // means `active_provider` only ever claims Gpu when we actually + // attempted the GPU build — bread_onnx::build_session's loud EP- + // selection logging (visible once tracing_subscriber is initialized, + // which this crate's own main.rs already does) is what catches the + // *silent per-node fallback* class of bug this rewrite exists to fix, + // rather than papering over it by unconditionally reporting Gpu. + let migraphx_available = { use ort::execution_providers::ExecutionProvider as _; - ort::ep::ROCm::default().is_available().unwrap_or(false) + ort::ep::MIGraphX::default().is_available().unwrap_or(false) }; - if rocm_available { - match build_onnx_session(path, ort::ep::ROCm::default().build()) { + if migraphx_available { + match bread_onnx::build_session( + path, + ort::session::builder::GraphOptimizationLevel::Level3, + &[bread_onnx::Provider::MiGraphX { device_id: 0 }], + ) { Ok(s) => { - tracing::info!("ONNX session loaded (ROCm iGPU)"); + tracing::info!("ONNX session loaded (MIGraphX iGPU)"); return (Some(s), ExecutionProvider::Gpu); } - Err(e) => tracing::debug!("ROCm EP unavailable: {}; trying CPU", e), + Err(e) => tracing::debug!("MIGraphX EP unavailable: {}; trying CPU", e), } } - match build_onnx_session(path, ort::ep::CPU::default().build()) { + match bread_onnx::build_session( + path, + ort::session::builder::GraphOptimizationLevel::Level3, + &[bread_onnx::Provider::Cpu], + ) { Ok(s) => { tracing::info!("ONNX session loaded (CPU)"); (Some(s), ExecutionProvider::Cpu) @@ -271,14 +299,3 @@ fn try_load_session( } } } - -fn build_onnx_session( - path: &std::path::Path, - ep: ort::ep::ExecutionProviderDispatch, -) -> anyhow::Result { - let mut builder = ort::session::Session::builder() - .map_err(|e| anyhow::anyhow!("builder: {}", e))? - .with_execution_providers([ep]) - .map_err(|e| anyhow::anyhow!("ep: {}", e))?; - builder.commit_from_file(path).map_err(|e| anyhow::anyhow!("load: {}", e)) -} diff --git a/breadpad-shared/src/config.rs b/breadpad-shared/src/config.rs index 2eeb3d5..12702ec 100644 --- a/breadpad-shared/src/config.rs +++ b/breadpad-shared/src/config.rs @@ -212,15 +212,13 @@ impl Config { } pub fn config_path() -> PathBuf { - dirs::config_dir() - .unwrap_or_else(|| PathBuf::from("~/.config")) - .join("breadpad") - .join("breadpad.toml") + // Was `dirs::config_dir().unwrap_or_else(|| PathBuf::from("~/.config"))` + // — same literal-tilde-fallback bug as `classifier.rs::model_dir` (see + // its doc comment) and breadclip-core's `data_dir`; PathBuf/std::fs + // never expand `~`. + bread_utils::xdg::config_dir("breadpad").join("breadpad.toml") } pub fn style_css_path() -> PathBuf { - dirs::config_dir() - .unwrap_or_else(|| PathBuf::from("~/.config")) - .join("breadpad") - .join("style.css") + bread_utils::xdg::config_dir("breadpad").join("style.css") } diff --git a/breadpad/Cargo.toml b/breadpad/Cargo.toml index c835bb9..4bf9b0b 100644 --- a/breadpad/Cargo.toml +++ b/breadpad/Cargo.toml @@ -23,3 +23,5 @@ gtk4-layer-shell.workspace = true hyprland.workspace = true dirs.workspace = true tokio.workspace = true +# TODO(owner): switch to tag-pinned git dependency once bread-utils is merged and tagged, matching the bread-theme pattern +bread-utils = { path = "../../bread-ecosystem-fix-worktree/bread-utils" } diff --git a/breadpad/src/main.rs b/breadpad/src/main.rs index e33087c..3d946f2 100644 --- a/breadpad/src/main.rs +++ b/breadpad/src/main.rs @@ -536,13 +536,14 @@ fn run_popup(preset_type: Option, no_classify: bool, cfg: Config) -> Res } fn get_active_workspace() -> Option { - // Use hyprctl via CLI since the async API would require a runtime here - let out = std::process::Command::new("hyprctl") - .args(["activeworkspace", "-j"]) - .output() - .ok()?; - let val: serde_json::Value = serde_json::from_slice(&out.stdout).ok()?; - val.get("id").and_then(|v| v.as_i64()).map(|id| id.to_string()) + // Was a `Command::new("hyprctl").output()` call with no timeout (the + // `hyprland` crate's async API would require a runtime here, which this + // call site doesn't have) — bread_utils::hypr's socket1 client is + // synchronous and needs neither a subprocess nor a runtime. + bread_utils::hypr::request_json("j/activeworkspace")? + .get("id") + .and_then(|v| v.as_i64()) + .map(|id| id.to_string()) } fn build_window( From 710cb768ea921202f9c06479aa6cf4598dbf738b Mon Sep 17 00:00:00 2001 From: Breadway Date: Sun, 19 Jul 2026 03:52:59 +0800 Subject: [PATCH 9/9] Switch to tag-pinned bread-ecosystem deps; bump version to v0.5.0 --- Cargo.lock | 612 +++++++++++++++---------------------- Cargo.toml | 2 +- README.md | 2 +- breadpad-shared/Cargo.toml | 5 +- breadpad/Cargo.toml | 3 +- packaging/arch/PKGBUILD | 4 +- 6 files changed, 250 insertions(+), 378 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 01a39f8..147ec8a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -92,9 +92,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.102" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" [[package]] name = "async-broadcast" @@ -276,9 +276,9 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitflags" -version = "2.13.0" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" [[package]] name = "block-buffer" @@ -304,7 +304,8 @@ dependencies = [ [[package]] name = "bread-onnx" -version = "0.2.3" +version = "0.3.0" +source = "git+https://git.breadway.dev/Breadway/bread-ecosystem?tag=v0.3.0#8e82d2d833e992ce939a5b836f910ee109f2e939" dependencies = [ "anyhow", "bread-utils", @@ -329,7 +330,8 @@ dependencies = [ [[package]] name = "bread-utils" -version = "0.2.3" +version = "0.3.0" +source = "git+https://git.breadway.dev/Breadway/bread-ecosystem?tag=v0.3.0#8e82d2d833e992ce939a5b836f910ee109f2e939" dependencies = [ "dirs 5.0.1", "serde", @@ -338,7 +340,7 @@ dependencies = [ [[package]] name = "breadman" -version = "0.4.1" +version = "0.5.0" dependencies = [ "anyhow", "breadpad-shared", @@ -355,7 +357,7 @@ dependencies = [ [[package]] name = "breadpad" -version = "0.4.1" +version = "0.5.0" dependencies = [ "anyhow", "bread-utils", @@ -375,7 +377,7 @@ dependencies = [ [[package]] name = "breadpad-shared" -version = "0.4.1" +version = "0.5.0" dependencies = [ "anyhow", "bread-onnx", @@ -403,7 +405,7 @@ dependencies = [ [[package]] name = "breadpad-test" -version = "0.4.1" +version = "0.5.0" dependencies = [ "anyhow", "breadpad-shared", @@ -431,9 +433,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.11.1" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "cairo-rs" @@ -469,9 +471,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.63" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" +checksum = "c89588d05638b5b4594a3348a2d6c20277e43a7f5c5202b05cc56888475a47b8" dependencies = [ "find-msvc-tools", "shlex", @@ -495,9 +497,20 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "chacha20" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] [[package]] name = "chrono" @@ -537,9 +550,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.6.1" +version = "4.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +checksum = "dd059f9da4f5c36b3787f65d38ccaab1cc315f07b01f89abc8359ee6a8205011" dependencies = [ "clap_builder", "clap_derive", @@ -547,9 +560,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.6.0" +version = "4.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +checksum = "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b" dependencies = [ "anstream", "anstyle", @@ -675,6 +688,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.5.0" @@ -686,9 +708,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -696,18 +718,18 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.18" +version = "0.9.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.21" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" [[package]] name = "crossterm" @@ -1052,12 +1074,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - [[package]] name = "form_urlencoded" version = "1.2.2" @@ -1069,24 +1085,24 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" dependencies = [ "futures-core", ] [[package]] name = "futures-core" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" [[package]] name = "futures-executor" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" dependencies = [ "futures-core", "futures-task", @@ -1095,9 +1111,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" +checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" [[package]] name = "futures-lite" @@ -1114,9 +1130,9 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" dependencies = [ "proc-macro2", "quote", @@ -1125,21 +1141,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" +checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" [[package]] name = "futures-task" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" +checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" [[package]] name = "futures-util" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" dependencies = [ "futures-core", "futures-macro", @@ -1176,9 +1192,9 @@ dependencies = [ [[package]] name = "gdk4" -version = "0.11.2" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd42fdbbf48612c6e8f47c65fb92d2e8f39c25aecd6af047e83897c1a22d2a4e" +checksum = "d81e2a6c6ecba2aab60633a98df1868b03fa0bfdce8105edc27c1bccf71f0e39" dependencies = [ "cairo-rs", "gdk-pixbuf", @@ -1192,9 +1208,9 @@ dependencies = [ [[package]] name = "gdk4-sys" -version = "0.11.2" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d974ac4f15e67472c3a9728daf612590b4a5762a4b33f0edd298df0b80d043c" +checksum = "3d8f608d8d7d229975c4d0d026f5d3071598c4ddab3c5262b0a31840fec78d13" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", @@ -1237,31 +1253,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", - "js-sys", "libc", "r-efi 5.3.0", "wasip2", - "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi 6.0.0", - "wasip2", - "wasip3", + "rand_core 0.10.1", + "wasm-bindgen", ] [[package]] name = "gio" -version = "0.22.6" +version = "0.22.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3848bcba3a35cc0a71df8ba8ecfd799d6bfb862342a53a4a915fb62213aa4e6" +checksum = "8b3e1f669909c326b9413bde5a742097b8c90a7d78f45326db13668984769ded" dependencies = [ "futures-channel", "futures-core", @@ -1276,9 +1291,9 @@ dependencies = [ [[package]] name = "gio-sys" -version = "0.22.0" +version = "0.22.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64729ba2772c080448f9f966dba8f4456beeb100d8c28a865ef8a0f2ef4987e1" +checksum = "353fdc7da7cd16da916104b1e0e4e7de380ec9c8aaa20d4d742d66310ab4b0d5" dependencies = [ "glib-sys", "gobject-sys", @@ -1309,9 +1324,9 @@ dependencies = [ [[package]] name = "glib" -version = "0.22.7" +version = "0.22.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c207e04e51605dcf7b2924c41591b3a10e1438eaac5bcf448fb91f325381104a" +checksum = "ddbcf514bd1881fc1b960e4e52b4e82873f4da3bceddbd58d42827b508888100" dependencies = [ "bitflags", "futures-channel", @@ -1342,9 +1357,9 @@ dependencies = [ [[package]] name = "glib-sys" -version = "0.22.6" +version = "0.22.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f7fbac234ed5bc2a28359b7bde8e1b9cdf1441cc2d7f068e4824672d7db9445" +checksum = "030967459f9f676851872c6304adea7825c6d462ec9b72554c733cf0c5952233" dependencies = [ "libc", "system-deps", @@ -1363,32 +1378,30 @@ dependencies = [ [[package]] name = "graphene-rs" -version = "0.22.0" +version = "0.22.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7d1b7881f96869f49808b6adfe906a93a57a34204952253444d68c3208d71f1" +checksum = "eb856b9c558971c3f13ab692358926da710b046932a4e087aedcc35b040d7dff" dependencies = [ "glib", "graphene-sys", - "libc", ] [[package]] name = "graphene-sys" -version = "0.22.0" +version = "0.22.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "517f062f3fd6b7fd3e57a3f038a74b3c23ca32f51199ff028aa704609943f79c" +checksum = "5c7ffdfde88f3570d3705e0d8a2433e036d387a1f2930bbf47eafcb5f569fd04" dependencies = [ "glib-sys", "libc", - "pkg-config", "system-deps", ] [[package]] name = "gsk4" -version = "0.11.1" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53c912dfcbd28acace5fc99c40bb9f25e1dcb73efb1f2608327f66a99acdcb62" +checksum = "b867be1c5f14dcb8f552c0eff6e9a9b1da5f8b43943e8efc3a63c889d84952ff" dependencies = [ "cairo-rs", "gdk4", @@ -1401,9 +1414,9 @@ dependencies = [ [[package]] name = "gsk4-sys" -version = "0.11.1" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7d54bbc7a9d8b6ffe4f0c95eede15ccfb365c8bf521275abe6bcfb57b18fb8a" +checksum = "5b7c7eb2e681ee896646cfb8872b431f24d09f53ba9283289d9b10caa6707088" dependencies = [ "cairo-sys-rs", "gdk4-sys", @@ -1417,9 +1430,9 @@ dependencies = [ [[package]] name = "gtk4" -version = "0.11.3" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7181b837f04cbe93f79441475f7a00560a92cba7a72e38cc1a68b6f8b78eaae2" +checksum = "98a0a0466484f64b07b5b8184d43fa46be78eb0b8e04ae4e179af31d770b76d9" dependencies = [ "cairo-rs", "field-offset", @@ -1466,9 +1479,9 @@ dependencies = [ [[package]] name = "gtk4-macros" -version = "0.11.0" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3581b242ba62fdff122ebb626ea641582ec326031622bd19d60f85029c804a87" +checksum = "5ac7179400a36a04de039c24206bb841c5596992b907b43b23ee8d5bdc40d00e" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -1478,9 +1491,9 @@ dependencies = [ [[package]] name = "gtk4-sys" -version = "0.11.3" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20ba8e695e2640455561274e65e45f0a151619e450746007667f4b23ceae4e1b" +checksum = "82b8f954786af0b1984425c4446b77f5ff6594346181316be3f850caab1c6f01" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", @@ -1495,15 +1508,6 @@ dependencies = [ "system-deps", ] -[[package]] -name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "foldhash", -] - [[package]] name = "hashbrown" version = "0.17.1" @@ -1539,7 +1543,7 @@ dependencies = [ "indicatif 0.17.11", "libc", "log", - "rand 0.9.4", + "rand 0.9.5", "serde", "serde_json", "thiserror 2.0.18", @@ -1549,9 +1553,9 @@ dependencies = [ [[package]] name = "http" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" dependencies = [ "bytes", "itoa", @@ -1559,9 +1563,9 @@ dependencies = [ [[package]] name = "http-body" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" dependencies = [ "bytes", "http", @@ -1569,9 +1573,9 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" dependencies = [ "bytes", "futures-core", @@ -1619,7 +1623,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots 1.0.7", + "webpki-roots 1.0.9", ] [[package]] @@ -1789,12 +1793,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - [[package]] name = "ident_case" version = "1.0.1" @@ -1829,9 +1827,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.17.1", - "serde", - "serde_core", + "hashbrown", ] [[package]] @@ -1889,13 +1885,12 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "js-sys" -version = "0.3.99" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" dependencies = [ "cfg-if", "futures-util", - "once_cell", "wasm-bindgen", ] @@ -1911,12 +1906,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" - [[package]] name = "libc" version = "0.2.186" @@ -1935,9 +1924,9 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" +checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" dependencies = [ "libc", ] @@ -1971,9 +1960,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.32" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] name = "lru-slab" @@ -2008,9 +1997,9 @@ dependencies = [ [[package]] name = "matrixmultiply" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08" +checksum = "3f607c237553f086e7043417a51df26b2eb899d3caff94e6a67592ff992fedc7" dependencies = [ "autocfg", "rawpointer", @@ -2018,9 +2007,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.1" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] name = "memoffset" @@ -2049,9 +2038,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" dependencies = [ "libc", "wasi", @@ -2246,13 +2235,12 @@ checksum = "d7b497d21a8b6fbb4b5a544f8fadb77e801a09ae0add9e411d31c6f89e3c1e90" [[package]] name = "pango" -version = "0.22.6" +version = "0.22.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "251bdc6e6487b811be0e406a21e301e07e45c0aa8fa39e00c0c8e12a91752438" +checksum = "5d800d8d0de2ad5d0fb046f5344dbaba14a003cf3dd27cc21d85893d35ea316c" dependencies = [ "gio", "glib", - "libc", "pango-sys", ] @@ -2350,7 +2338,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared", - "rand 0.8.6", + "rand 0.8.7", ] [[package]] @@ -2401,9 +2389,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.13.1" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" +checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3" [[package]] name = "portable-atomic-util" @@ -2432,23 +2420,13 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn", -] - [[package]] name = "proc-macro-crate" version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit 0.25.12+spec-1.1.0", + "toml_edit 0.25.13+spec-1.1.0", ] [[package]] @@ -2462,9 +2440,9 @@ dependencies = [ [[package]] name = "quinn" -version = "0.11.9" +version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" dependencies = [ "bytes", "cfg_aliases", @@ -2482,14 +2460,15 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.14" +version = "0.11.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560" dependencies = [ "bytes", - "getrandom 0.3.4", + "getrandom 0.4.3", "lru-slab", - "rand 0.9.4", + "rand 0.10.2", + "rand_pcg", "ring", "rustc-hash", "rustls", @@ -2503,23 +2482,23 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.14" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" dependencies = [ "cfg_aliases", "libc", "once_cell", "socket2", "tracing", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "quote" -version = "1.0.45" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" dependencies = [ "proc-macro2", ] @@ -2538,9 +2517,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" dependencies = [ "libc", "rand_chacha 0.3.1", @@ -2549,14 +2528,25 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.5", ] +[[package]] +name = "rand" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand_core 0.10.1", +] + [[package]] name = "rand_chacha" version = "0.3.1" @@ -2595,6 +2585,21 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +dependencies = [ + "rand_core 0.10.1", +] + [[package]] name = "rawpointer" version = "0.2.1" @@ -2665,9 +2670,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.3" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" dependencies = [ "aho-corasick", "memchr", @@ -2677,9 +2682,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.14" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad" dependencies = [ "aho-corasick", "memchr", @@ -2688,9 +2693,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "reqwest" @@ -2727,7 +2732,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots 1.0.7", + "webpki-roots 1.0.9", ] [[package]] @@ -2760,9 +2765,9 @@ dependencies = [ [[package]] name = "rustc-hash" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" [[package]] name = "rustc_version" @@ -2788,9 +2793,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.40" +version = "0.23.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" +checksum = "3c54fcab019b409d04215d3a17cb438fd7fbf192ee61461f20f4fe18704bc138" dependencies = [ "log", "once_cell", @@ -2803,9 +2808,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.14.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" +checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046" dependencies = [ "web-time", "zeroize", @@ -2824,9 +2829,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" [[package]] name = "ryu" @@ -2932,12 +2937,12 @@ dependencies = [ [[package]] name = "sha1" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest", ] @@ -2948,7 +2953,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest", ] @@ -2979,9 +2984,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" [[package]] name = "siphasher" @@ -2997,15 +3002,15 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.15.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "socket2" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", "windows-sys 0.61.2", @@ -3060,9 +3065,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.117" +version = "2.0.119" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" dependencies = [ "proc-macro2", "quote", @@ -3098,7 +3103,7 @@ dependencies = [ "cfg-expr", "heck", "pkg-config", - "toml 1.1.2+spec-1.1.0", + "toml 1.1.3+spec-1.1.0", "version-compare", ] @@ -3115,7 +3120,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.2", + "getrandom 0.4.3", "once_cell", "rustix", "windows-sys 0.61.2", @@ -3163,9 +3168,9 @@ dependencies = [ [[package]] name = "thread_local" -version = "1.1.9" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" dependencies = [ "cfg-if", ] @@ -3182,9 +3187,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" dependencies = [ "tinyvec_macros", ] @@ -3217,7 +3222,7 @@ dependencies = [ "monostate", "onig", "paste", - "rand 0.9.4", + "rand 0.9.5", "rayon", "rayon-cond", "regex", @@ -3233,9 +3238,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.52.3" +version = "1.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +checksum = "d988bcd52dbe076d3d46903332f58c912b87a2c49b1428419a5845154762ffee" dependencies = [ "bytes", "libc", @@ -3251,9 +3256,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.7.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +checksum = "6328af13490e73a9b4694030fafd93f8c8c6a9dede33e821c3fc63eddf8042ba" dependencies = [ "proc-macro2", "quote", @@ -3284,9 +3289,9 @@ dependencies = [ [[package]] name = "toml" -version = "1.1.2+spec-1.1.0" +version = "1.1.3+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" +checksum = "53c96ecdfa941c8fc4fcaed14f99ada8ebed502eef533015095a07e3301d4c3c" dependencies = [ "indexmap", "serde_core", @@ -3294,7 +3299,7 @@ dependencies = [ "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", "toml_writer", - "winnow 1.0.3", + "winnow 1.0.4", ] [[package]] @@ -3331,14 +3336,14 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.25.12+spec-1.1.0" +version = "0.25.13+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" +checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" dependencies = [ "indexmap", "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", - "winnow 1.0.3", + "winnow 1.0.4", ] [[package]] @@ -3347,7 +3352,7 @@ version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow 1.0.3", + "winnow 1.0.4", ] [[package]] @@ -3358,9 +3363,9 @@ checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" [[package]] name = "toml_writer" -version = "1.1.1+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" [[package]] name = "tower" @@ -3587,11 +3592,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.23.2" +version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7" +checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" dependencies = [ - "getrandom 0.4.2", + "getrandom 0.4.3", "js-sys", "wasm-bindgen", ] @@ -3631,27 +3636,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.3+wasi-0.2.9" +version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" dependencies = [ - "wit-bindgen 0.57.1", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" -dependencies = [ - "wit-bindgen 0.51.0", + "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.122" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" dependencies = [ "cfg-if", "once_cell", @@ -3662,9 +3658,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.72" +version = "0.4.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f" +checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" dependencies = [ "js-sys", "wasm-bindgen", @@ -3672,9 +3668,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.122" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3682,9 +3678,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.122" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" dependencies = [ "bumpalo", "proc-macro2", @@ -3695,52 +3691,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.122" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap", - "wasm-encoder", - "wasmparser", -] - -[[package]] -name = "wasmparser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" -dependencies = [ - "bitflags", - "hashbrown 0.15.5", - "indexmap", - "semver", -] - [[package]] name = "web-sys" -version = "0.3.99" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436" +checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" dependencies = [ "js-sys", "wasm-bindgen", @@ -3762,14 +3724,14 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "webpki-roots 1.0.7", + "webpki-roots 1.0.9", ] [[package]] name = "webpki-roots" -version = "1.0.7" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" +checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" dependencies = [ "rustls-pki-types", ] @@ -4097,107 +4059,19 @@ dependencies = [ [[package]] name = "winnow" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" dependencies = [ "memchr", ] -[[package]] -name = "wit-bindgen" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] - [[package]] name = "wit-bindgen" version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck", - "indexmap", - "prettyplease", - "syn", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags", - "indexmap", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", -] - [[package]] name = "writeable" version = "0.6.3" @@ -4261,7 +4135,7 @@ dependencies = [ "hex", "nix", "ordered-stream", - "rand 0.8.6", + "rand 0.8.7", "serde", "serde_repr", "sha1", @@ -4302,18 +4176,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.50" +version = "0.8.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1" +checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.50" +version = "0.8.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639" +checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5" dependencies = [ "proc-macro2", "quote", @@ -4343,9 +4217,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.8.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" [[package]] name = "zerotrie" @@ -4382,9 +4256,9 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" [[package]] name = "zvariant" diff --git a/Cargo.toml b/Cargo.toml index 53daebd..2790c28 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ members = [ resolver = "2" [workspace.package] -version = "0.4.1" +version = "0.5.0" edition = "2021" license = "MIT" authors = ["Breadway"] diff --git a/README.md b/README.md index 6d41ee8..c7797ce 100644 --- a/README.md +++ b/README.md @@ -146,7 +146,7 @@ breadpad model-info # shows active EP and model path ## Installation ```bash -git clone https://github.com/breadway/breadpad +git clone https://git.breadway.dev/Breadway/breadpad cd breadpad cargo build --release cp target/release/breadpad ~/.local/bin/ diff --git a/breadpad-shared/Cargo.toml b/breadpad-shared/Cargo.toml index cc2e2f5..da15c41 100644 --- a/breadpad-shared/Cargo.toml +++ b/breadpad-shared/Cargo.toml @@ -26,9 +26,8 @@ regex.workspace = true ureq.workspace = true reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "json"] } ical = "0.11" -# TODO(owner): switch to tag-pinned git dependency once bread-onnx/bread-utils are merged and tagged, matching the bread-theme pattern -bread-onnx = { path = "../../bread-ecosystem-fix-worktree/bread-onnx" } -bread-utils = { path = "../../bread-ecosystem-fix-worktree/bread-utils" } +bread-onnx = { git = "https://git.breadway.dev/Breadway/bread-ecosystem", tag = "v0.3.0" } +bread-utils = { git = "https://git.breadway.dev/Breadway/bread-ecosystem", tag = "v0.3.0" } [dev-dependencies] tempfile = "3" diff --git a/breadpad/Cargo.toml b/breadpad/Cargo.toml index 4bf9b0b..aa840d2 100644 --- a/breadpad/Cargo.toml +++ b/breadpad/Cargo.toml @@ -23,5 +23,4 @@ gtk4-layer-shell.workspace = true hyprland.workspace = true dirs.workspace = true tokio.workspace = true -# TODO(owner): switch to tag-pinned git dependency once bread-utils is merged and tagged, matching the bread-theme pattern -bread-utils = { path = "../../bread-ecosystem-fix-worktree/bread-utils" } +bread-utils = { git = "https://git.breadway.dev/Breadway/bread-ecosystem", tag = "v0.3.0" } diff --git a/packaging/arch/PKGBUILD b/packaging/arch/PKGBUILD index 22511b6..d4d5b93 100644 --- a/packaging/arch/PKGBUILD +++ b/packaging/arch/PKGBUILD @@ -1,11 +1,11 @@ -# Maintainer: Breadway +# Maintainer: Breadway pkgname=breadpad pkgver=0.3.1 pkgrel=1 pkgdesc="Quick-capture scratchpad and note viewer with AI classification" arch=('x86_64') -url="https://github.com/Breadway/breadpad" +url="https://git.breadway.dev/Breadway/breadpad" license=('MIT') # Some Rust deps (ring/mlua) build vendored C/asm into static archives; makepkg's # default -flto=auto emits GCC LTO bitcode the Rust (lld) link cannot read,