Commit Graph
18 Commits
Author SHA1 Message Date
nvrl 2733ac1a3a ui: touch parity, saves that survive outages, edits that do not collide
PUT /api/nodes/{id} takes base_updated_at and answers 409 when the node
was saved since; the editor sends it, so a save from another tab, the
phone or the SFTP mount is no longer overwritten in silence. :w! and
:wq! now force the write.

An autosave the server could not take is kept and retried with backoff,
and at once on the online event; network failures say "the server
cannot be reached" rather than "Failed to fetch". At boot that is no
longer shown as a login screen. A session that expires mid-edit shows
the login with a note, and logging back in resumes the open draft.

Folder listings and the media list are read page by page, so more than
500 children are no longer cut off. Search results carry the space and
folder they sit in, and the overview's recent list includes files.
Embedding uploads report progress, like uploads into a folder.

On touch devices vim is off by default. Each row has an actions sheet
(rename, move, share, seal, delete), folders start with a .. row, and
the dock gains today's entry, upload, search all spaces, drop for a
marked node, help, and the board's list, reorder, rename and delete.
The agenda gets a done button per card and a button for today's entry.

Sharing a folder or a file is refused in the client. :mv .. in a search
moves up from the match's own folder. Files dropped outside the list or
editor no longer navigate the tab away. The public share view renders
/n/ links as plain text, and tells a network error from a dead link.
2026-10-20 05:53:34 +02:00
nvrl aa34be8663 export, import: an export that pours back into a workspace whole
An export used to lose what plain files cannot carry: dates, names as
typed, the boards. It now writes .workspace.json beside the tree (format
1), and an import that finds one restores them. The markdown stays
plain, so the archive still reads without the application.

POST /api/import (:import! / in the UI) imports at the root: each
top-level directory becomes a space, or merges into the space already
there by that name, with its board and cards and the diary role. With a
manifest, media/ comes back as embedded files rather than a folder, a
file whose name ends in .md stays a file, and empty folders come back.

Export claims every path in one set, folders, then documents, then
files, so a file named notes.md beside a document notes no longer makes
a duplicate entry that failed the whole zip. Links to folders are
rewritten like links to documents, in one regex pass per kind instead
of a replace per file and document. A sealed file that will not open
now fails the export instead of being left out silently. The temporary
zip is made in the data directory, not in /tmp.
2026-10-20 01:38:42 +02:00
nvrl a641d9b6ba vault: files sealed under a password that is never stored
Argon2id turns the vault password into a key that seals a random master
key; only the sealed copy is kept. Each file gets its own key, sealed
under the master key in its header with its media id, and its bytes are
AES-256-GCM STREAM records, so a swapped, cut or altered file refuses
to open. Uploads with ?encrypt=true are sealed as they stream in.

:vault, :unlock, :lock, :passwd, :encrypt, :decrypt and :upload! drive
it. The vault locks after WORKSPACE_VAULT_IDLE_MIN idle minutes, on
logout and on restart; while locked, sealed files answer 423, share
links never serve them, and :export! refuses rather than leave them out.

Sealing an existing file rewrites it under a new id and retires the old
one: the mirror deletes the plaintext from Proton Drive outright,
trash included, whatever WORKSPACE_BACKUP_PRUNE says, and drops it from
the readable files/ view. The mirror now compares on-disk sizes, so
sealed files are not re-hashed every run.
2026-10-19 16:03:59 +02:00
nvrl 2103edc299 files: a node kind of their own, in the tree beside the notes
A file node points at one media row and owns it the way a document owns
what it embeds, so folder deletes, the media browser and exports keep
working. POST /api/nodes/{id}/files streams uploads into a folder;
imports turn non-markdown files into file nodes; exports put them at
their tree path. The browser previews images, video, audio and text,
offers the rest as a download, and takes files dropped on the list.

Migration 0010 widens the kind CHECK by editing the stored schema, as
SQLite documents for loosening a constraint: sqlx runs migrations in a
transaction, where foreign keys cannot be switched off for a rebuild.
2026-10-19 15:44:30 +02:00
nvrl 9255d36b61 agenda, capture and finder: what needs doing comes to you
GET /api/agenda gathers unfinished cards from every board that are late,
due within a week, or in hand, plus whether the diary has today; the
reminder mail and the agenda share one diary check. t opens it (and it
opens itself once a day when something is late or due), T writes today's
entry, the bar counts late and due cards, Ctrl-P searches every space,
and a document o made that is left empty is removed on the way out.
2026-10-19 14:51:52 +02:00
nvrl e1c8e22ea5 board: one model — first list inbox, last list done
Space sends a card to the done list or back to the inbox instead of
ticking it in place. Enter on a card with no document starts one named
after it, and q comes back to the board on the same card. :card captures
into the inbox without leaving the current view (c anywhere), :due takes
fri, tm, +3d and the like, :name! renames a list, done cards older than a
week fold away behind z, and the board spells out its keys. The phone dock
gains send, done, due and notes doors.
2026-10-19 14:45:35 +02:00
nvrl d9b78170d8 keys act on the focused pane, deletes ask y/N in place
dd, x, i, R, s, y and :mv used the open document whenever one was open,
so walking the list and pressing dd deleted the document beside it. They
now act on the row under the cursor while the list has focus. Deletes ask
an in-app y/N naming the node instead of confirm(), and the board no
longer lets tree keys through to the hidden list.
2026-10-19 14:39:38 +02:00
nvrl ecfbab0eaa ui: share view in the shell, icon rail, phone polish
- shared view uses the bar, porthole and status band; outline gets its own
  window on wide screens; j/k, gg/G, Ctrl-d/u scroll with a vim-style ruler
- rail doors are line icons in groups with hover tooltips and keycaps
- phone: number-only space pills, no nested rims, icon dock, toast messages,
  back button and one-line meta in the document header
- keycaps (.kbd) wherever keys are shown, styled scrollbars in Chrome,
  outline tracks position on jumps, breakpoints unified at 767px
2026-10-17 17:27:59 +02:00
nvrl 9391b10af1 hyprland 2026-10-17 14:25:32 +02:00
nvrl 8a37ca5dfe redesign 2026-10-17 13:44:05 +02:00
nvrl 25411ca115 ui fixes 2026-09-21 15:10:23 +02:00
nvrl 1a37420461 added document move 2026-09-21 00:05:24 +02:00
nvrl 714a84089a feat: j/k scroll the open document, and import loose files
With a document open and not being edited, j/k scroll the reading pane,
gg/G jump to its top and bottom, and Ctrl-D/Ctrl-U page it. h still backs
out to the list, where the same keys walk the rows again.

The import pickers offered a folder or a zip and nothing else, so a single
file could not be imported at all. The second picker now takes any files;
the server already handled them.
2026-09-20 20:42:20 +02:00
nvrl d8ebf4b850 rename and stuff, better navigation 2026-09-19 20:46:05 +02:00
nvrl 49b2056215 feat: spaces, folder import, boards and mail; rename to narl-workspace
The diary grows into a workspace: a flat list of dated entries becomes a
tree of spaces, and the app is renamed to match what it now is.

Spaces and nodes
  One recursive `nodes` table replaces `entries`: a space is a root node,
  and folders and documents nest under it without limit. Entries keep
  their ids through the migration, so share tokens, media links and the
  drafts in localStorage all still resolve.

Folder import
  A folder or a zip is uploaded as a document tree. Markdown becomes
  documents titled from their first heading, everything else becomes
  media, and relative links between the imported files are rewritten to
  the paths they landed at, so a set of cross-linked notes stays
  navigable after the upload.

Boards
  A kanban board per space, with the space as the board: lists, cards,
  due dates and an optional link to a document. `diary` has none.

Mail
  SMTP notifications through an outbox. A mail is enqueued under a key
  naming the occurrence it belongs to, so a restart cannot resend and a
  relay that refuses is retried with a widening backoff. Four kinds: the
  diary reminder, cards due and overdue, a weekly digest, and logins
  from a browser that has not been seen before or a burst of failures.
  All of it is dormant until WORKSPACE_SMTP_URL is set.

Rename
  Everything user-visible says workspace; everything that already exists
  outside this repo keeps its name, because renaming it would break a
  running deployment for tidiness alone: the database file, the docker
  volume, the session cookie, the Proton device, the keyring entry and
  the localStorage keys. Configuration reads WORKSPACE_* first and falls
  back to DIARY_*, so the deployed .env keeps working untouched.
2026-09-18 14:53:22 +02:00
nvrl 76f0887ac5 feature(SHARE) added secret token to share to prevent getting crawled 2026-09-09 14:37:16 +02:00
nvrl 515a82dfc2 yank clipboard + ? fix 2026-09-05 08:13:18 +02:00
nvrl 4002a35305 Terminal-themed markdown diary
Single-user diary served as one Rust binary with the Svelte frontend
embedded.

- axum 0.8 + SQLite (sqlx), migrations run at startup
- stateless HMAC session cookie; credentials from .env, compared in
  constant time
- FTS5 full-text search, user input rewritten into quoted prefix terms
- media uploads linked to entries by scanning bodies for /api/media/<id>,
  which scopes share-token media access and entry deletion
- unguessable share links (192-bit token) serving a public read-only page
- vim keybindings via CodeMirror plus a modal browse layer and : command
  line; disabled on touch devices
2026-09-05 05:39:25 +02:00