Compare commits
4 commits
fde2c7571d
...
9dcc6cfc94
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9dcc6cfc94 | ||
|
|
b3e49dc753 | ||
|
|
2f737abbf6 | ||
|
|
b9c2702168 |
13 changed files with 208 additions and 52 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -28,5 +28,5 @@ svgs.txt
|
|||
Cargo.lock
|
||||
dist/
|
||||
|
||||
# Local hygiene notes (not for commit)
|
||||
CLAUDE.md
|
||||
# Local knowledge-graph cache
|
||||
graphify-out/
|
||||
|
|
|
|||
37
AGENTS.md
Normal file
37
AGENTS.md
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
# AGENTS.md — Repo hygiene
|
||||
|
||||
Scope: this file covers *repo hygiene* — branching, remotes, CI, cleanup. It is not project documentation.
|
||||
|
||||
This repo follows the branch/release workflow documented in `CONTRIBUTING.md`
|
||||
— read and follow it for any git, branch, or release work here (the
|
||||
single-trunk model, `feature/x`/`fix/x` branch naming, how RC tags work,
|
||||
etc). Don't improvise a different workflow. The short version: there is one
|
||||
long-lived branch, `main` — no `dev` or `beta` branch exists. `main`
|
||||
auto-publishes a dev-track build on every push. "Beta" and "stable" are both
|
||||
just tags, not branches: push a `vX.Y.Z-rc.N` tag to publish a beta-track
|
||||
build, push a plain `vX.Y.Z` tag to cut the signed stable release.
|
||||
"Freezing" for stabilization means pausing pushes to `main`, not moving a
|
||||
branch. This replaced an earlier three-branch (`dev`/`beta`/`main`) model
|
||||
after `main` was found to have silently rotted out of sync with `dev`/`beta`
|
||||
across most repos in this ecosystem.
|
||||
|
||||
## Remotes
|
||||
- `origin` — Forgejo (`git.breadway.dev` via Hestia, SSH) — authoritative.
|
||||
- `github` — GitHub mirror. Push both when publishing.
|
||||
|
||||
## CI
|
||||
- `check.yml` — clippy + test, triggers on push to `feature/**`/`fix/**`.
|
||||
- `dev-release.yml` — triggers on push to `main`.
|
||||
- `rc-release.yml` — triggers on `vX.Y.Z-rc.N` tag push.
|
||||
- `release.yml` — triggers on any other `v*` tag push.
|
||||
|
||||
All four run on a self-hosted runner (`hestia`) inside a pinned Arch
|
||||
container — not the host's native environment. The Containerfile/build
|
||||
script are shared across bread-ecosystem products and live in
|
||||
`bread-ecosystem/ci/`; this repo's `ci/build.sh` clones that repo at the
|
||||
sha in `ci/bread-ecosystem.rev` (deliberately pinned, not `main`) and
|
||||
delegates to it. Nothing runs automatically on plain commits or PRs
|
||||
beyond what's listed.
|
||||
|
||||
## Don't
|
||||
- Don't embed credentials in remote URLs — SSH or a credential helper only.
|
||||
42
Cargo.lock
generated
42
Cargo.lock
generated
|
|
@ -305,21 +305,31 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "bread-screenshots"
|
||||
version = "0.3.1"
|
||||
source = "git+https://git.breadway.dev/Breadway/bread-ecosystem?branch=main#f86e299f4a0ea73ff485cd84923b986ddcc8242e"
|
||||
source = "git+https://git.breadway.dev/Breadway/bread-ecosystem?rev=69ce2d67a8de8a09c064aa9d7ff99b46656f0b1d#69ce2d67a8de8a09c064aa9d7ff99b46656f0b1d"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bread-utils",
|
||||
"bread-utils 0.3.1 (git+https://git.breadway.dev/Breadway/bread-ecosystem?rev=69ce2d67a8de8a09c064aa9d7ff99b46656f0b1d)",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bread-shared"
|
||||
version = "0.7.0"
|
||||
source = "git+https://git.breadway.dev/Breadway/bread?tag=v0.7.0#22e34e2cf2202305d7960759dfccb54dc79f948b"
|
||||
dependencies = [
|
||||
"dirs 5.0.1",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"toml 0.8.23",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bread-theme"
|
||||
version = "0.3.1"
|
||||
source = "git+https://git.breadway.dev/Breadway/bread-ecosystem?branch=main#f86e299f4a0ea73ff485cd84923b986ddcc8242e"
|
||||
source = "git+https://git.breadway.dev/Breadway/bread-ecosystem?tag=v0.7.1#db2fa3c4b4c1e6933bc5cf62a236d05972fdc886"
|
||||
dependencies = [
|
||||
"dirs 5.0.1",
|
||||
"gtk4",
|
||||
"libadwaita",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
|
@ -327,7 +337,18 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "bread-utils"
|
||||
version = "0.3.1"
|
||||
source = "git+https://git.breadway.dev/Breadway/bread-ecosystem?branch=main#f86e299f4a0ea73ff485cd84923b986ddcc8242e"
|
||||
source = "git+https://git.breadway.dev/Breadway/bread-ecosystem?tag=v0.7.1#db2fa3c4b4c1e6933bc5cf62a236d05972fdc886"
|
||||
dependencies = [
|
||||
"bread-shared",
|
||||
"dirs 5.0.1",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bread-utils"
|
||||
version = "0.3.1"
|
||||
source = "git+https://git.breadway.dev/Breadway/bread-ecosystem?rev=69ce2d67a8de8a09c064aa9d7ff99b46656f0b1d#69ce2d67a8de8a09c064aa9d7ff99b46656f0b1d"
|
||||
dependencies = [
|
||||
"dirs 5.0.1",
|
||||
"serde",
|
||||
|
|
@ -360,6 +381,7 @@ version = "0.3.4"
|
|||
dependencies = [
|
||||
"anyhow",
|
||||
"bread-screenshots",
|
||||
"bread-utils 0.3.1 (git+https://git.breadway.dev/Breadway/bread-ecosystem?tag=v0.7.1)",
|
||||
"breadpad-shared",
|
||||
"chrono",
|
||||
"dirs 5.0.1",
|
||||
|
|
@ -587,7 +609,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -951,7 +973,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1262,7 +1284,7 @@ dependencies = [
|
|||
"gobject-sys",
|
||||
"libc",
|
||||
"system-deps",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2756,7 +2778,7 @@ dependencies = [
|
|||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -3080,7 +3102,7 @@ dependencies = [
|
|||
"getrandom 0.4.2",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
49
EVENTS.md
Normal file
49
EVENTS.md
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
# breadpad — bread event integration
|
||||
|
||||
breadpad is a standalone capture popup: it works exactly the same with or
|
||||
without `breadd` running. When breadd *is* present, the `breadpad` binary
|
||||
publishes events into the shared bread automation fabric after actions that
|
||||
already happened. See the parent `bread` repo's `Documentation.md` —
|
||||
specifically its "Namespaces" and "Integrating a bread\* app" sections — for
|
||||
the general convention this follows.
|
||||
|
||||
App id: **`pad`**. Transport: `bread-utils`'s `bread_client` module
|
||||
(feature `bread-client`) — the capture popup links it directly. breadpad is
|
||||
short-lived (one popup, or one `fire <id>` invocation from the existing
|
||||
systemd user timer), so each `emit` is its own short-lived connection.
|
||||
There is no long-running breadpad daemon and therefore no command
|
||||
subscription.
|
||||
|
||||
`breadman` (the viewer) does not emit or subscribe. Notes created or edited
|
||||
there are not quick-capture, and it is not on the reminder-fire path.
|
||||
|
||||
## Events published (`bread.pad.*`)
|
||||
|
||||
| Event | Data | When |
|
||||
|-------|------|------|
|
||||
| `bread.pad.captured` | `{ "id": "<note id>" }` | The capture popup saved a note successfully (`Store::save_note` returned `Ok`). Not emitted when the field is empty, the window is dismissed, classification-only preview happens, or the write fails. |
|
||||
| `bread.pad.reminder.due` | `{ "id": "<note id>" }` | `breadpad fire <id>` decided the reminder is due (`Scheduler::fire` returned true) and is about to show the reminder window. This is the existing in-process systemd-timer hook (`breadpad-reminder-<id>.timer` → `breadpad fire <id>`), not a new daemon. Not emitted when the note is missing, the fire is outside the missed-grace window, or the reminder window is opened as a `--screenshot` sample. |
|
||||
|
||||
Note bodies are never included in the payload — only the local note id.
|
||||
Notes stay in `~/.local/share/breadpad/notes.jsonl`; the event bus is for
|
||||
*notifications about* capture and due reminders, not a channel for note
|
||||
content.
|
||||
|
||||
## Commands honored (`bread.command.pad.*`)
|
||||
|
||||
None. breadpad has no persistent process that could subscribe, and the
|
||||
actions a command verb would map to already exist as local CLI / keybind
|
||||
paths (`breadpad` for capture, `breadpad fire <id>` for the reminder
|
||||
window, `breadman` for viewing and editing). Stubbing `capture` / `snooze`
|
||||
/ `done` on the bus without a subscriber (or inventing a daemon just to
|
||||
hold one) would be a no-op dressed up as an API. If breadpad later grows a
|
||||
long-running piece that can honor a verb for real, add the verb then.
|
||||
|
||||
## Fail-safe behavior
|
||||
|
||||
- If breadd isn't installed or isn't running, `emit` is a silent no-op
|
||||
(`BreadClient::emit` never blocks or errors the caller). Capture, save,
|
||||
systemd timers, and the reminder window are entirely unaffected.
|
||||
- There is no command subscription to reconnect. Restarting breadd does
|
||||
not require restarting breadpad; the next real capture or fire will emit
|
||||
again if breadd is reachable at that moment.
|
||||
32
README.md
32
README.md
|
|
@ -32,11 +32,11 @@ breadman GTK4 note viewer / manager
|
|||
|
||||
### Classification
|
||||
|
||||
Every note passes through a three-tier pipeline at capture time:
|
||||
Every note passes through a three-tier pipeline at capture time. **Tier 1 is the only tier that ships with breadpad** — the ONNX classifier model is not bundled, and Ollama is optional. Capture works without either.
|
||||
|
||||
1. **Rule-based parser** — always runs first; handles time extraction ("at 7pm", "in 30 minutes", "tomorrow morning", "next Friday"), recurrence ("every Sunday at 9pm", "every weekday morning"), and strong type signals ("?" → question, "idea:" prefix → idea, action verbs → todo). High-confidence results skip the remaining tiers entirely.
|
||||
2. **Small local ONNX model** — runs when Tier 1 can't confidently assign a type. Responsible for type classification only; Tier 1's extracted time, recurrence rule, and cleaned body are always preserved.
|
||||
3. **Large local model via Ollama** — runs only when Tier 2 confidence falls below a configurable threshold. Communicates with a locally running Ollama instance over HTTP. If Ollama is unreachable, the Tier 2 result is used. No cloud APIs are involved.
|
||||
1. **Rule-based parser** — always runs first; handles time extraction ("at 7pm", "in 30 minutes", "tomorrow morning", "next Friday"), recurrence ("every Sunday at 9pm", "every weekday morning"), and strong type signals ("?" → question, "idea:" prefix → idea, action verbs → todo). High-confidence results skip the remaining tiers entirely. This is the default path; no model files required.
|
||||
2. **Small local ONNX model (optional, not shipped)** — runs when Tier 1 can't confidently assign a type *and* you have dropped in your own classifier. Responsible for type classification only; Tier 1's extracted time, recurrence rule, and cleaned body are always preserved.
|
||||
3. **Large local model via Ollama (optional)** — runs only when Tier 2 confidence falls below a configurable threshold. Communicates with a locally running Ollama instance over HTTP. If Ollama is unreachable, the previous tier's result is used. No cloud APIs are involved.
|
||||
|
||||
Manual override always available — the AI-assigned type is shown as a chip you can tap to change before saving.
|
||||
|
||||
|
|
@ -104,11 +104,11 @@ Always runs. Handles:
|
|||
|
||||
Returns a calibrated confidence. If ≥ 0.82, Tiers 2 and 3 are skipped.
|
||||
|
||||
#### Tier 2 — Small local ONNX model
|
||||
#### Tier 2 — Small local ONNX model (optional, not shipped)
|
||||
|
||||
Runs when Tier 1 confidence is below threshold. Responsible for **type classification only** — Tier 1's extracted time, recurrence rule, and cleaned body are always preserved.
|
||||
Runs when Tier 1 confidence is below threshold **and** a compatible `classifier.onnx` + `tokenizer.json` are present. Responsible for **type classification only** — Tier 1's extracted time, recurrence rule, and cleaned body are always preserved.
|
||||
|
||||
Invoked via `ort` (ONNX Runtime Rust bindings, `load-dynamic`) on the CPU. Requires an external `libonnxruntime.so`; set `model.ort_dylib_path` in `breadpad.toml` or let breadpad auto-discover it via `ORT_DYLIB_PATH`.
|
||||
Invoked via `ort` (ONNX Runtime Rust bindings, `load-dynamic`) on the CPU. Requires an external `libonnxruntime.so`; set `model.ort_dylib_path` in `breadpad.toml` or let breadpad auto-discover it via `ORT_DYLIB_PATH`. Without a model file or runtime library, Tier 2 is skipped and Tier 1 (plus optional Tier 3) still works.
|
||||
|
||||
#### Tier 3 — Large local model via Ollama
|
||||
|
||||
|
|
@ -123,7 +123,7 @@ If Ollama is unreachable or returns an invalid response, breadpad logs a warning
|
|||
~/.local/share/breadpad/model/tokenizer.json
|
||||
```
|
||||
|
||||
breadpad ships without a bundled model. Drop a compatible ONNX classifier and `tokenizer.json` at those paths, then configure `model.ort_dylib_path` to point at your ONNX Runtime library.
|
||||
**breadpad does not ship a classifier model.** Tier 1 rules work with no extra files. If you want Tier 2, drop a compatible ONNX classifier and `tokenizer.json` at those paths and point `model.ort_dylib_path` at your ONNX Runtime library.
|
||||
|
||||
```bash
|
||||
breadpad model-info # shows active EP and model path
|
||||
|
|
@ -138,7 +138,7 @@ breadpad model-info # shows active EP and model path
|
|||
- D-Bus session bus (for notifications)
|
||||
- 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 2 (ONNX classifier, optional):** A model you supply yourself (`classifier.onnx` + `tokenizer.json`) and an external `libonnxruntime.so`. Set `model.ort_dylib_path` in `breadpad.toml`, or set `ORT_DYLIB_PATH` in your environment. Neither the model nor the runtime is shipped. Without them, Tier 2 is disabled; Tier 1 (and Tier 3, if Ollama is running) 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.
|
||||
|
||||
---
|
||||
|
|
@ -146,15 +146,19 @@ 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/
|
||||
cp target/release/breadman ~/.local/bin/
|
||||
```
|
||||
|
||||
# Place your ONNX classifier and tokenizer in the model directory
|
||||
That's enough for capture + viewing. Tier 1 classification works out of the box. The ONNX classifier is **not** included — only add it if you want Tier 2:
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.local/share/breadpad/model
|
||||
# Then set model.ort_dylib_path in breadpad.toml to your libonnxruntime.so
|
||||
# Drop your own classifier.onnx + tokenizer.json in that directory, then
|
||||
# set model.ort_dylib_path in breadpad.toml to your libonnxruntime.so
|
||||
```
|
||||
|
||||
On Arch Linux, install GTK4 dependencies first:
|
||||
|
|
@ -245,10 +249,10 @@ breadpad --no-classify
|
|||
breadpad --status
|
||||
```
|
||||
|
||||
Hyprland keybind:
|
||||
Hyprland keybind (BOS default is Super+U; bind whatever you want):
|
||||
|
||||
```
|
||||
bind = $mainMod, N, exec, breadpad
|
||||
bind = $mainMod, U, exec, breadpad
|
||||
```
|
||||
|
||||
### breadman (viewer)
|
||||
|
|
|
|||
|
|
@ -12,11 +12,10 @@ path = "src/main.rs"
|
|||
[dependencies]
|
||||
breadpad-shared = { path = "../breadpad-shared" }
|
||||
# Capture primitives for `--screenshot` mode — see src/screenshot.rs.
|
||||
bread-screenshots = { git = "https://git.breadway.dev/Breadway/bread-ecosystem", branch = "main" }
|
||||
# libadwaita-based settings-screen components (see src/views/settings.rs) —
|
||||
# on "dev", not breadpad-shared's tag-pinned copy, since the `adw` feature
|
||||
# doesn't exist on a tagged release yet.
|
||||
bread-theme = { git = "https://git.breadway.dev/Breadway/bread-ecosystem", branch = "main", features = ["adw"] }
|
||||
# v0.7.1 predates this crate; rev-pin so this is not `branch = "main"`.
|
||||
bread-screenshots = { git = "https://git.breadway.dev/Breadway/bread-ecosystem", rev = "69ce2d67a8de8a09c064aa9d7ff99b46656f0b1d" }
|
||||
# `adw` / `chip` landed after v0.7.1 — breadman keeps local shims (see src/theme_widgets.rs).
|
||||
bread-theme = { git = "https://git.breadway.dev/Breadway/bread-ecosystem", tag = "v0.7.1", features = ["gtk"] }
|
||||
libadwaita = { version = "0.9", features = ["v1_7"] }
|
||||
anyhow.workspace = true
|
||||
tracing.workspace = true
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
//! flagged in design review as the weakest surface in the app). AdwDialog
|
||||
//! gives us the scrim, the title, and correct modal anchoring for free.
|
||||
|
||||
use bread_theme::adw;
|
||||
use breadpad_shared::{
|
||||
parser::parse_rule_based,
|
||||
scheduler::Scheduler,
|
||||
|
|
@ -54,25 +53,25 @@ pub fn open_editor(
|
|||
.margin_end(16)
|
||||
.build();
|
||||
|
||||
let group = adw::preferences_group("Details", None);
|
||||
let group = libadwaita::PreferencesGroup::builder().title("Details").build();
|
||||
|
||||
let body_row = libadwaita::EntryRow::builder().title("Body").build();
|
||||
body_row.set_text(¬e.body);
|
||||
group.add(&body_row);
|
||||
|
||||
let type_row = adw::action_row("Type", None);
|
||||
let type_row = libadwaita::ActionRow::builder().title("Type").build();
|
||||
let type_pill_box = gtk4::Box::builder().orientation(gtk4::Orientation::Horizontal).spacing(4).valign(gtk4::Align::Center).build();
|
||||
let selected_type: Rc<RefCell<String>> = Rc::new(RefCell::new(note.note_type.as_str().to_string()));
|
||||
let type_pills: Vec<(gtk4::Button, &'static str)> = NoteType::all_builtin().iter().map(|&name| (bread_theme::gtk::chip(name), name)).collect();
|
||||
let type_pills: Vec<(gtk4::Button, &'static str)> = NoteType::all_builtin().iter().map(|&name| (crate::theme_widgets::chip(name), name)).collect();
|
||||
for (btn, name) in &type_pills {
|
||||
bread_theme::gtk::set_chip_active(btn, *name == selected_type.borrow().as_str());
|
||||
crate::theme_widgets::set_chip_active(btn, *name == selected_type.borrow().as_str());
|
||||
let sel = selected_type.clone();
|
||||
let name = *name;
|
||||
let all_btns: Vec<gtk4::Button> = type_pills.iter().map(|(b, _)| b.clone()).collect();
|
||||
btn.connect_clicked(move |clicked| {
|
||||
*sel.borrow_mut() = name.to_string();
|
||||
for b in &all_btns { bread_theme::gtk::set_chip_active(b, false); }
|
||||
bread_theme::gtk::set_chip_active(clicked, true);
|
||||
for b in &all_btns { crate::theme_widgets::set_chip_active(b, false); }
|
||||
crate::theme_widgets::set_chip_active(clicked, true);
|
||||
});
|
||||
type_pill_box.append(btn);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ use std::sync::Arc;
|
|||
|
||||
mod editor;
|
||||
mod screenshot;
|
||||
mod theme_widgets;
|
||||
mod views;
|
||||
|
||||
// ── Args ─────────────────────────────────────────────────────────────────────
|
||||
|
|
@ -357,7 +358,7 @@ fn build_app_window(
|
|||
// Needed once before constructing any adw:: widget (see views::settings) —
|
||||
// also forces dark mode, since bread-theme's palette is a fixed dark base
|
||||
// regardless of the system GTK preference.
|
||||
bread_theme::adw::init();
|
||||
theme_widgets::init_adw();
|
||||
|
||||
let store = Arc::new(Store::new()?);
|
||||
let notes = store.load_all()?;
|
||||
|
|
@ -627,9 +628,8 @@ fn show_add_note_window(parent: >k4::ApplicationWindow, state: AppState, prese
|
|||
.build();
|
||||
vbox.append(&body_entry);
|
||||
|
||||
// Type pills — same bread_theme::gtk::chip widget the editor dialog and
|
||||
// settings screen use, instead of three different type-picker widgets
|
||||
// across the app.
|
||||
// Type pills — same chip widget the editor dialog and settings screen
|
||||
// use, instead of three different type-picker widgets across the app.
|
||||
vbox.append(>k4::Label::builder().label("Type").xalign(0.0).build());
|
||||
let chip_box = gtk4::Box::builder()
|
||||
.orientation(gtk4::Orientation::Horizontal)
|
||||
|
|
@ -638,17 +638,17 @@ fn show_add_note_window(parent: >k4::ApplicationWindow, state: AppState, prese
|
|||
let selected_type: Rc<RefCell<NoteType>> = Rc::new(RefCell::new(preselect.clone()));
|
||||
let chips: Vec<(gtk4::Button, NoteType)> = NoteType::all_builtin()
|
||||
.iter()
|
||||
.map(|&name| (bread_theme::gtk::chip(name), NoteType::from_str(name)))
|
||||
.map(|&name| (theme_widgets::chip(name), NoteType::from_str(name)))
|
||||
.collect();
|
||||
for (btn, nt) in &chips {
|
||||
bread_theme::gtk::set_chip_active(btn, *nt == preselect);
|
||||
theme_widgets::set_chip_active(btn, *nt == preselect);
|
||||
let sel = selected_type.clone();
|
||||
let nt_c = nt.clone();
|
||||
let all_btns: Vec<gtk4::Button> = chips.iter().map(|(b, _)| b.clone()).collect();
|
||||
btn.connect_clicked(move |clicked| {
|
||||
*sel.borrow_mut() = nt_c.clone();
|
||||
for b in &all_btns { bread_theme::gtk::set_chip_active(b, false); }
|
||||
bread_theme::gtk::set_chip_active(clicked, true);
|
||||
for b in &all_btns { theme_widgets::set_chip_active(b, false); }
|
||||
theme_widgets::set_chip_active(clicked, true);
|
||||
});
|
||||
chip_box.append(btn);
|
||||
}
|
||||
|
|
|
|||
23
breadman/src/theme_widgets.rs
Normal file
23
breadman/src/theme_widgets.rs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
//! Local stand-ins for `bread_theme::gtk::{chip, set_chip_active}` and
|
||||
//! `bread_theme::adw::init`, which are not on bread-theme v0.7.1.
|
||||
|
||||
use gtk4::prelude::*;
|
||||
|
||||
pub fn chip(label: &str) -> gtk4::Button {
|
||||
gtk4::Button::builder().label(label).css_classes(["chip"]).build()
|
||||
}
|
||||
|
||||
pub fn set_chip_active(chip: &impl IsA<gtk4::Widget>, active: bool) {
|
||||
if active {
|
||||
chip.add_css_class("active");
|
||||
} else {
|
||||
chip.remove_css_class("active");
|
||||
}
|
||||
}
|
||||
|
||||
/// Initializes libadwaita and forces dark mode (bread-theme's palette is a
|
||||
/// fixed dark base regardless of the system GTK preference).
|
||||
pub fn init_adw() {
|
||||
libadwaita::init().expect("failed to initialize libadwaita");
|
||||
libadwaita::StyleManager::default().set_color_scheme(libadwaita::ColorScheme::ForceDark);
|
||||
}
|
||||
|
|
@ -92,10 +92,10 @@ pub fn build(cfg: &Config, on_save: impl Fn(Config) + 'static) -> gtk4::Scrolled
|
|||
let selected_type: Rc<RefCell<String>> = Rc::new(RefCell::new(cfg.settings.default_type.clone()));
|
||||
let type_pills: Vec<(gtk4::Button, &'static str)> = NoteType::all_builtin()
|
||||
.iter()
|
||||
.map(|&name| (bread_theme::gtk::chip(name), name))
|
||||
.map(|&name| (crate::theme_widgets::chip(name), name))
|
||||
.collect();
|
||||
for (btn, name) in &type_pills {
|
||||
bread_theme::gtk::set_chip_active(btn, *name == selected_type.borrow().as_str());
|
||||
crate::theme_widgets::set_chip_active(btn, *name == selected_type.borrow().as_str());
|
||||
type_pill_box.append(btn);
|
||||
}
|
||||
general_list.append(&field_row("Default type", None, &type_pill_box));
|
||||
|
|
@ -260,8 +260,8 @@ pub fn build(cfg: &Config, on_save: impl Fn(Config) + 'static) -> gtk4::Scrolled
|
|||
let all_btns: Vec<gtk4::Button> = type_pills.iter().map(|(b, _)| b.clone()).collect();
|
||||
btn.connect_clicked(move |clicked| {
|
||||
*sel.borrow_mut() = name.to_string();
|
||||
for b in &all_btns { bread_theme::gtk::set_chip_active(b, false); }
|
||||
bread_theme::gtk::set_chip_active(clicked, true);
|
||||
for b in &all_btns { crate::theme_widgets::set_chip_active(b, false); }
|
||||
crate::theme_widgets::set_chip_active(clicked, true);
|
||||
apply_now();
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ authors.workspace = true
|
|||
|
||||
|
||||
[dependencies]
|
||||
bread-theme = { git = "https://git.breadway.dev/Breadway/bread-ecosystem", branch = "main", features = ["gtk"] }
|
||||
bread-theme = { git = "https://git.breadway.dev/Breadway/bread-ecosystem", tag = "v0.7.1", features = ["gtk"] }
|
||||
anyhow.workspace = true
|
||||
tracing.workspace = true
|
||||
serde.workspace = true
|
||||
|
|
|
|||
|
|
@ -12,7 +12,11 @@ path = "src/main.rs"
|
|||
[dependencies]
|
||||
breadpad-shared = { path = "../breadpad-shared" }
|
||||
# Capture primitives for `--screenshot` mode — see src/screenshot.rs.
|
||||
bread-screenshots = { git = "https://git.breadway.dev/Breadway/bread-ecosystem", branch = "main" }
|
||||
# v0.7.1 predates this crate; rev-pin so this is not `branch = "main"`.
|
||||
bread-screenshots = { git = "https://git.breadway.dev/Breadway/bread-ecosystem", rev = "69ce2d67a8de8a09c064aa9d7ff99b46656f0b1d" }
|
||||
# Bread event bus: emit after a real capture/reminder fire. Fail-silent if
|
||||
# breadd is down. See EVENTS.md.
|
||||
bread-utils = { git = "https://git.breadway.dev/Breadway/bread-ecosystem", tag = "v0.7.1", features = ["bread-client"] }
|
||||
anyhow.workspace = true
|
||||
ort.workspace = true
|
||||
tracing.workspace = true
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ use breadpad_shared::{
|
|||
store::Store,
|
||||
types::{Note, NoteType},
|
||||
};
|
||||
use bread_utils::bread_client::BreadClient;
|
||||
use gtk4::{glib, prelude::*};
|
||||
use gtk4_layer_shell::{Edge, KeyboardMode, Layer, LayerShell};
|
||||
use std::cell::RefCell;
|
||||
|
|
@ -16,6 +17,21 @@ use std::sync::{Arc, Once};
|
|||
|
||||
mod screenshot;
|
||||
|
||||
/// Sibling-app id in `bread_shared::apps::KNOWN_APPS`. Events are `bread.pad.*`.
|
||||
const APP_ID: &str = "pad";
|
||||
|
||||
/// `bread.pad.captured` after a successful quick-capture save. Fire-and-forget:
|
||||
/// `BreadClient::emit` is a silent no-op when breadd is down or missing.
|
||||
fn emit_captured(id: &str) {
|
||||
BreadClient::connect(APP_ID).emit("bread.pad.captured", serde_json::json!({ "id": id }));
|
||||
}
|
||||
|
||||
/// `bread.pad.reminder.due` when `breadpad fire <id>` actually shows a reminder
|
||||
/// (the existing systemd-timer hook — not a new daemon). Same fail-silent emit.
|
||||
fn emit_reminder_due(id: &str) {
|
||||
BreadClient::connect(APP_ID).emit("bread.pad.reminder.due", serde_json::json!({ "id": id }));
|
||||
}
|
||||
|
||||
static ORT_INIT: Once = Once::new();
|
||||
|
||||
fn init_ort_once(cfg: &Config) {
|
||||
|
|
@ -331,6 +347,8 @@ fn cmd_fire(id: &str, cfg: &Config) -> Result<()> {
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
emit_reminder_due(¬e.id);
|
||||
|
||||
// Schedule next recurrence before showing UI
|
||||
if note.rrule.is_some() {
|
||||
if let Some(next) = Scheduler::next_recurrence(¬e, &cfg.reminders.default_morning) {
|
||||
|
|
@ -911,6 +929,7 @@ fn save_note_classified(
|
|||
tracing::error!("failed to save note: {}", e);
|
||||
return;
|
||||
}
|
||||
emit_captured(¬e.id);
|
||||
if note.time.is_some() {
|
||||
if let Err(e) = Scheduler::schedule(¬e) {
|
||||
tracing::warn!("failed to schedule reminder: {}", e);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue