Imported from packetThrower/zorite (
AGENTS.md). Install upstream withnpx skills add packetThrower/zorite. Copyright stays with the author.
AGENTS.md
Guidance for AI agents and human contributors. Keep changes consistent with what's already here — match the surrounding code, and prefer deleting over adding.
What this is
Zorite — a cross-platform (macOS / Windows / Linux) Markdown daily-journal desktop app.
Rust + GPUI + gpui-component + SQLite. The repo is a Cargo
workspace (edition 2024): the app at the root, plus eight reusable crates under crates/
(MIT, published on crates.io; the app is GPL-3.0-or-later).
Layout
src/— the app: window, journal feed, editor wiring, SQLite (db.rs), import, settings, and the host-side renderers (ui/).crates/gpui-bidi— bidirectional text for GPUI: index↔x over reordered glyphs, logical-order row layout, and the row painter both renderers use (#66).crates/zorite-editor— from-scratch text editor for GPUI (the WYSIWYG markdown surface).crates/zorite-markdown— Markdown reading-view renderer.crates/gpui-pdf— page-virtualized PDF viewer (pure-Rusthayro, no native libs).crates/gpui-whiteboard— infinite pan/zoom whiteboard canvas.crates/ratex-gpui— LaTeX math renderer + structural editor (RaTeX engine).crates/os-cursors— custom mouse cursors (no gpui fork; Linux XCursor / macOS / Windows).crates/os-spellcheck— native OS spell-check (no deps; macOS/Windows, Linux no-op).docs/— Astro Starlight docs site (auto-deploys on push tomain).
The three views
A note renders in exactly one of three views. Use these names — in code, comments, and commits — so a search for the view name finds its implementation:
| View | What it is | Owner | Switched by |
|---|---|---|---|
| WYSIWYG | Live-preview editing: markers dimmed/hidden, headings sized, images/tables/math render inline, reveal-on-caret | crates/zorite-editor with a markdown_style installed |
AppView.wysiwyg on (the default; Settings → Markdown) |
| raw | Plain-text editing: the bare markdown source, no styling | crates/zorite-editor with no markdown_style |
AppView.wysiwyg off, while editing |
| reader | Read-only rendered markdown (clickable links, checkboxes) | crates/zorite-markdown (MarkdownView) |
AppView.wysiwyg off, when not editing |
The app picks the view in day_section (src/ui/journal.rs) and its
src/ui/page_view.rs twin; hosts wire renderers/handlers to both crates from
src/ui/ and the EditorEvent subscriptions in src/app.rs.
The cross-view rule: any user-facing markdown behavior — rendering a
construct, clicking it, hover cursors — must be implemented (or knowingly
skipped) in both the reader (zorite-markdown) and WYSIWYG
(zorite-editor); raw is plain text by design. They are separate engines and
share nothing: a feature added to one does NOT appear in the other. This has
bitten before — links ([[wiki]], #tag, [text](url)) navigated in the
reader for months while WYSIWYG silently ignored clicks (fixed in 0.4.1).
When you touch one side, grep the other for the same construct.
Parity direction: when the two views disagree visually, WYSIWYG's look is the baseline — object widths and spacing (tables, code cards, body line height 1.45) converge toward it. The exceptions: bullet-list item spacing AND list indentation (bullet→text gap) follow the READER's roomier look. (User-set rules, 2026-07-02.)
zorite-editor comments use W1/W2/W4a–c/W6 milestone codes for WYSIWYG
features — the legend is in that crate's lib.rs top doc.
Build, run, and the gate
cargo run # launch the app (root bin: zorite)
cargo run --features fps # + gpui-fps performance HUD (dev only)
Before every commit, run what CI runs (.github/workflows/ci.yml) — these must pass:
cargo fmt --check # or `cargo fmt` to fix
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace
cargo check alone is not the gate — clippy is, and any warning fails the build.
CI also compiles on five targets (macOS, Linux x64/arm, Windows x64/arm), so every
change must stay cross-platform.
Code conventions
-
No speculative abstraction. This codebase has zero one-impl traits and no factories or config nobody sets. Climb the ladder: does it need to exist? → reuse an existing helper → stdlib → one line → only then new code. Don't add a dependency for what a few lines do.
-
Surgical diffs. Every changed line should trace to the change you're making. Don't reformat or "improve" adjacent code. It's a
-D warningsrepo — leave no orphaneddead_code/unusedbehind your edit. -
Crates stay host-agnostic.
crates/*depend ongpuionly — notgpui-component, not the app — and run on all three platforms with no native libraries. Sibling dependencies are sanctioned one at a time, and there are two:zorite-editor→zorite-markdown, for the shared construct recognition inzorite_markdown::syntax(alert kinds, table styles, heading scales, writing direction) — never for rendering.- both renderers →
gpui-bidi, the bidi layer (#66). It is a leaf: the index↔x map, logical-order row layout, and the row painter that works around gpui collapsing a bidi row to one colour. Shared because the two engines have the same problem and must not solve it twice — when they did, they disagreed about which side a table's first column sits on.
Keep the editor/rendering cores GUI-free where a crate already splits them (e.g. ratex-gpui's
editor::{model,cursor,geometry,input,latex}are GUI-free; onlyviewis gpui glue). -
The app owns rendering. Renderers in the crates are host-agnostic; the app supplies the concrete one (see the
MarkdownView::on_math/on_inline_mathwiring inui/). -
Cross-platform IO. No
$HOMEor Unix-only assumptions — usepaths::*(e.g.paths::desktop_dir()) and gate platform code with#[cfg(...)]. -
Optional functionality sits behind a Cargo feature so consumers can drop it (ratex-gpui's default-on
editor; gpui-pdf's opt-inmarkup/search). -
Chrome controls are
.small(). gpui-component controls in app chrome — settings cards, side panels, popovers, find bars, in-pane prompts — takeSizable::small(), and the settingstext_buttonmatches that scale (its per-rownb_buttonsits a notch below). Dialog bodies/footers and focal surfaces (the unlock screen, the page-title field) keep the default size.
Tests
- Unit tests live in-file under
#[cfg(test)]; cover non-trivial logic (import parsers, editor/whiteboard geometry, DB and link-rewriting). - Headless UI tests of chrome flows live in
src/app/ui_tests.rs: a realAppViewin gpui's test window (#[gpui::test],VisualTestContext), actions dispatched, keys simulated, state read back.paths::data_diris a throwaway dir undercfg(test), so they never open a real notebook. Add one when a chrome flow gets verified by hand. - Live-testing the GUI: synthetic keyboard input does not reach a GPUI window (mouse does) — verify shortcuts by hand. Kill all running instances before relaunching, and close the app before touching its SQLite DB (it opens a real one in the platform data dir).
Commits
Conventional commits with a scope: fix(math): …, refactor: …, docs(changelog): ….
Releases
Stable = add a ## [x.y.z] section to CHANGELOG.md and push an annotated tag
vX.Y.Z (no Cargo.toml bump — the version comes from the tag). Pre-release = a
-suffix tag (e.g. v0.4.0-beta.1). release.yml builds every platform; the
winget submission fires automatically when it completes on a stable tag
(after_release.yml, a workflow_run trigger — a manual
gh workflow run after_release.yml -f tag_name=vX.Y.Z remains as the fallback
if a run flakes). Homebrew tap + Scoop bucket bumps stay manual (version +
hashes from the release's SHA256SUMS).
Crate descriptions and README intros are written for a person skimming crates.io: one plain sentence saying what the crate is and does, no internal jargon (say "custom cursors", not "NSCursor swizzling" — the mechanism belongs in a "How it works" section), no marketing. The docs site takes each crate's description from its Cargo.toml, so there is one place to edit.
Crate releases are independent of app releases: bump version in
crates/<name>/Cargo.toml (and the version requirement in any workspace crate that
depends on it — path deps carry one so they publish), update the README's dependency
snippet, then cargo publish -p <name> in dependency order (bidi → markdown → editor).
gpui is the crates.io gpui-pre family, spec "0.3" (caret) so consumers aren't
pinned to one snapshot; Cargo.lock holds the exact version for this workspace. The
token needs the publish-update scope; crates.io limits new-crate publishes to five
then one per ten minutes.