Files
narl-workspace/docs/wiki/Development.md
T
nvrl 0eabcb2333 keys: one table drives dispatch, help, footers, the rail and the wiki
keymap.ts lists every key with its context, action and help line.
actions.ts holds what each one does, typed against the table so a key
without an action, or an action without a key, does not compile.
modes.ts stacks the contexts (always > sheet > drawer > workspace >
writing > focused tile > special > global) and writes the two-key
pending logic once. App.svelte's 480-line onkeydown is now one call.

Help, the panel footers (a shared Keys component), the rail's keycaps
and Commands.md all read the table; the wiki gains the card and
scratchpad key tables.

One fix on the way: Ctrl-S in the editor no longer saves twice, which
could make the second save fail as "changed somewhere else".
2026-10-22 03:42:19 +02:00

4.0 KiB

Development

Running it from source

make dev-api   # cargo run, serving the API on :4242
make dev-web   # vite on :4243, proxying /api to :4242

Open http://127.0.0.1:4243. The release binary embeds web/dist, and a debug build reads it from disk, so build the frontend once (cd web && bun install && bun run build) for cargo run alone to serve the app. Without it the Rust side still compiles and tests, and the pages answer "frontend not built".

Point WORKSPACE_DATA_DIR at a throwaway directory while trying things, so the real data stays out of it.

The code

where what
src/main.rs start-up, the router, security headers, background loops
src/routes/ one module per area of the API: nodes, boards, trash, revisions, links, capture, diary, media, file_text, seal, share, import, export …
src/sftp/ the SFTP mount
src/backup/ the Proton Drive mirror
src/mail/, src/notify/ the outbox, and what is worth a mail
src/vault.rs the vault's keys and file format
src/sweep.rs the daily clean-up: trash, old versions, unreferenced uploads
migrations/ the SQLite schema, one numbered file per change
web/src/lib/keymap.ts every key: where it is heard, what it is called, its help line
web/src/lib/actions.ts, modes.ts what each key does, and which contexts hear it first
web/src/lib/store.svelte.ts the client's state, and every call to the API
web/src/lib/commands.ts the : commands
web/src/lib/Help.svelte the ? screen

The database is one SQLite file with FTS5 for search. Every migration is applied at start, after a copy of the database is kept as diary.db.pre-<version>. Many rules are enforced by triggers as well as in the code, so no path can break them: a sealed file has no indexed text, a file in a sealed folder is sealed, the diary cannot be deleted.

Checks

make check

runs what CI runs:

  • cargo fmt --check
  • cargo clippy --all-targets -- -D warnings
  • cargo test
  • bun run check in web/ (svelte-check)

The Rust tests run against an in-memory database; most modules have a test block at the bottom.

CI

.github/workflows/ci.yml runs on every push and pull request:

  • rust: builds the frontend, so the tests see the app the binary ships, then format, clippy and the tests;
  • web: svelte-check;
  • docs: checks that the generated wiki pages are up to date and that every link between the pages resolves.

.github/workflows/wiki.yml publishes docs/wiki to the GitHub wiki on every push to main that changes it, or changes a file the generated pages are read from.

The wiki

The pages live in docs/wiki and the pictures in docs/images. Edit them there; an edit on GitHub's wiki page is overwritten by the next publish.

Three pages are generated by scripts/wiki.ts:

page read from
Keys and commands web/src/lib/keymap.ts and web/src/lib/commands.ts
Configuration .env.example
API the API section of README.md

After changing one of those, run make wiki and commit the result. CI fails when they are stale.

Links between pages are written as Page-Name.md so that they work when the files are read in the repository; the publish step drops the .md for the wiki.

Screenshots

make screenshots

builds the app, starts it on a temporary data directory with a password made up for the run, fills it with an example workspace, and drives a headless Chromium through the views the docs show. The pictures are written to docs/images. It needs chromium on the path (or CHROMIUM=/path/to/chrome), and shrinks the pictures with ImageMagick when it is installed.

Conventions

  • One finished feature per commit, with the README, :help and the tests in the same commit.
  • Commit messages are area: a sentence, followed by prose about why.
  • Comments explain why, not what.