Releases: iamrohithrnair/emeraldian
Release list
v0.6.0
Changelog
All notable changes to this project are documented here. The format follows
Keep a Changelog, and versions follow
Semantic Versioning.
[0.6.0] — 2026-09-26
Copy and paste through the system clipboard, shortcuts that can be moved to
keys every layout can reach, and settings the app writes for you that no
longer disturb the rest of config.toml. A config.toml written by an earlier
version loads unchanged; the new [keys] section is optional.
Added
-
Copy and paste through the system clipboard. While editing a note,
Ctrl+C,Ctrl+XandCtrl+Vcopy, cut and paste, so text can finally move
between a note and another app. A paste replaces the selection, keeps its line
breaks, and undoes in one step. With nothing selected,Ctrl+CandCtrl+X
say so rather than doing nothing silently.The terminal's own paste now arrives as one block, using bracketed paste.
Into a note it goes in whole; into the chat box, a search, the palette or a
name prompt it becomes a single line, because a pasted newline used to send
a half-written chat message.Vim mode behaves like vim with
clipboard=unnamedplus:y,d,c,x,
sand the rest put text on the system clipboard, andp/Pput whatever
was copied last, here or elsewhere."+and"*are accepted and mean the
same register. In Visual modepputs over the selection and keeps what was
copied,Ctrl+Cyanks, andCtrl+Vpastes over the selection.No new dependencies: the clipboard is reached through
pbcopy/pbpasteon
macOS,wl-copy/wl-paste,xcliporxselon Linux, and PowerShell on
Windows. Where none of those can copy, as over SSH, it falls back to OSC 52
and asks the terminal to do it (tmux needsset -g set-clipboard on). -
Shortcuts can be moved to other keys, under
[keys]inconfig.toml.
Ctrl+\andCtrl+]can't be typed on layouts where\and]sit
behind AltGr, Spanish among them, which left both sidebar toggles out of
reach from the keyboard. Any app-wide shortcut can now be given a key of
your own, such astoggle_left_sidebar = "alt+e". The default keeps working
beside it, the palette shows the new key, and a name or key that isn't
understood is reported at startup rather than quietly ignored. "Reset
shortcuts to defaults" in the palette puts every key back, after asking.
Fixed
-
f,tandggwork in Visual mode. The key after them was read as a
Visual command of its own, sovfxcut the selection instead of stretching
it to thex, andvggwent nowhere. They now extend the selection as they
do in vim. -
Toggling vim mode changes one line of
config.toml, not the whole file.
It used to write out every setting from memory, which dropped your comments
and layout, and saved settings you had only changed for that session. It now
edits justvimunder[editor], however it was written there, and leaves a
file that doesn't parse alone rather than overwriting it. Resetting shortcuts
works the same way.
[0.5.0] — 2026-08-10
A minor rather than a patch release: vim mode is a new way to use the editor,
and the keyboard behaves differently while it is on. Nothing changes for anyone
who leaves it off, and a config.toml written by an earlier version still
loads — the new key has a default.
Added
-
F3closes the tab, in vim mode and out.Ctrl+Wstill does it with vim
off, but vim spends that key on the window prefix, and every plain
Ctrl+letter is already taken by the app, by vim, or by the terminal itself.
Ctrl+Shift+Wis not the answer either: without the Kitty protocol the Shift
cannot be encoded, so it arrives asCtrl+W, arms the prefix and swallows the
next keystroke. F-keys are unambiguous everywhere, which is whyF4carries
the vim toggle too. -
Vim mode in the note editor, on
F4. Normal, Insert, Visual and
Visual-Line.Motions
h j k l,gj/gk,w W b B e E ge,0 ^ $,gg G,{ }, and
f F t Twith;/,to repeat.jandkmove by source line as they do
in vim;gj/gkmove by the row on screen.Operators
d c y > <over any motion, or doubled for the line —dd,cc,
yy,>>,<<— with counts anywhere they are accepted in vim, sod3w
and3dwagree. Text objectsiw aw,i" a",i( a(,i[ a[,i{ a{,
with nesting counted so an inner pair wins.Single keys
i I a A o O,x X s S,D C Y,p P,r,J,~,u,
Ctrl+R, andCtrl+A/Ctrl+Xto increment and decrement.One resolver defines every motion, and both operators and visual-mode
movement go through it, sodwandwcannot disagree about where a word
ends. Vim's exclusive/inclusive distinction is modelled rather than
approximated:dwstops before the next word anddetakes the last letter
of this one.Outside Insert mode the Ctrl keys vim defines take vim's meanings rather than
the app's:Ctrl+Rredoes,Ctrl+D/Ctrl+UandCtrl+F/Ctrl+Bscroll,
Ctrl+A/Ctrl+Xadjust a number, andCtrl+O/Ctrl+Iwalk the note
history. In Insert mode onlyCtrl+WandCtrl+Uare claimed, for vim's
word- and line-delete. Everything else is unchanged there, in every other
pane, and with vim mode off; shifted combinations are left alone, so
Ctrl+Shift+Fstill searches.The app's commands move onto a
Spaceleader and its panes ontoCtrl+W.
PressingSpacedraws a which-key menu built from the same table that binds
the keys, so a binding cannot exist without being listed.Ctrl+W h/j/k/l
moves between the explorer, note and sidebar,[b/]bstep through tabs.Vim mode applies to the editor and not to reading: a note being read has no
buffer to act on, so the reading pane keeps every key it always had and
Ctrl+Eis still the way in. The two navigation keys are the exception,
because moving between panes and notes is not editing.A
:line, typed along the bottom row rather than in a dialog::w:wq
:x:q:q!:qa:e <name>:42:h, plus:setfor the handful of
options worth changing mid-session and:mkconfigto write them down. Most
are an existing action under a different name, so:wandCtrl+Sare not
two implementations of saving.In-buffer search on
/and?, withn/Nto step and:nohto clear.
A plain substring rather than a regular expression — notes are prose, and a
half-supported regex dialect would be worse than an honest literal one — and
case-insensitive until the pattern contains a capital, which is vim's
smartcase. Every match on screen is highlighted, not only the one jumped to..repeats the last change, including the text typed during it. It replays
the keys rather than a parsed command, which is the one representation that
covers an operator with a motion, a lonex, and an insert uniformly.Deliberately hard to get stuck in: the mode is named in the status bar, the
cursor is a block in Normal and a bar in Insert, a pending2dis shown as
it is typed, the hint bar leads with the way out, and the reference opens by
itself the first time it is ever switched on.F4works from anywhere,
including from inside Normal mode and over an open overlay.Off by default, and with it off the editor behaves exactly as it did before.
-
editor.viminconfig.toml, also reachable as/vim on|offand from the
command palette. It is the one setting written the moment it changes rather
than when settings are saved, because it decides what every key does and
silently losing it on the next launch is a different order of problem. -
Config::save_to, mirroringState::save_to, so tests that write settings
do not reach into the config directory of the machine running them. -
Forward navigation.
Action::Backnow records what it stepped away from, so
there is something to return to; opening a note any other way clears the
trail, as a browser does. Reachable asCtrl+Iin vim mode.
[0.4.2] — 2026-08-07
Nothing in the app changed. This release exists to carry the packaging work,
and to prove that the release pipeline itself works end to end.
Added
cargo install emeraldiannow works, without a git URL. All four crates are
on crates.io, so the site and the README point at the registry.- The crates.io workflow can be run by hand with
verify_onlyto check that
trusted publishing is still configured correctly, without publishing
anything. Worth doing after renaming this repository or the workflow file,
since either invalidates the trusted publisher silently.
Fixed
- The Homebrew tap is updated by the release, rather than only appearing to
be. The step guarded ongit diff, which reports tracked files only, so a
formula the tap had never held read as no change at all and was never
committed. The job reported success on every release from the first one
onwards while the tap stayed empty, which is whybrew installhad never
worked. - Tagging a release no longer fails when every crate is already published. The
check that made a re-run a no-op sat after authentication, and authenticating
is itself a failure until a trusted publisher exists.
[0.4.1] — 2026-08-07
A packaging release. Nothing about using the app has changed.
Changed
- The library crates are named after the project:
emeraldian-core,
emeraldian-themeandemeraldian-agent, where they wereotui-*. They are
about to go on crates.io, and a crate name cannot be changed or reclaimed once
it is taken, so this was cheap now and impossible later. - Two things deliberately kept their old names. Thread names read
emerald-agentrather thanemeraldian-agent, because Linux caps a thread
name at 15 bytes and refuses anything longer, which would have left those
threads nameless i...
v0.5.0
Changelog
All notable changes to this project are documented here. The format follows
Keep a Changelog, and versions follow
Semantic Versioning.
[0.5.0] — 2026-08-10
A minor rather than a patch release: vim mode is a new way to use the editor,
and the keyboard behaves differently while it is on. Nothing changes for anyone
who leaves it off, and a config.toml written by an earlier version still
loads — the new key has a default.
Added
-
F3closes the tab, in vim mode and out.Ctrl+Wstill does it with vim
off, but vim spends that key on the window prefix, and every plain
Ctrl+letter is already taken by the app, by vim, or by the terminal itself.
Ctrl+Shift+Wis not the answer either: without the Kitty protocol the Shift
cannot be encoded, so it arrives asCtrl+W, arms the prefix and swallows the
next keystroke. F-keys are unambiguous everywhere, which is whyF4carries
the vim toggle too. -
Vim mode in the note editor, on
F4. Normal, Insert, Visual and
Visual-Line.Motions
h j k l,gj/gk,w W b B e E ge,0 ^ $,gg G,{ }, and
f F t Twith;/,to repeat.jandkmove by source line as they do
in vim;gj/gkmove by the row on screen.Operators
d c y > <over any motion, or doubled for the line —dd,cc,
yy,>>,<<— with counts anywhere they are accepted in vim, sod3w
and3dwagree. Text objectsiw aw,i" a",i( a(,i[ a[,i{ a{,
with nesting counted so an inner pair wins.Single keys
i I a A o O,x X s S,D C Y,p P,r,J,~,u,
Ctrl+R, andCtrl+A/Ctrl+Xto increment and decrement.One resolver defines every motion, and both operators and visual-mode
movement go through it, sodwandwcannot disagree about where a word
ends. Vim's exclusive/inclusive distinction is modelled rather than
approximated:dwstops before the next word anddetakes the last letter
of this one.Outside Insert mode the Ctrl keys vim defines take vim's meanings rather than
the app's:Ctrl+Rredoes,Ctrl+D/Ctrl+UandCtrl+F/Ctrl+Bscroll,
Ctrl+A/Ctrl+Xadjust a number, andCtrl+O/Ctrl+Iwalk the note
history. In Insert mode onlyCtrl+WandCtrl+Uare claimed, for vim's
word- and line-delete. Everything else is unchanged there, in every other
pane, and with vim mode off; shifted combinations are left alone, so
Ctrl+Shift+Fstill searches.The app's commands move onto a
Spaceleader and its panes ontoCtrl+W.
PressingSpacedraws a which-key menu built from the same table that binds
the keys, so a binding cannot exist without being listed.Ctrl+W h/j/k/l
moves between the explorer, note and sidebar,[b/]bstep through tabs.Vim mode applies to the editor and not to reading: a note being read has no
buffer to act on, so the reading pane keeps every key it always had and
Ctrl+Eis still the way in. The two navigation keys are the exception,
because moving between panes and notes is not editing.A
:line, typed along the bottom row rather than in a dialog::w:wq
:x:q:q!:qa:e <name>:42:h, plus:setfor the handful of
options worth changing mid-session and:mkconfigto write them down. Most
are an existing action under a different name, so:wandCtrl+Sare not
two implementations of saving.In-buffer search on
/and?, withn/Nto step and:nohto clear.
A plain substring rather than a regular expression — notes are prose, and a
half-supported regex dialect would be worse than an honest literal one — and
case-insensitive until the pattern contains a capital, which is vim's
smartcase. Every match on screen is highlighted, not only the one jumped to..repeats the last change, including the text typed during it. It replays
the keys rather than a parsed command, which is the one representation that
covers an operator with a motion, a lonex, and an insert uniformly.Deliberately hard to get stuck in: the mode is named in the status bar, the
cursor is a block in Normal and a bar in Insert, a pending2dis shown as
it is typed, the hint bar leads with the way out, and the reference opens by
itself the first time it is ever switched on.F4works from anywhere,
including from inside Normal mode and over an open overlay.Off by default, and with it off the editor behaves exactly as it did before.
-
editor.viminconfig.toml, also reachable as/vim on|offand from the
command palette. It is the one setting written the moment it changes rather
than when settings are saved, because it decides what every key does and
silently losing it on the next launch is a different order of problem. -
Config::save_to, mirroringState::save_to, so tests that write settings
do not reach into the config directory of the machine running them. -
Forward navigation.
Action::Backnow records what it stepped away from, so
there is something to return to; opening a note any other way clears the
trail, as a browser does. Reachable asCtrl+Iin vim mode.
[0.4.2] — 2026-08-07
Nothing in the app changed. This release exists to carry the packaging work,
and to prove that the release pipeline itself works end to end.
Added
cargo install emeraldiannow works, without a git URL. All four crates are
on crates.io, so the site and the README point at the registry.- The crates.io workflow can be run by hand with
verify_onlyto check that
trusted publishing is still configured correctly, without publishing
anything. Worth doing after renaming this repository or the workflow file,
since either invalidates the trusted publisher silently.
Fixed
- The Homebrew tap is updated by the release, rather than only appearing to
be. The step guarded ongit diff, which reports tracked files only, so a
formula the tap had never held read as no change at all and was never
committed. The job reported success on every release from the first one
onwards while the tap stayed empty, which is whybrew installhad never
worked. - Tagging a release no longer fails when every crate is already published. The
check that made a re-run a no-op sat after authentication, and authenticating
is itself a failure until a trusted publisher exists.
[0.4.1] — 2026-08-07
A packaging release. Nothing about using the app has changed.
Changed
- The library crates are named after the project:
emeraldian-core,
emeraldian-themeandemeraldian-agent, where they wereotui-*. They are
about to go on crates.io, and a crate name cannot be changed or reclaimed once
it is taken, so this was cheap now and impossible later. - Two things deliberately kept their old names. Thread names read
emerald-agentrather thanemeraldian-agent, because Linux caps a thread
name at 15 bytes and refuses anything longer, which would have left those
threads nameless in a debugger. AndOTUI_BIN_DIR,OTUI_VERSION,
OTUI_STATE_FILEandOTUI_CA_BUNDLEkeep their prefix, because they may
already be exported in someone's shell profile and renaming them would not
fail loudly, it would quietly ignore what they had set.
Added
- The crates carry the metadata crates.io displays: repository, homepage,
keywords and categories. They had none, and a published version is immutable. .github/workflows/crates-io.yml, publishing on a release tag through OIDC
trusted publishing rather than a stored token.
[0.4.0] — 2026-08-07
The release that renames the project. A minor bump rather than a patch: the
binary you type is a different word, and no amount of care inside the code
makes that anything other than breaking.
Changed
- The project is now called emeraldian. It was
obsidian-tui, and that name
had to go: Obsidian is a trademark of Dynalist Inc., registered for exactly this
class of software, and Obsidian now ships a terminal interface of its own — so
the old name pointed at the wrong project twice over. One of Obsidian's
maintainers asked, reasonably, and this is that change. Nothing about what the
tool does has changed, and it still works on the same plain folder of Markdown
files it always did. - The binary is
emeraldian.emeraldian ~/Notes,emeraldian --list-vaults, and so on. Theobsidian://URIs the desktop app registers are
still answered, and theobsidian-darkandobsidian-lightthemes still have
the names they had — they describe Obsidian's own colour schemes, and they are
written into config files that already exist. - Your settings move themselves. Everything kept in
~/.config/obsidian-tui— config, custom themes, saved agent sessions, UI
state and stored API keys — is moved to~/.config/emeraldianthe first time
the renamed build starts. Nothing is copied or deleted, an existing
emeraldiandirectory always wins, and a failure leaves the old directory
exactly where it was. - The Homebrew formula is
emeraldian, and the npm package isemeraldian.
A Homebrew install has to be replaced rather than upgraded, because the formula
is a different one:
brew uninstall obsidian-tui && brew install iamrohithrnair/tap/emeraldian. - The site moved to https://emeraldian-tui.github.io, and the repository to
iamrohithrnair/emeraldian. GitHub redirects the old links.
[0.3.1] — 2026-08-06
Added
images.protocoloverrules the terminal's own answer. Terminals are asked
what they can draw and are believed, which is right almost everywhere; the
exception is one that claims a protocol it doesn't paint, where the picture
goes missing entirely and leaves a hole in the page. That is not a failure the
terminal can be asked about, so it can now be told instead:auto,kitty,
iterm2,sixelorhalfblocks. Half-blocks...
v0.4.2
Changelog
All notable changes to this project are documented here. The format follows
Keep a Changelog, and versions follow
Semantic Versioning.
[0.4.2] — 2026-08-07
Nothing in the app changed. This release exists to carry the packaging work,
and to prove that the release pipeline itself works end to end.
Added
cargo install emeraldiannow works, without a git URL. All four crates are
on crates.io, so the site and the README point at the registry.- The crates.io workflow can be run by hand with
verify_onlyto check that
trusted publishing is still configured correctly, without publishing
anything. Worth doing after renaming this repository or the workflow file,
since either invalidates the trusted publisher silently.
Fixed
- The Homebrew tap is updated by the release, rather than only appearing to
be. The step guarded ongit diff, which reports tracked files only, so a
formula the tap had never held read as no change at all and was never
committed. The job reported success on every release from the first one
onwards while the tap stayed empty, which is whybrew installhad never
worked. - Tagging a release no longer fails when every crate is already published. The
check that made a re-run a no-op sat after authentication, and authenticating
is itself a failure until a trusted publisher exists.
[0.4.1] — 2026-08-07
A packaging release. Nothing about using the app has changed.
Changed
- The library crates are named after the project:
emeraldian-core,
emeraldian-themeandemeraldian-agent, where they wereotui-*. They are
about to go on crates.io, and a crate name cannot be changed or reclaimed once
it is taken, so this was cheap now and impossible later. - Two things deliberately kept their old names. Thread names read
emerald-agentrather thanemeraldian-agent, because Linux caps a thread
name at 15 bytes and refuses anything longer, which would have left those
threads nameless in a debugger. AndOTUI_BIN_DIR,OTUI_VERSION,
OTUI_STATE_FILEandOTUI_CA_BUNDLEkeep their prefix, because they may
already be exported in someone's shell profile and renaming them would not
fail loudly, it would quietly ignore what they had set.
Added
- The crates carry the metadata crates.io displays: repository, homepage,
keywords and categories. They had none, and a published version is immutable. .github/workflows/crates-io.yml, publishing on a release tag through OIDC
trusted publishing rather than a stored token.
[0.4.0] — 2026-08-07
The release that renames the project. A minor bump rather than a patch: the
binary you type is a different word, and no amount of care inside the code
makes that anything other than breaking.
Changed
- The project is now called emeraldian. It was
obsidian-tui, and that name
had to go: Obsidian is a trademark of Dynalist Inc., registered for exactly this
class of software, and Obsidian now ships a terminal interface of its own — so
the old name pointed at the wrong project twice over. One of Obsidian's
maintainers asked, reasonably, and this is that change. Nothing about what the
tool does has changed, and it still works on the same plain folder of Markdown
files it always did. - The binary is
emeraldian.emeraldian ~/Notes,emeraldian --list-vaults, and so on. Theobsidian://URIs the desktop app registers are
still answered, and theobsidian-darkandobsidian-lightthemes still have
the names they had — they describe Obsidian's own colour schemes, and they are
written into config files that already exist. - Your settings move themselves. Everything kept in
~/.config/obsidian-tui— config, custom themes, saved agent sessions, UI
state and stored API keys — is moved to~/.config/emeraldianthe first time
the renamed build starts. Nothing is copied or deleted, an existing
emeraldiandirectory always wins, and a failure leaves the old directory
exactly where it was. - The Homebrew formula is
emeraldian, and the npm package isemeraldian.
A Homebrew install has to be replaced rather than upgraded, because the formula
is a different one:
brew uninstall obsidian-tui && brew install iamrohithrnair/tap/emeraldian. - The site moved to https://emeraldian-tui.github.io, and the repository to
iamrohithrnair/emeraldian. GitHub redirects the old links.
[0.3.1] — 2026-08-06
Added
images.protocoloverrules the terminal's own answer. Terminals are asked
what they can draw and are believed, which is right almost everywhere; the
exception is one that claims a protocol it doesn't paint, where the picture
goes missing entirely and leaves a hole in the page. That is not a failure the
terminal can be asked about, so it can now be told instead:auto,kitty,
iterm2,sixelorhalfblocks. Half-blocks are drawn out of ordinary
coloured cells, so they work anywhere text does. The cell size still comes
from the terminal, which answers that part correctly either way.
[0.3.0] — 2026-08-05
The release that makes the editor usable for actually writing in: text wraps,
the cursor goes where it looks like it goes, and Markdown is styled while you
type it instead of only once you stop.
Added
- Long lines wrap while editing. A line wider than the pane used to be cut
off at the edge, and since the editor had no way to pan sideways either, the
rest of it was not merely hidden but unreachable.editor.wraphas been in the
config file since the first release, documented as doing exactly this, and was
read by nothing; it now works, and setting it tofalsegenuinely pans instead.
A wrapped row is marked with a⤷in the gutter, so a soft wrap is still
distinguishable from a real line break, and a wrapped list item is indented to
sit under its own text rather than under its bullet. - The arrow keys,
Home,EndandPageUp/PageDownmove by the rows on
screen rather than by lines in the file. On a paragraph that fills four rows,
four presses ofDowncross it — which is what every other editor does, and
the only behavior that makes sense once text wraps. - Markdown is styled while you edit it, the way Obsidian's live preview
styles it: headings coloured and bold, bullets drawn as•, task boxes as
[☑], quotes as▎, and bold, italic, code, links, tags and==highlights==
in the same colours the reading pane uses. Nothing is hidden and nothing moves:
every glyph substituted in is exactly as wide as the character it stands for,
which is what keeps the caret exactly where the character is. The line the
cursor is on shows its syntax at full contrast, so what you edit is what you
see, and because the widths match, the text does not shift as the cursor
arrives or leaves. A fenced code block is drawn on its own background and its
contents are never read as Markdown. - Clicking places the cursor, and dragging selects. Both are resolved against
the text as it was actually drawn, so they land on the character under the
pointer however the line wrapped. Entercontinues a list.-,*,1.and- [ ]carry down to the
next line, an ordered list counts up, and a task always starts unchecked —
carrying "done" onto a line nobody has done yet would be a lie. Pressing it on
an item with nothing on it ends the list, which is how you stop.TabandShift+Tabnest and unnest a list item, or indent every line of
a selection. In proseTabis still a tab.Shift+Tabdid nothing at all
before.- The status bar shows
Ln 12/40, Col 3while editing, and how many characters
are selected.
Fixed
- The mouse wheel now scrolls while editing. It was writing to the reading
view's scroll offset, which the editor never reads, so it silently did nothing. - The caret was positioned by counting characters and then clamped to the pane,
so on a long line it parked at the right margin and lied about where it was,
and on CJK or emoji text — where one character is two columns wide — it was in
the wrong place on any line containing one. Ctrl+Eno longer reflows the note. Reading laid prose out from the pane's
left edge while editing started it after the line-number gutter, so the two
modes wrapped the same paragraph to different widths and every line broke
somewhere else on the way in and out of the editor. The gutter's width is now
reserved in both modes — blank while reading — so switching restyles the page
without moving a word of it. It costs four columns of reading width, and
nothing at all when line numbers are switched off.- The scrollbar counted lines rather than rows, so it misreported how far through
a note with any wrapped lines in it you were. - The cursor line's highlight and a code block's background now reach the edge of
the pane instead of stopping wherever the text happened to end. - A caret was drawn in the note pane even when the keyboard belonged to the chat
panel or the file explorer. - The editor no longer scrolls past the end of a note that would fit on screen.
- The outline scrolled to the wrong place. Picking a heading set the reading
view's scroll offset to the heading's line number in the file, but that offset
counts rendered rows — and prose wraps, headings gain a rule under them and a
picture takes a dozen rows, so the two numbers drift further apart the further
down the note you go. The draw pass now writes down which row each block landed
on, the same way it already records where it put every picture, and the jump is
translated through it.
Changed
- Moved to the 2024 edition, stable since Rust 1.85 and comfortably under
the 1.90 floor the dependencies already set. Mostly invisible:cargo fix
found three real changes and rustfmt'...
v0.4.1
Changelog
All notable changes to this project are documented here. The format follows
Keep a Changelog, and versions follow
Semantic Versioning.
[0.4.1] — 2026-08-07
A packaging release. Nothing about using the app has changed.
Changed
- The library crates are named after the project:
emeraldian-core,
emeraldian-themeandemeraldian-agent, where they wereotui-*. They are
about to go on crates.io, and a crate name cannot be changed or reclaimed once
it is taken, so this was cheap now and impossible later. - Two things deliberately kept their old names. Thread names read
emerald-agentrather thanemeraldian-agent, because Linux caps a thread
name at 15 bytes and refuses anything longer, which would have left those
threads nameless in a debugger. AndOTUI_BIN_DIR,OTUI_VERSION,
OTUI_STATE_FILEandOTUI_CA_BUNDLEkeep their prefix, because they may
already be exported in someone's shell profile and renaming them would not
fail loudly, it would quietly ignore what they had set.
Added
- The crates carry the metadata crates.io displays: repository, homepage,
keywords and categories. They had none, and a published version is immutable. .github/workflows/crates-io.yml, publishing on a release tag through OIDC
trusted publishing rather than a stored token.
[0.4.0] — 2026-08-07
The release that renames the project. A minor bump rather than a patch: the
binary you type is a different word, and no amount of care inside the code
makes that anything other than breaking.
Changed
- The project is now called emeraldian. It was
obsidian-tui, and that name
had to go: Obsidian is a trademark of Dynalist Inc., registered for exactly this
class of software, and Obsidian now ships a terminal interface of its own — so
the old name pointed at the wrong project twice over. One of Obsidian's
maintainers asked, reasonably, and this is that change. Nothing about what the
tool does has changed, and it still works on the same plain folder of Markdown
files it always did. - The binary is
emeraldian.emeraldian ~/Notes,emeraldian --list-vaults, and so on. Theobsidian://URIs the desktop app registers are
still answered, and theobsidian-darkandobsidian-lightthemes still have
the names they had — they describe Obsidian's own colour schemes, and they are
written into config files that already exist. - Your settings move themselves. Everything kept in
~/.config/obsidian-tui— config, custom themes, saved agent sessions, UI
state and stored API keys — is moved to~/.config/emeraldianthe first time
the renamed build starts. Nothing is copied or deleted, an existing
emeraldiandirectory always wins, and a failure leaves the old directory
exactly where it was. - The Homebrew formula is
emeraldian, and the npm package isemeraldian.
A Homebrew install has to be replaced rather than upgraded, because the formula
is a different one:
brew uninstall obsidian-tui && brew install iamrohithrnair/tap/emeraldian. - The site moved to https://emeraldian-tui.github.io, and the repository to
iamrohithrnair/emeraldian. GitHub redirects the old links.
[0.3.1] — 2026-08-06
Added
images.protocoloverrules the terminal's own answer. Terminals are asked
what they can draw and are believed, which is right almost everywhere; the
exception is one that claims a protocol it doesn't paint, where the picture
goes missing entirely and leaves a hole in the page. That is not a failure the
terminal can be asked about, so it can now be told instead:auto,kitty,
iterm2,sixelorhalfblocks. Half-blocks are drawn out of ordinary
coloured cells, so they work anywhere text does. The cell size still comes
from the terminal, which answers that part correctly either way.
[0.3.0] — 2026-08-05
The release that makes the editor usable for actually writing in: text wraps,
the cursor goes where it looks like it goes, and Markdown is styled while you
type it instead of only once you stop.
Added
- Long lines wrap while editing. A line wider than the pane used to be cut
off at the edge, and since the editor had no way to pan sideways either, the
rest of it was not merely hidden but unreachable.editor.wraphas been in the
config file since the first release, documented as doing exactly this, and was
read by nothing; it now works, and setting it tofalsegenuinely pans instead.
A wrapped row is marked with a⤷in the gutter, so a soft wrap is still
distinguishable from a real line break, and a wrapped list item is indented to
sit under its own text rather than under its bullet. - The arrow keys,
Home,EndandPageUp/PageDownmove by the rows on
screen rather than by lines in the file. On a paragraph that fills four rows,
four presses ofDowncross it — which is what every other editor does, and
the only behavior that makes sense once text wraps. - Markdown is styled while you edit it, the way Obsidian's live preview
styles it: headings coloured and bold, bullets drawn as•, task boxes as
[☑], quotes as▎, and bold, italic, code, links, tags and==highlights==
in the same colours the reading pane uses. Nothing is hidden and nothing moves:
every glyph substituted in is exactly as wide as the character it stands for,
which is what keeps the caret exactly where the character is. The line the
cursor is on shows its syntax at full contrast, so what you edit is what you
see, and because the widths match, the text does not shift as the cursor
arrives or leaves. A fenced code block is drawn on its own background and its
contents are never read as Markdown. - Clicking places the cursor, and dragging selects. Both are resolved against
the text as it was actually drawn, so they land on the character under the
pointer however the line wrapped. Entercontinues a list.-,*,1.and- [ ]carry down to the
next line, an ordered list counts up, and a task always starts unchecked —
carrying "done" onto a line nobody has done yet would be a lie. Pressing it on
an item with nothing on it ends the list, which is how you stop.TabandShift+Tabnest and unnest a list item, or indent every line of
a selection. In proseTabis still a tab.Shift+Tabdid nothing at all
before.- The status bar shows
Ln 12/40, Col 3while editing, and how many characters
are selected.
Fixed
- The mouse wheel now scrolls while editing. It was writing to the reading
view's scroll offset, which the editor never reads, so it silently did nothing. - The caret was positioned by counting characters and then clamped to the pane,
so on a long line it parked at the right margin and lied about where it was,
and on CJK or emoji text — where one character is two columns wide — it was in
the wrong place on any line containing one. Ctrl+Eno longer reflows the note. Reading laid prose out from the pane's
left edge while editing started it after the line-number gutter, so the two
modes wrapped the same paragraph to different widths and every line broke
somewhere else on the way in and out of the editor. The gutter's width is now
reserved in both modes — blank while reading — so switching restyles the page
without moving a word of it. It costs four columns of reading width, and
nothing at all when line numbers are switched off.- The scrollbar counted lines rather than rows, so it misreported how far through
a note with any wrapped lines in it you were. - The cursor line's highlight and a code block's background now reach the edge of
the pane instead of stopping wherever the text happened to end. - A caret was drawn in the note pane even when the keyboard belonged to the chat
panel or the file explorer. - The editor no longer scrolls past the end of a note that would fit on screen.
- The outline scrolled to the wrong place. Picking a heading set the reading
view's scroll offset to the heading's line number in the file, but that offset
counts rendered rows — and prose wraps, headings gain a rule under them and a
picture takes a dozen rows, so the two numbers drift further apart the further
down the note you go. The draw pass now writes down which row each block landed
on, the same way it already records where it put every picture, and the jump is
translated through it.
Changed
- Moved to the 2024 edition, stable since Rust 1.85 and comfortably under
the 1.90 floor the dependencies already set. Mostly invisible:cargo fix
found three real changes and rustfmt's 2024 style edition reordered imports.
The substantive one is thatenv::set_varis unsafe in 2024, which correctly
flagged a bug — a test set an environment variable while other modules read
the environment from parallel test threads. The state module now takes a path
(load_from/save_to) so nothing has to mutate a global to be testable. - Nested
if letblocks collapsed into let-chains where 2024 allows it.
[0.2.0] — 2026-08-05
The release that puts the parts of a vault a text reader could never show —
pictures and drawings — on the screen, and adds an assistant that can be set up
without leaving the app.
Added
- Pictures are drawn in the reading pane.
![[chart.png]]and
render as real images in terminals that support
Kitty's graphics protocol, iTerm2's, or sixel, and as half-block mosaics
everywhere else. The terminal is asked what it can do at startup, before the
alternate screen is entered, since that question is answered on stdin. - Obsidian's
![[chart.png|400]]width, in pixels, is honoured. Anything else
after the pipe stays an alias, which is only ever used as alt text. - Decoding happens off the draw loop, so a large photo d...
v0.4.0
Changelog
All notable changes to this project are documented here. The format follows
Keep a Changelog, and versions follow
Semantic Versioning.
[0.4.0] — 2026-08-07
The release that renames the project. A minor bump rather than a patch: the
binary you type is a different word, and no amount of care inside the code
makes that anything other than breaking.
Changed
- The project is now called emeraldian. It was
obsidian-tui, and that name
had to go: Obsidian is a trademark of Dynalist Inc., registered for exactly this
class of software, and Obsidian now ships a terminal interface of its own — so
the old name pointed at the wrong project twice over. One of Obsidian's
maintainers asked, reasonably, and this is that change. Nothing about what the
tool does has changed, and it still works on the same plain folder of Markdown
files it always did. - The binary is
emeraldian.emeraldian ~/Notes,emeraldian --list-vaults, and so on. Theobsidian://URIs the desktop app registers are
still answered, and theobsidian-darkandobsidian-lightthemes still have
the names they had — they describe Obsidian's own colour schemes, and they are
written into config files that already exist. - Your settings move themselves. Everything kept in
~/.config/obsidian-tui— config, custom themes, saved agent sessions, UI
state and stored API keys — is moved to~/.config/emeraldianthe first time
the renamed build starts. Nothing is copied or deleted, an existing
emeraldiandirectory always wins, and a failure leaves the old directory
exactly where it was. - The Homebrew formula is
emeraldian, and the npm package isemeraldian.
A Homebrew install has to be replaced rather than upgraded, because the formula
is a different one:
brew uninstall obsidian-tui && brew install iamrohithrnair/tap/emeraldian. - The site moved to https://emeraldian-tui.github.io, and the repository to
iamrohithrnair/emeraldian. GitHub redirects the old links.
[0.3.1] — 2026-08-06
Added
images.protocoloverrules the terminal's own answer. Terminals are asked
what they can draw and are believed, which is right almost everywhere; the
exception is one that claims a protocol it doesn't paint, where the picture
goes missing entirely and leaves a hole in the page. That is not a failure the
terminal can be asked about, so it can now be told instead:auto,kitty,
iterm2,sixelorhalfblocks. Half-blocks are drawn out of ordinary
coloured cells, so they work anywhere text does. The cell size still comes
from the terminal, which answers that part correctly either way.
[0.3.0] — 2026-08-05
The release that makes the editor usable for actually writing in: text wraps,
the cursor goes where it looks like it goes, and Markdown is styled while you
type it instead of only once you stop.
Added
- Long lines wrap while editing. A line wider than the pane used to be cut
off at the edge, and since the editor had no way to pan sideways either, the
rest of it was not merely hidden but unreachable.editor.wraphas been in the
config file since the first release, documented as doing exactly this, and was
read by nothing; it now works, and setting it tofalsegenuinely pans instead.
A wrapped row is marked with a⤷in the gutter, so a soft wrap is still
distinguishable from a real line break, and a wrapped list item is indented to
sit under its own text rather than under its bullet. - The arrow keys,
Home,EndandPageUp/PageDownmove by the rows on
screen rather than by lines in the file. On a paragraph that fills four rows,
four presses ofDowncross it — which is what every other editor does, and
the only behavior that makes sense once text wraps. - Markdown is styled while you edit it, the way Obsidian's live preview
styles it: headings coloured and bold, bullets drawn as•, task boxes as
[☑], quotes as▎, and bold, italic, code, links, tags and==highlights==
in the same colours the reading pane uses. Nothing is hidden and nothing moves:
every glyph substituted in is exactly as wide as the character it stands for,
which is what keeps the caret exactly where the character is. The line the
cursor is on shows its syntax at full contrast, so what you edit is what you
see, and because the widths match, the text does not shift as the cursor
arrives or leaves. A fenced code block is drawn on its own background and its
contents are never read as Markdown. - Clicking places the cursor, and dragging selects. Both are resolved against
the text as it was actually drawn, so they land on the character under the
pointer however the line wrapped. Entercontinues a list.-,*,1.and- [ ]carry down to the
next line, an ordered list counts up, and a task always starts unchecked —
carrying "done" onto a line nobody has done yet would be a lie. Pressing it on
an item with nothing on it ends the list, which is how you stop.TabandShift+Tabnest and unnest a list item, or indent every line of
a selection. In proseTabis still a tab.Shift+Tabdid nothing at all
before.- The status bar shows
Ln 12/40, Col 3while editing, and how many characters
are selected.
Fixed
- The mouse wheel now scrolls while editing. It was writing to the reading
view's scroll offset, which the editor never reads, so it silently did nothing. - The caret was positioned by counting characters and then clamped to the pane,
so on a long line it parked at the right margin and lied about where it was,
and on CJK or emoji text — where one character is two columns wide — it was in
the wrong place on any line containing one. Ctrl+Eno longer reflows the note. Reading laid prose out from the pane's
left edge while editing started it after the line-number gutter, so the two
modes wrapped the same paragraph to different widths and every line broke
somewhere else on the way in and out of the editor. The gutter's width is now
reserved in both modes — blank while reading — so switching restyles the page
without moving a word of it. It costs four columns of reading width, and
nothing at all when line numbers are switched off.- The scrollbar counted lines rather than rows, so it misreported how far through
a note with any wrapped lines in it you were. - The cursor line's highlight and a code block's background now reach the edge of
the pane instead of stopping wherever the text happened to end. - A caret was drawn in the note pane even when the keyboard belonged to the chat
panel or the file explorer. - The editor no longer scrolls past the end of a note that would fit on screen.
- The outline scrolled to the wrong place. Picking a heading set the reading
view's scroll offset to the heading's line number in the file, but that offset
counts rendered rows — and prose wraps, headings gain a rule under them and a
picture takes a dozen rows, so the two numbers drift further apart the further
down the note you go. The draw pass now writes down which row each block landed
on, the same way it already records where it put every picture, and the jump is
translated through it.
Changed
- Moved to the 2024 edition, stable since Rust 1.85 and comfortably under
the 1.90 floor the dependencies already set. Mostly invisible:cargo fix
found three real changes and rustfmt's 2024 style edition reordered imports.
The substantive one is thatenv::set_varis unsafe in 2024, which correctly
flagged a bug — a test set an environment variable while other modules read
the environment from parallel test threads. The state module now takes a path
(load_from/save_to) so nothing has to mutate a global to be testable. - Nested
if letblocks collapsed into let-chains where 2024 allows it.
[0.2.0] — 2026-08-05
The release that puts the parts of a vault a text reader could never show —
pictures and drawings — on the screen, and adds an assistant that can be set up
without leaving the app.
Added
- Pictures are drawn in the reading pane.
![[chart.png]]and
render as real images in terminals that support
Kitty's graphics protocol, iTerm2's, or sixel, and as half-block mosaics
everywhere else. The terminal is asked what it can do at startup, before the
alternate screen is entered, since that question is answered on stdin. - Obsidian's
![[chart.png|400]]width, in pixels, is honoured. Anything else
after the pipe stays an alias, which is only ever used as alt text. - Decoding happens off the draw loop, so a large photo doesn't stall scrolling.
The rows a picture will need are worked out from its header on the first
frame, so text below it doesn't jump when the picture arrives. - Pictures are resampled with a Lanczos filter rather than nearest-neighbour.
Nearest-neighbour keeps whichever pixel a sample lands on and discards the
rest, which deletes most of the strokes that make text in a screenshot or a
diagram legible. The cost is paid once, on the worker thread. images.enabledandimages.max_height_percentin the config. The cap is a
share of the reading pane rather than a fixed number of rows, so a picture is
as large as the window allows: a cap small enough for an 80x24 terminal leaves
a diagram unreadable on a full-screen one. A picture is never scaled up, and
one wider than the pane is scaled down to fit.- Startup reports which graphics protocol the terminal is using and how big one
cell is. A terminal that quietly fell back to half-blocks is otherwise
indistinguishable from one that drew the picture badly. - The reading pane pans sideways.
←/→(orh/l) move across content
wider than the window;greturns to the top-left. Tables are laid o...
v0.3.1
Changelog
All notable changes to this project are documented here. The format follows
Keep a Changelog, and versions follow
Semantic Versioning.
[0.3.1] — 2026-08-06
Added
images.protocoloverrules the terminal's own answer. Terminals are asked
what they can draw and are believed, which is right almost everywhere; the
exception is one that claims a protocol it doesn't paint, where the picture
goes missing entirely and leaves a hole in the page. That is not a failure the
terminal can be asked about, so it can now be told instead:auto,kitty,
iterm2,sixelorhalfblocks. Half-blocks are drawn out of ordinary
coloured cells, so they work anywhere text does. The cell size still comes
from the terminal, which answers that part correctly either way.
[0.3.0] — 2026-08-05
The release that makes the editor usable for actually writing in: text wraps,
the cursor goes where it looks like it goes, and Markdown is styled while you
type it instead of only once you stop.
Added
- Long lines wrap while editing. A line wider than the pane used to be cut
off at the edge, and since the editor had no way to pan sideways either, the
rest of it was not merely hidden but unreachable.editor.wraphas been in the
config file since the first release, documented as doing exactly this, and was
read by nothing; it now works, and setting it tofalsegenuinely pans instead.
A wrapped row is marked with a⤷in the gutter, so a soft wrap is still
distinguishable from a real line break, and a wrapped list item is indented to
sit under its own text rather than under its bullet. - The arrow keys,
Home,EndandPageUp/PageDownmove by the rows on
screen rather than by lines in the file. On a paragraph that fills four rows,
four presses ofDowncross it — which is what every other editor does, and
the only behavior that makes sense once text wraps. - Markdown is styled while you edit it, the way Obsidian's live preview
styles it: headings coloured and bold, bullets drawn as•, task boxes as
[☑], quotes as▎, and bold, italic, code, links, tags and==highlights==
in the same colours the reading pane uses. Nothing is hidden and nothing moves:
every glyph substituted in is exactly as wide as the character it stands for,
which is what keeps the caret exactly where the character is. The line the
cursor is on shows its syntax at full contrast, so what you edit is what you
see, and because the widths match, the text does not shift as the cursor
arrives or leaves. A fenced code block is drawn on its own background and its
contents are never read as Markdown. - Clicking places the cursor, and dragging selects. Both are resolved against
the text as it was actually drawn, so they land on the character under the
pointer however the line wrapped. Entercontinues a list.-,*,1.and- [ ]carry down to the
next line, an ordered list counts up, and a task always starts unchecked —
carrying "done" onto a line nobody has done yet would be a lie. Pressing it on
an item with nothing on it ends the list, which is how you stop.TabandShift+Tabnest and unnest a list item, or indent every line of
a selection. In proseTabis still a tab.Shift+Tabdid nothing at all
before.- The status bar shows
Ln 12/40, Col 3while editing, and how many characters
are selected.
Fixed
- The mouse wheel now scrolls while editing. It was writing to the reading
view's scroll offset, which the editor never reads, so it silently did nothing. - The caret was positioned by counting characters and then clamped to the pane,
so on a long line it parked at the right margin and lied about where it was,
and on CJK or emoji text — where one character is two columns wide — it was in
the wrong place on any line containing one. Ctrl+Eno longer reflows the note. Reading laid prose out from the pane's
left edge while editing started it after the line-number gutter, so the two
modes wrapped the same paragraph to different widths and every line broke
somewhere else on the way in and out of the editor. The gutter's width is now
reserved in both modes — blank while reading — so switching restyles the page
without moving a word of it. It costs four columns of reading width, and
nothing at all when line numbers are switched off.- The scrollbar counted lines rather than rows, so it misreported how far through
a note with any wrapped lines in it you were. - The cursor line's highlight and a code block's background now reach the edge of
the pane instead of stopping wherever the text happened to end. - A caret was drawn in the note pane even when the keyboard belonged to the chat
panel or the file explorer. - The editor no longer scrolls past the end of a note that would fit on screen.
- The outline scrolled to the wrong place. Picking a heading set the reading
view's scroll offset to the heading's line number in the file, but that offset
counts rendered rows — and prose wraps, headings gain a rule under them and a
picture takes a dozen rows, so the two numbers drift further apart the further
down the note you go. The draw pass now writes down which row each block landed
on, the same way it already records where it put every picture, and the jump is
translated through it.
Changed
- Moved to the 2024 edition, stable since Rust 1.85 and comfortably under
the 1.90 floor the dependencies already set. Mostly invisible:cargo fix
found three real changes and rustfmt's 2024 style edition reordered imports.
The substantive one is thatenv::set_varis unsafe in 2024, which correctly
flagged a bug — a test set an environment variable while other modules read
the environment from parallel test threads. The state module now takes a path
(load_from/save_to) so nothing has to mutate a global to be testable. - Nested
if letblocks collapsed into let-chains where 2024 allows it.
[0.2.0] — 2026-08-05
The release that puts the parts of a vault a text reader could never show —
pictures and drawings — on the screen, and adds an assistant that can be set up
without leaving the app.
Added
- Pictures are drawn in the reading pane.
![[chart.png]]and
render as real images in terminals that support
Kitty's graphics protocol, iTerm2's, or sixel, and as half-block mosaics
everywhere else. The terminal is asked what it can do at startup, before the
alternate screen is entered, since that question is answered on stdin. - Obsidian's
![[chart.png|400]]width, in pixels, is honoured. Anything else
after the pipe stays an alias, which is only ever used as alt text. - Decoding happens off the draw loop, so a large photo doesn't stall scrolling.
The rows a picture will need are worked out from its header on the first
frame, so text below it doesn't jump when the picture arrives. - Pictures are resampled with a Lanczos filter rather than nearest-neighbour.
Nearest-neighbour keeps whichever pixel a sample lands on and discards the
rest, which deletes most of the strokes that make text in a screenshot or a
diagram legible. The cost is paid once, on the worker thread. images.enabledandimages.max_height_percentin the config. The cap is a
share of the reading pane rather than a fixed number of rows, so a picture is
as large as the window allows: a cap small enough for an 80x24 terminal leaves
a diagram unreadable on a full-screen one. A picture is never scaled up, and
one wider than the pane is scaled down to fit.- Startup reports which graphics protocol the terminal is using and how big one
cell is. A terminal that quietly fell back to half-blocks is otherwise
indistinguishable from one that drew the picture badly. - The reading pane pans sideways.
←/→(orh/l) move across content
wider than the window;greturns to the top-left. Tables are laid out at
their content's width and panned across, instead of being squeezed to fit —
eight columns divided between a narrow pane left three characters each, which
is not a narrow table but an unreadable one. - A picture that can't be drawn — no support in the terminal, a missing file, a
URL on the web — leaves its alt text in place rather than a hole. - Excalidraw notes open as drawings. A
.excalidraw.mdnote used to show a
warning banner and a wall of compressed base64, which is the one thing in a
vault that a text reader could make no sense of at all. The scene is now drawn
as vectors on a braille canvas: rectangles, ellipses, diamonds, lines, arrows
with heads, freehand strokes, and the text labels, rotation included. Both the
plainjsonand the compressedcompressed-jsonblock the Obsidian plugin
writes are read, as is a legacy.excalidrawJSON file. - The drawing is scaled to the pane's width and scrolls vertically like the prose
it replaces, since diagrams are usually far taller than a terminal. - Excalidraw draws near-black ink on white paper. A stroke that would vanish into
the theme's background is drawn in the theme's text colour instead; every colour
the author chose deliberately is kept. - The assistant can be set up from inside the app.
/provideroffers the
eight backends it knows how to reach — Anthropic, OpenAI, Ollama, LM Studio,
OpenRouter, Groq, a custom endpoint, or off — and choosing one sets its address
as well, so nobody has to remember Ollama's port. /modelasks the provider which models it actually has and offers the list.
Better than a table shipped in the binary: names change monthly, and a local
server's list depends on what you've pulled. The request runs off the draw loop,
so the app stays usable while it waits./keystores an API key for the current provider, ...
v0.3.0
Changelog
All notable changes to this project are documented here. The format follows
Keep a Changelog, and versions follow
Semantic Versioning.
[0.3.0] — 2026-08-05
The release that makes the editor usable for actually writing in: text wraps,
the cursor goes where it looks like it goes, and Markdown is styled while you
type it instead of only once you stop.
Added
- Long lines wrap while editing. A line wider than the pane used to be cut
off at the edge, and since the editor had no way to pan sideways either, the
rest of it was not merely hidden but unreachable.editor.wraphas been in the
config file since the first release, documented as doing exactly this, and was
read by nothing; it now works, and setting it tofalsegenuinely pans instead.
A wrapped row is marked with a⤷in the gutter, so a soft wrap is still
distinguishable from a real line break, and a wrapped list item is indented to
sit under its own text rather than under its bullet. - The arrow keys,
Home,EndandPageUp/PageDownmove by the rows on
screen rather than by lines in the file. On a paragraph that fills four rows,
four presses ofDowncross it — which is what every other editor does, and
the only behavior that makes sense once text wraps. - Markdown is styled while you edit it, the way Obsidian's live preview
styles it: headings coloured and bold, bullets drawn as•, task boxes as
[☑], quotes as▎, and bold, italic, code, links, tags and==highlights==
in the same colours the reading pane uses. Nothing is hidden and nothing moves:
every glyph substituted in is exactly as wide as the character it stands for,
which is what keeps the caret exactly where the character is. The line the
cursor is on shows its syntax at full contrast, so what you edit is what you
see, and because the widths match, the text does not shift as the cursor
arrives or leaves. A fenced code block is drawn on its own background and its
contents are never read as Markdown. - Clicking places the cursor, and dragging selects. Both are resolved against
the text as it was actually drawn, so they land on the character under the
pointer however the line wrapped. Entercontinues a list.-,*,1.and- [ ]carry down to the
next line, an ordered list counts up, and a task always starts unchecked —
carrying "done" onto a line nobody has done yet would be a lie. Pressing it on
an item with nothing on it ends the list, which is how you stop.TabandShift+Tabnest and unnest a list item, or indent every line of
a selection. In proseTabis still a tab.Shift+Tabdid nothing at all
before.- The status bar shows
Ln 12/40, Col 3while editing, and how many characters
are selected.
Fixed
- The mouse wheel now scrolls while editing. It was writing to the reading
view's scroll offset, which the editor never reads, so it silently did nothing. - The caret was positioned by counting characters and then clamped to the pane,
so on a long line it parked at the right margin and lied about where it was,
and on CJK or emoji text — where one character is two columns wide — it was in
the wrong place on any line containing one. Ctrl+Eno longer reflows the note. Reading laid prose out from the pane's
left edge while editing started it after the line-number gutter, so the two
modes wrapped the same paragraph to different widths and every line broke
somewhere else on the way in and out of the editor. The gutter's width is now
reserved in both modes — blank while reading — so switching restyles the page
without moving a word of it. It costs four columns of reading width, and
nothing at all when line numbers are switched off.- The scrollbar counted lines rather than rows, so it misreported how far through
a note with any wrapped lines in it you were. - The cursor line's highlight and a code block's background now reach the edge of
the pane instead of stopping wherever the text happened to end. - A caret was drawn in the note pane even when the keyboard belonged to the chat
panel or the file explorer. - The editor no longer scrolls past the end of a note that would fit on screen.
- The outline scrolled to the wrong place. Picking a heading set the reading
view's scroll offset to the heading's line number in the file, but that offset
counts rendered rows — and prose wraps, headings gain a rule under them and a
picture takes a dozen rows, so the two numbers drift further apart the further
down the note you go. The draw pass now writes down which row each block landed
on, the same way it already records where it put every picture, and the jump is
translated through it.
Changed
- Moved to the 2024 edition, stable since Rust 1.85 and comfortably under
the 1.90 floor the dependencies already set. Mostly invisible:cargo fix
found three real changes and rustfmt's 2024 style edition reordered imports.
The substantive one is thatenv::set_varis unsafe in 2024, which correctly
flagged a bug — a test set an environment variable while other modules read
the environment from parallel test threads. The state module now takes a path
(load_from/save_to) so nothing has to mutate a global to be testable. - Nested
if letblocks collapsed into let-chains where 2024 allows it.
[0.2.0] — 2026-08-05
The release that puts the parts of a vault a text reader could never show —
pictures and drawings — on the screen, and adds an assistant that can be set up
without leaving the app.
Added
- Pictures are drawn in the reading pane.
![[chart.png]]and
render as real images in terminals that support
Kitty's graphics protocol, iTerm2's, or sixel, and as half-block mosaics
everywhere else. The terminal is asked what it can do at startup, before the
alternate screen is entered, since that question is answered on stdin. - Obsidian's
![[chart.png|400]]width, in pixels, is honoured. Anything else
after the pipe stays an alias, which is only ever used as alt text. - Decoding happens off the draw loop, so a large photo doesn't stall scrolling.
The rows a picture will need are worked out from its header on the first
frame, so text below it doesn't jump when the picture arrives. - Pictures are resampled with a Lanczos filter rather than nearest-neighbour.
Nearest-neighbour keeps whichever pixel a sample lands on and discards the
rest, which deletes most of the strokes that make text in a screenshot or a
diagram legible. The cost is paid once, on the worker thread. images.enabledandimages.max_height_percentin the config. The cap is a
share of the reading pane rather than a fixed number of rows, so a picture is
as large as the window allows: a cap small enough for an 80x24 terminal leaves
a diagram unreadable on a full-screen one. A picture is never scaled up, and
one wider than the pane is scaled down to fit.- Startup reports which graphics protocol the terminal is using and how big one
cell is. A terminal that quietly fell back to half-blocks is otherwise
indistinguishable from one that drew the picture badly. - The reading pane pans sideways.
←/→(orh/l) move across content
wider than the window;greturns to the top-left. Tables are laid out at
their content's width and panned across, instead of being squeezed to fit —
eight columns divided between a narrow pane left three characters each, which
is not a narrow table but an unreadable one. - A picture that can't be drawn — no support in the terminal, a missing file, a
URL on the web — leaves its alt text in place rather than a hole. - Excalidraw notes open as drawings. A
.excalidraw.mdnote used to show a
warning banner and a wall of compressed base64, which is the one thing in a
vault that a text reader could make no sense of at all. The scene is now drawn
as vectors on a braille canvas: rectangles, ellipses, diamonds, lines, arrows
with heads, freehand strokes, and the text labels, rotation included. Both the
plainjsonand the compressedcompressed-jsonblock the Obsidian plugin
writes are read, as is a legacy.excalidrawJSON file. - The drawing is scaled to the pane's width and scrolls vertically like the prose
it replaces, since diagrams are usually far taller than a terminal. - Excalidraw draws near-black ink on white paper. A stroke that would vanish into
the theme's background is drawn in the theme's text colour instead; every colour
the author chose deliberately is kept. - The assistant can be set up from inside the app.
/provideroffers the
eight backends it knows how to reach — Anthropic, OpenAI, Ollama, LM Studio,
OpenRouter, Groq, a custom endpoint, or off — and choosing one sets its address
as well, so nobody has to remember Ollama's port. /modelasks the provider which models it actually has and offers the list.
Better than a table shipped in the binary: names change monthly, and a local
server's list depends on what you've pulled. The request runs off the draw loop,
so the app stays usable while it waits./keystores an API key for the current provider, typed into a prompt that
shows dots. Kept inauth.jsonbeside the config, mode0600— deliberately
not inconfig.toml, which people commit to dotfiles repositories. An exported
variable still wins, so nothing that worked before behaves differently.- The chat panel's title now names the model that will answer, or says what is
missing and which command fixes it. - The assistant works behind a corporate TLS proxy. Such a proxy re-signs
traffic with the company's own certificate authority, which is in the machine's
trust store but not in the root list compiled into the binary, so every request
failed with "unknown issuer". ...
v0.1.2
Changelog
All notable changes to this project are documented here. The format follows
Keep a Changelog, and versions follow
Semantic Versioning.
0.1.2 — 2026-07-27
Added
- The file explorer sorts by modification time. Notes now open with the most
recently edited at the top, which is usually where you left off.sin the
explorer steps through the six orders (modified, created and file name, each
both ways), as does/sort;/sort listshows them with the current one
marked. Folders stay alphabetical whichever order is chosen, since a folder's
timestamp changes for reasons that aren't visible on screen. - The order is stored as
ui.sort_orderin the config, so it survives a
restart. An unrecognised value falls back to the default rather than costing
you the rest of the file. NoteMetagained acreatedtimestamp, falling back to the modification time
on filesystems that don't record one.- The
?overlay gained a "File explorer" section, which it had been missing.
0.1.1 — 2026-07-27
Fixed
- Graph nodes render as solid discs. Nodes were drawn by sampling a fixed
17 points regardless of size, which left gaps as soon as a node was more than
a couple of dots across and turned the graph into a field of speckle. They are
now rasterized onto the braille canvas's own dot lattice. Unresolved links and
attachments draw as hollow rings, so the notes you meant to write stand out. - Resetting the graph view frames the graph.
0recentred on the origin,
but the force layout drifts away from it as it settles, so "reset" pushed the
view off the graph entirely. It now fits to the layout's actual bounds. - Labels no longer cover the nodes they name. Every node's full footprint is
reserved before any label is placed. - The graph legend has its own row instead of being painted over the canvas.
- Shortcut hints, the
?overlay and--helplisted keys that didn't exist or
had the wrong case (lrather thanLfor labels). They now match the real
bindings, and tests fail the build if they drift again. - The README claimed Obsidian has no official CLI. It does.
Added
- Slash commands in the assistant panel. Type
/for a completion list;
Tabcompletes,Enterruns. Switch backend with/provider,/modeland
/base-url, check credentials with/loginand/status, keep and reload
conversations with/save,/resumeand/sessions, and free up context
with/compact. Commands run locally and never reach the model. - Conversations are saved as JSON beside the config file, not in the vault.
- Obsidian CLI integration. When Obsidian's official
CLI is enabled,/obsidianreports its status and
/obsidian open— or "Open this note in Obsidian" in the palette — hands the
current note to the desktop app. Absent, or with the app closed, obsidian-tui
says so and carries on. - Graph keys for fitting (
f), recentring on the selection (c), stepping
between nodes (n/N) and rebuilding the layout (r).
Changed
qquits, and asks first. It works from the explorer, note pane, sidebar
and graph, and the prompt names how many notes have unsaved changes.qstays
a letter wherever you might be typing — the editor, the chat box, a search
field — andCtrl+Qis the way out from there.- The quit and delete confirmations accept
Enteras well asy; anything else
still cancels.
0.1.0 — 2026-07-27
First release.
Notes
- Three-pane Obsidian-style layout: icon ribbon, file explorer, note pane with
tabs, and an outline/backlinks/tags sidebar. - Live-preview Markdown rendering of Obsidian's dialect —
[[wikilinks]]
(dimmed when unresolved),#tags,- [ ]tasks,> [!note]callouts,
tables, frontmatter, and fenced code with syntax highlighting for Rust,
Python, JavaScript/TypeScript, Go and shell. - A text editor with selection, word movement, grouped undo/redo, soft tabs and
Markdown formatting shortcuts. - Backlinks with the source line as context, a document outline, and a tag
browser. - Following a link to a note that doesn't exist creates it. Renaming a note
rewrites every wikilink pointing at it. Deletions go to the vault trash.
Graph
- Force-directed graph view with Barnes-Hut repulsion, so layout is O(n log n)
and stops consuming CPU once it settles. - Unresolved links appear as their own nodes — the notes you meant to write.
- Local graph for the open note, filters for tags, orphans and attachments, and
pan/zoom/select.
Assistant
- A chat panel backed by a native Rust agent runtime: streaming, tool calling,
cancellation and usage accounting. - Fifteen tools over the live vault — search, read, create, append, replace,
link, rename, delete, list tags, inspect links and neighbourhoods, plus UI
actions to open a note or focus the graph. - Tools execute on the UI thread against the same state the user sees, so the
agent's changes appear immediately. - Providers: Anthropic Messages API, and any OpenAI-compatible server
(Ollama, LM Studio, vLLM, OpenRouter) for fully offline use. With no
credentials the panel explains how to configure one instead of failing. --promptanswers a single question on stdout without starting the TUI.allow_writes = falserestricts the assistant to reading and searching.
Interface
- Command palette, quick switcher, global content search, theme picker, vault
switcher, help overlay, and input/confirmation prompts. - Context-sensitive shortcut hint bar.
- Mouse support: clickable ribbon buttons, explorer rows, tabs, sidebar panels
and graph nodes; the scroll wheel targets the pane under the pointer. - Twenty themes including Obsidian's own light and dark, Catppuccin, Tokyo
Night, Gruvbox, Nord, Solarized, Dracula, Rosé Pine, Everforest, and a
terminaltheme that inherits the terminal's palette. User themes can be
added as TOML files that inherit from any built-in.
Vault
- Discovers vaults from Obsidian's own
obsidian.jsonon macOS, Linux and
Windows, and works on any plain folder of Markdown. - Accepts
obsidian://open,newandsearchURIs alongside ordinary flags. - Unreadable vaults are reported clearly, including the macOS privacy
permission that usually causes it.
v0.1.1
Changelog
All notable changes to this project are documented here. The format follows
Keep a Changelog, and versions follow
Semantic Versioning.
0.1.1 — 2026-07-27
Fixed
- Graph nodes render as solid discs. Nodes were drawn by sampling a fixed
17 points regardless of size, which left gaps as soon as a node was more than
a couple of dots across and turned the graph into a field of speckle. They are
now rasterized onto the braille canvas's own dot lattice. Unresolved links and
attachments draw as hollow rings, so the notes you meant to write stand out. - Resetting the graph view frames the graph.
0recentred on the origin,
but the force layout drifts away from it as it settles, so "reset" pushed the
view off the graph entirely. It now fits to the layout's actual bounds. - Labels no longer cover the nodes they name. Every node's full footprint is
reserved before any label is placed. - The graph legend has its own row instead of being painted over the canvas.
- Shortcut hints, the
?overlay and--helplisted keys that didn't exist or
had the wrong case (lrather thanLfor labels). They now match the real
bindings, and tests fail the build if they drift again. - The README claimed Obsidian has no official CLI. It does.
Added
- Slash commands in the assistant panel. Type
/for a completion list;
Tabcompletes,Enterruns. Switch backend with/provider,/modeland
/base-url, check credentials with/loginand/status, keep and reload
conversations with/save,/resumeand/sessions, and free up context
with/compact. Commands run locally and never reach the model. - Conversations are saved as JSON beside the config file, not in the vault.
- Obsidian CLI integration. When Obsidian's official
CLI is enabled,/obsidianreports its status and
/obsidian open— or "Open this note in Obsidian" in the palette — hands the
current note to the desktop app. Absent, or with the app closed, obsidian-tui
says so and carries on. - Graph keys for fitting (
f), recentring on the selection (c), stepping
between nodes (n/N) and rebuilding the layout (r).
Changed
qquits, and asks first. It works from the explorer, note pane, sidebar
and graph, and the prompt names how many notes have unsaved changes.qstays
a letter wherever you might be typing — the editor, the chat box, a search
field — andCtrl+Qis the way out from there.- The quit and delete confirmations accept
Enteras well asy; anything else
still cancels.
0.1.0 — 2026-07-27
First release.
Notes
- Three-pane Obsidian-style layout: icon ribbon, file explorer, note pane with
tabs, and an outline/backlinks/tags sidebar. - Live-preview Markdown rendering of Obsidian's dialect —
[[wikilinks]]
(dimmed when unresolved),#tags,- [ ]tasks,> [!note]callouts,
tables, frontmatter, and fenced code with syntax highlighting for Rust,
Python, JavaScript/TypeScript, Go and shell. - A text editor with selection, word movement, grouped undo/redo, soft tabs and
Markdown formatting shortcuts. - Backlinks with the source line as context, a document outline, and a tag
browser. - Following a link to a note that doesn't exist creates it. Renaming a note
rewrites every wikilink pointing at it. Deletions go to the vault trash.
Graph
- Force-directed graph view with Barnes-Hut repulsion, so layout is O(n log n)
and stops consuming CPU once it settles. - Unresolved links appear as their own nodes — the notes you meant to write.
- Local graph for the open note, filters for tags, orphans and attachments, and
pan/zoom/select.
Assistant
- A chat panel backed by a native Rust agent runtime: streaming, tool calling,
cancellation and usage accounting. - Fifteen tools over the live vault — search, read, create, append, replace,
link, rename, delete, list tags, inspect links and neighbourhoods, plus UI
actions to open a note or focus the graph. - Tools execute on the UI thread against the same state the user sees, so the
agent's changes appear immediately. - Providers: Anthropic Messages API, and any OpenAI-compatible server
(Ollama, LM Studio, vLLM, OpenRouter) for fully offline use. With no
credentials the panel explains how to configure one instead of failing. --promptanswers a single question on stdout without starting the TUI.allow_writes = falserestricts the assistant to reading and searching.
Interface
- Command palette, quick switcher, global content search, theme picker, vault
switcher, help overlay, and input/confirmation prompts. - Context-sensitive shortcut hint bar.
- Mouse support: clickable ribbon buttons, explorer rows, tabs, sidebar panels
and graph nodes; the scroll wheel targets the pane under the pointer. - Twenty themes including Obsidian's own light and dark, Catppuccin, Tokyo
Night, Gruvbox, Nord, Solarized, Dracula, Rosé Pine, Everforest, and a
terminaltheme that inherits the terminal's palette. User themes can be
added as TOML files that inherit from any built-in.
Vault
- Discovers vaults from Obsidian's own
obsidian.jsonon macOS, Linux and
Windows, and works on any plain folder of Markdown. - Accepts
obsidian://open,newandsearchURIs alongside ordinary flags. - Unreadable vaults are reported clearly, including the macOS privacy
permission that usually causes it.
v0.1.0
Changelog
All notable changes to this project are documented here. The format follows
Keep a Changelog, and versions follow
Semantic Versioning.
0.1.0 — 2026-07-27
First release.
Notes
- Three-pane Obsidian-style layout: icon ribbon, file explorer, note pane with
tabs, and an outline/backlinks/tags sidebar. - Live-preview Markdown rendering of Obsidian's dialect —
[[wikilinks]]
(dimmed when unresolved),#tags,- [ ]tasks,> [!note]callouts,
tables, frontmatter, and fenced code with syntax highlighting for Rust,
Python, JavaScript/TypeScript, Go and shell. - A text editor with selection, word movement, grouped undo/redo, soft tabs and
Markdown formatting shortcuts. - Backlinks with the source line as context, a document outline, and a tag
browser. - Following a link to a note that doesn't exist creates it. Renaming a note
rewrites every wikilink pointing at it. Deletions go to the vault trash.
Graph
- Force-directed graph view with Barnes-Hut repulsion, so layout is O(n log n)
and stops consuming CPU once it settles. - Unresolved links appear as their own nodes — the notes you meant to write.
- Local graph for the open note, filters for tags, orphans and attachments, and
pan/zoom/select.
Assistant
- A chat panel backed by a native Rust agent runtime: streaming, tool calling,
cancellation and usage accounting. - Fifteen tools over the live vault — search, read, create, append, replace,
link, rename, delete, list tags, inspect links and neighbourhoods, plus UI
actions to open a note or focus the graph. - Tools execute on the UI thread against the same state the user sees, so the
agent's changes appear immediately. - Providers: Anthropic Messages API, and any OpenAI-compatible server
(Ollama, LM Studio, vLLM, OpenRouter) for fully offline use. With no
credentials the panel explains how to configure one instead of failing. --promptanswers a single question on stdout without starting the TUI.allow_writes = falserestricts the assistant to reading and searching.
Interface
- Command palette, quick switcher, global content search, theme picker, vault
switcher, help overlay, and input/confirmation prompts. - Context-sensitive shortcut hint bar.
- Mouse support: clickable ribbon buttons, explorer rows, tabs, sidebar panels
and graph nodes; the scroll wheel targets the pane under the pointer. - Twenty themes including Obsidian's own light and dark, Catppuccin, Tokyo
Night, Gruvbox, Nord, Solarized, Dracula, Rosé Pine, Everforest, and a
terminaltheme that inherits the terminal's palette. User themes can be
added as TOML files that inherit from any built-in.
Vault
- Discovers vaults from Obsidian's own
obsidian.jsonon macOS, Linux and
Windows, and works on any plain folder of Markdown. - Accepts
obsidian://open,newandsearchURIs alongside ordinary flags. - Unreadable vaults are reported clearly, including the macOS privacy
permission that usually causes it.