One crate imports the UI framework
gpui is pre-1.0 and its API moves. The single most valuable structural
decision in this project is that only kve-ui imports it. Everything below
is headless and testable without a window, which is what makes the project’s
definition of done — functional tests including the non-happy paths — affordable
rather than aspirational.
| Crate | |
|---|---|
kve-core |
The rope buffer, cursor positions, edits and undo. No UI. |
kve-config |
Settings, the command registry, and the translations. |
kve-markdown |
Reading markdown: the outline, the preview’s source, formatting, tables. |
kve-fs |
The project tree, the ignore rules and the file watcher. Reads one directory at a time and never walks. |
kve-search |
The file index and the fuzzy matcher. The one thing that does walk. |
kve-format |
The built-in formatters for languages other than markdown. |
kve-git |
Git, through gitoxide; git itself only for the network and branches. |
kve-canvas |
The whiteboard’s model: Obsidian’s JSON Canvas, read and written back field for field. |
kve-ui |
The window. The only crate that knows gpui exists. |
kve-preview |
The heavy preview: a second program, fed whole pages over a pipe. |
kve-bench |
The performance harness and the budgets it enforces. |
kve-drive |
A development tool: a fake user for the window, over Wayland’s virtual pointer and keyboard. Nothing depends on it. |
kaivalya-editor |
The binary. Argument parsing, and nothing else. |
A crate was created when there was something to put in it, not in advance — formatting with Phase 6, git with Phase 7, the whiteboard’s model with F209. What each one holds, in detail: the Technical reference.
The command registry
A command is declared once, with its scope, its translation key and its default binding. The menu, the keymap and any later command palette are three views of that one table.
Written the other way — a menu here, a keymap there — they drift apart, and the drift is what costs the time: a shortcut that exists but appears in no menu, a menu entry whose label was never translated. Because the label is a translation key rather than a string, a command cannot be added without being translatable, and a test asserts that every command resolves in both English and French.
The menu bar is generated from that table: one menu per scope, in registry order, and a test asserts that every registered command reaches it. Six of the entries — undo, redo, cut, copy, paste, select all — dispatch the editor element’s own actions, because the element implements and binds them already; binding them a second time would give one keystroke two handlers, and leaving them out of the menu would hide them from anyone looking for them.
A scope with nothing in it is left out rather than shown empty, so a menu never promises something the editor cannot do. Project comes first, because the first thing a session decides is what it is working on.
Positions count graphemes
The status bar reports the column a person reads, which counts grapheme
clusters rather than bytes or codepoints. In French, é may arrive as one
codepoint or as e plus a combining accent depending on where the text came
from, and a column counting codepoints would report different numbers for two
strings that look identical.
Only the current line is measured, so this costs nothing on a large file.
Speed is a budget, not an adjective
Every phase is measured against published budgets and the results are recorded. The budgets are absolute and stated against a reference machine — deliberately an old laptop, so that a budget met there is met on anything newer.
kve-bench runs the part of that gate which needs no display, on every commit.
The part that needs a window is run by hand on the reference machine, which is a
gap rather than a design: it closes when the editor element is ours to
instrument.
Colour resolves in one place
No crate below kve-ui ever emits a colour. kve-core returns positions,
kve-config returns settings, the grammars return tree-sitter capture names and
kve-git returns change kinds. Colour resolves in the UI
crate alone, out of one semantic token set — status_bar rather than grey,
attention rather than red — so a light and a dark theme differ in one table
instead of in every widget.
A test enforces it by scanning the headless crates for colour literals. The rule is free while there is one widget and a pass over the whole interface once there are two hundred, which is why it is Phase 1 work rather than polish.
The desktop’s theme is a preference, not a dependency
theme in the settings file takes system, light, dark, or a theme name;
it defaults to Kaivalya Blue, and system follows the desktop. Resolving what the desktop wants means asking
xdg-desktop-portal over D-Bus, and that portal is routinely slow or missing.
So it is never on the startup path. The window paints with the configured fallback, and follows the system answer from the frame after — and keeps following it, so changing the desktop’s colour scheme changes the editor’s without a restart. If the portal never answers, the fallback simply stands.
A theme name that is not installed falls back too, and says so in the status bar. Falling back is right; falling back quietly is not.