No description
Find a file
2026-06-06 23:20:07 +08:00
.github/workflows fix: add missing build deps for hestia (Ubuntu) runner 2026-06-06 23:20:07 +08:00
breadman Prepare repo for GitHub publication 2026-06-06 12:25:40 +08:00
breadpad Prepare repo for GitHub publication 2026-06-06 12:25:40 +08:00
breadpad-shared Refactor theme onto bread-theme; add bakery.toml and release workflow 2026-06-06 22:31:38 +08:00
breadpad-test Prepare repo for GitHub publication 2026-06-06 12:25:40 +08:00
.gitignore Prepare repo for GitHub publication 2026-06-06 12:25:40 +08:00
bakery.toml Refactor theme onto bread-theme; add bakery.toml and release workflow 2026-06-06 22:31:38 +08:00
breadpad.example.toml Prepare repo for GitHub publication 2026-06-06 12:25:40 +08:00
Cargo.lock Refactor theme onto bread-theme; add bakery.toml and release workflow 2026-06-06 22:31:38 +08:00
Cargo.toml Prepare repo for GitHub publication 2026-06-06 12:25:40 +08:00
LICENSE Prepare repo for GitHub publication 2026-06-06 12:25:40 +08:00
README.md Prepare repo for GitHub publication 2026-06-06 12:25:40 +08:00

breadpad / breadman

A quick-capture scratchpad and structured note viewer for Hyprland / Wayland, with AI-powered classification, reminders, recurrence, and snooze.

Two entry points, one binary, one shared workspace:

Binary Purpose
breadpad Layer-shell capture popup — type a note, press Enter, done
breadman Full note viewer and manager

Workspace layout

breadpad-shared   shared types, storage, classification, scheduler
breadpad          GTK4 layer-shell capture popup
breadman          GTK4 note viewer / manager

Features

Capture (breadpad)

  • Layer-shell popup, centered, keyboard-exclusive — appears instantly on your keybind
  • Single text field; press Enter or click to save, Escape to dismiss
  • Optional manual type override before saving (defaults to AI classification)
  • Timestamp and active Hyprland workspace recorded automatically

Classification

Every note passes through a three-tier pipeline at capture time:

  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.

Manual override always available — the AI-assigned type is shown as a chip you can tap to change before saving.

Note types (built-in)

Type Example
todo "buy milk on the way home"
reminder "pack calculator in bag at 7pm"
idea "what if breadman had a calendar view"
note "meeting went well, follow up Friday"
question "why does nmcli drop on suspend?"

User-defined tags can be added freely on top of the built-in types.

Reminders, recurrence, and snooze

  • 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
  • Missed reminders — if the system was off or suspended at the scheduled time, the reminder fires on next login

Viewer (breadman)

  • Sidebar with one entry per type + "All" and "Upcoming"
  • Each note card shows: body, type chip, timestamp, workspace tag, recurrence badge if set
  • Upcoming view: chronological list of all pending reminders and todos with times
  • Inline editing — click any card to edit body, type, time, or recurrence
  • Mark todo/reminder as done; done items move to an archive accessible via a toggle
  • Search across all notes (full-text, instant)
  • Sort: newest first (default)

Theming

  • Reads ~/.cache/wal/colors.json (pywal) on startup — matches the rest of the bread ecosystem
  • Falls back to Catppuccin Mocha
  • CSS override: ~/.config/breadpad/style.css
  • SIGHUP reloads theme at runtime

Storage

Notes are stored as JSONL at ~/.local/share/breadpad/notes.jsonl — one JSON object per line, human-readable, easy to back up or script against.

{"id":"a1b2c3","body":"Pack calculator in bag","type":"reminder","time":"2026-05-25T19:00:00Z","rrule":null,"done":false,"workspace":"1","created":"2026-05-25T18:45:00Z","snoozed_until":null,"completed":null,"tags":[],"caldav_uid":null}
{"id":"d4e5f6","body":"Look into relm4 reactive patterns","type":"idea","time":null,"rrule":null,"done":false,"workspace":"2","created":"2026-05-25T14:10:00Z","snoozed_until":null,"completed":null,"tags":[],"caldav_uid":null}

Completed notes are never deleted — they gain "done": true and a "completed" timestamp. A separate ~/.local/share/breadpad/archive.jsonl is written periodically for notes older than 30 days.


AI classification

Three-tier pipeline

Tier 1 — Rule-based parser

Always runs. Handles:

  • Time extraction: "at 7pm", "in 30 minutes", "tomorrow morning", "next Friday at 9am"
  • Recurrence: "every Sunday at 9pm", "every weekday morning" → stored as RRULE
  • Type signals: leading "?" or "why/how/what" → question; "idea:" prefix or "what if" → idea; action verbs → todo; time present → reminder

Returns a calibrated confidence. If ≥ 0.82, Tiers 2 and 3 are skipped.

Tier 2 — Small local ONNX model

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.

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.

Tier 3 — Large local model via Ollama

Runs only when Tier 2 confidence falls below model.ollama.confidence_threshold (default 0.6). Sends a structured prompt to a locally running Ollama instance over HTTP and parses the JSON response for type, body, and confidence. The Ollama model runs on the iGPU via Ollama's own backend — breadpad does not manage GPU allocation for this tier.

If Ollama is unreachable or returns an invalid response, breadpad logs a warning and uses the Tier 2 result. No cloud APIs are used anywhere.

Model location (Tier 2)

~/.local/share/breadpad/model/classifier.onnx
~/.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 model-info   # shows active EP and model path

Requirements

  • Linux with a running Hyprland compositor
  • GTK4 (≥ 4.12) + gtk4-layer-shell
  • 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 3 only (optional): Ollama running locally with your chosen model pulled (ollama pull llama3.2:3b). Tier 3 is silently skipped if Ollama is not running.

Installation

git clone https://github.com/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
mkdir -p ~/.local/share/breadpad/model
# Then set model.ort_dylib_path in breadpad.toml to your libonnxruntime.so

On Arch Linux, install GTK4 dependencies first:

sudo pacman -S gtk4 gtk4-layer-shell

Configuration

On first run, breadpad writes ~/.config/breadpad/breadpad.toml:

[settings]
default_type = "note"          # fallback type if classification is skipped
workspace_tag = true           # tag notes with active Hyprland workspace
snooze_options = ["15m", "1h", "tomorrow_morning"]  # shown in notification actions
archive_after_days = 30

[model]
path = "~/.local/share/breadpad/model/classifier.onnx"
tokenizer = "~/.local/share/breadpad/model/tokenizer.json"
ort_dylib_path = ""              # optional: explicit path to libonnxruntime.so; auto-discovered when empty

[model.ollama]
endpoint = "http://localhost:11434"
model = "llama3.2:3b"          # 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

[reminders]
default_morning = "08:00"      # what "tomorrow morning" resolves to
missed_grace_minutes = 60      # how long after boot to still fire a missed reminder

Usage

breadpad (capture)

# Open the capture popup (bind this to a key in hyprland.conf)
breadpad

# Open with a pre-selected type
breadpad --type todo

# Skip AI classification (save as plain note)
breadpad --no-classify

# Show model and storage status
breadpad --status

Hyprland keybind:

bind = $mainMod, N, exec, breadpad

breadman (viewer)

# Open the note viewer
breadman

# Open directly to a specific type view
breadman --view todo
breadman --view upcoming

# Mark a note done by ID (scriptable)
breadman done <id>

# List upcoming reminders in the terminal
breadman upcoming --plain

Scheduler

breadpad manages reminders via systemd user timers. Each scheduled note gets a transient timer unit:

breadpad-reminder-<id>.timer
breadpad-reminder-<id>.service

The service unit runs breadpad fire <id>, which sends a notify-send notification with snooze actions. Snoozing writes the new time back to the note and creates a replacement timer. Recurring notes create the next timer immediately on fire.

You can inspect pending timers:

systemctl --user list-timers 'breadpad-*'

Testing

breadpad-test is a CLI test harness for the classification pipeline. It runs a JSON corpus of labelled inputs through any tier of the pipeline and reports pass/fail.

# Run Tier 1 (rule-based only) — fast, no model needed
breadpad-test run

# See only failing cases
breadpad-test run --format failures

# Run Tier 2 (+ ONNX model)
breadpad-test run --tier 2

# Run full pipeline including Ollama
breadpad-test run --tier all

# Machine-readable output
breadpad-test run --format json

Corpus format

Default path: breadpad-test/corpus.json. Override with --corpus <path>.

[
  {
    "input": "pack my calculator in my bag tonight",
    "expected_type": "todo",
    "expected_time": null,
    "expected_body": "pack my calculator in my bag",
    "expected_rrule": null,
    "notes": "no time specified, should not infer one"
  }
]
  • expected_timeHH:MM; date component is ignored so tests are never date-sensitive
  • expected_rrule — matched as substring of the actual RRULE string
  • Any null field is skipped — only non-null fields are asserted

Tier modes

--tier What runs
1 (default) Tier 1 rule-based parser only — no model required
2 Tiers 1 + 2 (ONNX classifier)
3 / all Full pipeline including Tier 3 Ollama

Corpus management

# Interactively add an entry
breadpad-test add

# Show entry #5 and the pipeline's actual output
breadpad-test show 5

# Open corpus file in $EDITOR at entry #5
breadpad-test edit 5

Typical tuning workflow

# 1. See what Tier 1 gets wrong
breadpad-test run --tier 1 --format failures

# 2. Edit parser.rs, then rerun
cargo build -p breadpad-shared && breadpad-test run --tier 1 --format failures

# 3. Once Tier 1 is stable, audit Tier 2 regressions
breadpad-test run --tier 2 --format failures

Nextcloud Calendar integration

breadpad can push scheduled notes and recurring reminders to a CalDAV calendar (Nextcloud or any RFC 4791-compliant server). No cloud APIs are used — everything goes directly to your own server over HTTPS.

What gets pushed

Notes with a scheduled time (time field) or recurrence rule (rrule) are pushed as VEVENT entries when saved. Notes without a time are not pushed. Deleting a note also deletes the corresponding calendar event.

Configuration

Add a [calendar] section to ~/.config/breadpad/breadpad.toml:

[calendar]
enabled = true
url = "https://nextcloud.example.com/remote.php/dav/calendars/you/breadpad/"
username = "you"
password = "app-password-here"   # use a Nextcloud app password, not your login password

The calendar must already exist on the server. Create it in the Nextcloud Calendar app before enabling this integration.

CLI commands

# Verify the CalDAV connection and credentials
breadpad calendar test

# List CalDAV UIDs for all scheduled notes (queries the server if enabled, local store if not)
breadpad calendar list-uid

# Show the CalDAV UID for a specific note by its local ID
breadpad calendar list-uid <note-id>

Event format

Each note is pushed as a VEVENT with:

  • UID<note-id>@breadpad (stable and deterministic)
  • SUMMARY — note body
  • DTSTART / DTEND — scheduled time (or creation time for recurring notes without a fixed start)
  • RRULE — recurrence rule if set
  • DESCRIPTIONtype=<note-type>

Security note

Store your CalDAV password using a Nextcloud app password rather than your account password. App passwords can be revoked individually from the Nextcloud security settings.


Module layout

Crate / module Responsibility
breadpad-shared/src/types.rs Note, NoteType, RecurrenceRule, SnoozeState
breadpad-shared/src/store.rs JSONL read/write, atomic saves, archive rotation
breadpad-shared/src/classifier.rs Three-tier pipeline orchestration (Tier 1 → 2 → 3)
breadpad-shared/src/parser.rs Tier 1: rule-based time/recurrence/type parsing
breadpad-shared/src/ai.rs Tier 3: Ollama HTTP client, prompt construction, response parsing
breadpad-shared/src/calendar.rs CalDAV client: push, delete, list events; iCal VEVENT builder
breadpad-shared/src/scheduler.rs systemd timer creation, snooze, recurrence next-occurrence
breadpad/src/main.rs GTK4 layer-shell popup, text field, type chip selector
breadman/src/main.rs GTK4 app entry, sidebar, note list, search
breadman/src/views/ upcoming.rs, archive.rs, per-type list views
breadman/src/editor.rs Inline note editor popover

License

MIT