
Notes that swim the way you think
A local-first, hierarchical note-taking app built with Rust. Define your own note types with scripts, organize in infinite trees, and keep everything on your device.
Small creature, big capabilities
Everything you need to organize your world, nothing you don't.
Hierarchical notes
Organize notes in an infinite tree. Each note can have children, with configurable sort order โ alphabetical or manual positioning.
Typed note schemas
Define custom note types with Rhai scripts. Fields support text, numbers, dates, booleans, selects, ratings, and more.
User scripts
Each workspace stores its own Rhai scripts. Create, edit, enable, and reorder scripts from the built-in script manager.
On-save hooks
Scripts can compute derived fields automatically โ auto-generate titles, calculate durations, or set status badges on save.
Fuzzy search
Live search with debounced fuzzy matching across titles and text fields. Keyboard-navigable results that scroll into view.
Export & Import
Export an entire workspace as a .zip archive. Import into a new workspace with version-compatibility checks.
Local-first
All data lives in a single .krillnotes file on disk. No account, no cloud dependency, no internet connection required.
Cross-platform
Runs on macOS, Linux, and Windows via Tauri v2. Native performance backed by Rust and SQLite.
Operations log
Every mutation is recorded in an immutable log โ browse history, filter by type or date, and purge old entries.
Solid foundations, tiny footprint
Modern tools chosen for speed, reliability, and cross-platform reach.
Your notes, your types, your rules
Define custom note schemas with Rhai scripts. Add fields, hooks, and computed values โ no recompilation needed.
- Define schemas with typed fields (text, date, select, rating...)
- Auto-derive titles and computed fields with on_save hooks
- Custom view rendering with on_view hooks
- Query notes by type, filter, and aggregate
- Six example scripts ship out of the box
schema("Task", #{
title_can_edit: false,
fields: [
#{ name: "name", type: "text", required: true },
#{ name: "status", type: "select",
options: ["TODO", "WIP", "DONE"] },
#{ name: "due_date", type: "date" },
]
});
on_save("Task", |note| {
let status = note.fields["status"];
let symbol = if status == "DONE" { "โ" }
else if status == "WIP" { "โ" }
else { " " };
note.title = "[" + symbol + "] " + note.fields["name"];
note
});