- POST /api/diary/day finds a day's entry, or starts it at the root of the
diary, by the server's clock. T, the calendar and a capture all use it, so
they can no longer write two entries for one day: before, T counted days by
the browser's midnight and wrote into whichever diary folder was last open.
- A Ctrl-P result opens beside the results, as a / result does, so n and N
go on from it rather than finding no search.
- A card's description resolves its [[links]] the way a document does, and
following one leaves the card for what it leads to.
- gf on the board opens a card's notes. Enter was documented for that, but
Enter turns the card over.
- The pane header and the status line show where the open node is, rather
than where the list is, so a node from another space shows its own path.
Q lowers the scratchpad from under the bar over whatever is on screen,
as the desktop lowers its own. Its notes have no name or place; each is
written in the vim editor and saves itself. K (:keep [title]) makes the
note under the cursor a document in the folder being browsed, titled by
its first line, and takes it off the pad. :scratch <text> jots one from
the command line. The notes ride along in backups and whole-workspace
exports.
The card description's editor is now TextEditor, shared by both.
Enter on a card floats a sheet over the board: its labels, a markdown
description written in the vim editor (:w writes it to the card), and a
checklist walked with j/k, ticked with Space, grown with o. Labels take
their colour from their name. A repeating card comes back with its
checklist reopened.
Code in the editor and in rendered fences is coloured from nvim's
yorha.lua, one class per highlight group, for every language
@codemirror/language-data knows; the languages load on first use.
The ~/ with a cursor was a word drawn as a picture. The mark is now what
the app is: a workspace tiled the way the desktop tiles one, the first
window and the rest dwindling beside it with gaps between. The focused
window wears the theme's light at 135°, as the active border does, with a
soft glow, and carries the header every pane opens with, a square marker
and a rule; the others are outlines on the hull. It holds at 16 px as a lit
bar beside dim ones.
The touch icon, the 512 px copy and the banner are rendered from the same
SVG, and the screenshots are taken again with it in the bar.
Inside the special workspace the agenda and the calendar could only be
reached with Alt-h/j/k/l, which a desktop that binds Alt to its own windows
never lets through, and the same keys meant two things: t on the agenda put
the whole workspace away, t elsewhere went to it. Three esc buttons, one per
tile, all closed everything.
Now b, t and C only ever go to their tile, from anywhere. Tab and Shift-Tab
walk the tiles, as they walk the spaces elsewhere; l past the board's last
list goes on into the agenda, h in the agenda back to the board, j past its
end into the calendar. S toggles the workspace, and q and Esc leave it from
whichever tile — after leaving fullscreen first. The tile buttons are gone;
each tile's head shows its key instead, lit on the one with focus, and the
bar has a special pill beside the spaces.
A document opened from the workspace — a day of the calendar, an entry of
the agenda, today's entry, a card's notes — comes back to the tile it was
opened from on q.
The board tile shows the board of the space being browsed, or else the last
one shown, so coming from the diary, which has none, no longer leaves it
empty. Card and list actions follow the board on screen, not the browsed
space.
A ```typst (or ```typ) block is compiled by the typst crate into SVG and
shown as an image where it stands, so math, tables and figures can be
written in typst beside the markdown around them. It shows its source while
it is set, and the error typst gives, with the block's line, when it cannot
be.
Each block is a document of its own: its source is the only file in its
world and the fonts are typst-assets', inside the binary, so nothing is read
from disk or fetched — packages and images are refused. The page fits what
is on it, is transparent, and carries the theme's text colour. Answers are
cached by source, two blocks are laid out at a time off the async threads,
and comemo's memo is evicted after each compile.
A share page asks POST /api/share/{token}/typst, which takes the reader's
cookie and typesets only a block the shared document holds.
typst brings a palette crate whose AsRef impls on arrays made four
`kek.as_ref().into()` in the vault ambiguous; they deref instead.
The board swapped out the tree, and the agenda and the calendar were
overlays over it, each with a key to open and a key to close. They are now
three tiles of one special workspace, as the desktop keeps its scratchpad:
the board on the left, the agenda over the calendar on the right.
S puts it up and away from anywhere. b, t and C go straight to their tile,
and put the workspace away when that tile already has focus. Alt-h/j/k/l
move the focus between the tiles, f gives the focused one the porthole,
and the tiles without focus keep their cursors, dimmed. A digit switches
space as everywhere, and the board follows; the board's head names every
space that has one. Enter on an agenda card goes to it on the board beside.
A space without a board says so in the board's tile instead of refusing.
The rail offers the three tiles as doors, so a phone, which shows one at a
time, can walk between them. The help, the README, the wiki and the
screenshots say how it works now.
The help was a box narrower than the screen that had to be scrolled with
the mouse. It is now as big as the porthole, in newspaper columns that run
on to the right: h/l and j/k move a column, Ctrl-D/U and Space a screen,
g/G to either end. A row is never split between columns, and says what it
says before its first dash; / filters the rows by key or words, and shows
the ones left whole. The prose footer became an 'in the editor' group.
The cursor beside ~/ was a tall block in the alarm colour, the one hue the
theme keeps for something wrong, and the tilde was a hand-drawn wobble.
The mark is now drawn in the theme's light alone on its hull: a symmetric
tilde, the slash, and the underscore cursor the boot screen waits with,
inside the corner brackets the desktop frames things in. The touch icon,
the 512 px copy and the banner are rendered from the same SVG.
`file_text::catch_up` read every file without a row, which after a write meant
walking every file node there is to find the one or two just put in. It now
takes the part of the tree to look in: an upload asks about the folder it went
into, an import about the folder it was poured into, an SFTP save about the
folder it landed in, a decrypt about what was decrypted. Only an import at the
root and the pass at start look at everything.
The excerpt of a file found by its text marks each matched word between
U+0002 and U+0003, which no text file has a use for, and the list draws them
lit. A cut through a match leaves an opening without its close, and the rest
is lit to the end rather than the marker shown.
The screenshots are taken again, so the search one shows it.
`breaks` is on because the diary is written a line at a time and a line typed
in the browser is meant. Markdown written elsewhere is usually wrapped at a
width instead — an import, a save over the SFTP mount from vim with
`textwidth`, a pasted README — and kept its breaks, one in the middle of every
sentence, worst on a phone.
`hardWrapped` tells the two apart by what a wrapping editor leaves behind:
every line but the last is long, each break comes where the next word would
not have fitted on the line before, and the lines seldom end where a sentence
does. A paragraph that looks like that has its soft breaks read as spaces; a
break asked for outright, two spaces or a backslash at the end of the line,
stays. Lines broken by hand — a short sentence and then another, a list of
things that happened, a verse — keep their breaks as before.
rust-embed refused to derive without `web/dist`, so on a fresh clone
`cargo check`, `cargo test` and rust-analyzer failed with a derive error that
did not say what was missing. With `allow_missing` the embed is empty until
the frontend is built, and the handler gives the answer it already had for
that case, "frontend not built". The release build is unchanged once
`web/dist` is there, which `make build`, the Dockerfile and CI all see to.
The browser grouped the agenda by its own midnight and remembered that it had
opened it by its own date, while the server counts due dates, mails and the
diary in `WORKSPACE_TIMEZONE`. A laptop on a trip, or a browser set to UTC,
showed a card as late that the morning mail called due today, and opened the
agenda a second time on one workspace day or not at all. A day was also taken
to be 86400 seconds, which it is not when the clocks change.
The agenda answer now carries `today`: the date in the configured timezone,
when it starts and ends, and when the seventh day after it ends, all from the
same `day_bounds` the mails use. The browser groups by those and keys the
once-a-day opening on that date. The screenshot run marks the agenda as seen
under the same date.
Three small things the docs pass turned up.
A search of nothing but punctuation — `!!!`, `--` — gave the FTS query no
terms, and the list handler then fell through to browsing with no parent,
which answered with the spaces as if they were matches. It now finds nothing.
Two spaces could have the same name: the slug was made unique (`work-2`) but
the name kept, so the bar showed two pills that read alike and `:space work`
picked the first. A new space, a space renamed, and a directory made at the
root of the mount are refused when another space has that name in any case or
that slug. Folders and documents may still share names; they are told apart by
where they sit.
A name had no ceiling. A file is called by its name on the mount, where most
filesystems stop at 255 bytes, so a document, folder, file or space is now at
most 200 characters, checked where one is made or renamed.
`PUT /api/nodes/{id}` wrote the whole node, and a field left out was taken as
empty: a rename sent without the body erased the text. The browser always sent
it, which was the other half of the trouble. To rename a document it sent the
body it had — the draft for one open here, a copy fetched a moment earlier for
any other — and without the version it had seen, so a save made in another
tab, on the phone or over the mount in between was written over, kept only as
a revision nobody was told about.
Now `name` and `body`, like the rest, change only when they are sent. A rename
sends the name alone, and for a document open in a tile the `updated_at` that
tile last saw, so a rename of a document saved elsewhere since is refused the
way `:w` is, rather than stamping a new version over it that the next autosave
would then write the draft past. Toggling a space's board sends only that.
The README grew a section per feature and is a good reference, but nothing
said where to begin. docs/wiki now has a getting-started page, a guide that
walks through one day with the workspace and explains each part where it comes
up, and a page per part of the app, each with screenshots of what it
describes.
Three pages are read out of the code rather than written, so they cannot drift
from it: the keys and commands from Help.svelte and commands.ts, the same lists
`:help` draws; the settings from .env.example, which gains a heading for the
vault, trash and history settings so they no longer sit under mail; and the
API from the README's table. `make wiki` writes them, and `--check` fails when
one is stale or a link between pages leads nowhere. `--publish` lays the pages
out for a GitHub wiki: links without `.md`, images beside them, `[[` escaped so
the wiki does not take a command's help text for a link to a page.
The screenshots are taken by `make screenshots`: the debug binary on a
temporary data directory, with a password made up for the run, filled with an
example workspace over the API, and a headless Chromium driven over the
DevTools protocol through the views the docs show. Nothing outside the
temporary directory is touched but docs/images, and the pictures are cut to
256 colours, a third of their size, which a dark UI does not show.
The README opens with the banner, a CI badge, the guide, the features grouped
by what they are for, and six of the screenshots.
The app was a ring and a dot, drawn in CSS in the bar and inlined as a data
URL for the favicon. It now has an icon of its own: the `~/` of the prompt it
is named after, with the cursor waiting beside it in the alarm colour of the
theme. It is the favicon, a full-bleed touch icon for a phone's home screen,
and the mark in the bar, on the login and on the share page, served from
`web/public` so all of them are the same file.
The banner and a 512 px copy in docs/images are for the README and the wiki.
The crate description says what the workspace is now, not the diary it was.