Changelog
0.12.4 (Apr, 09 2026)
Added
- c3f2b41 Add
Symbolsconfig types with presets by @erikjuhani
Introduces symbols and symbol presets as the foundation for configurable UI symbols. Each preset provides a complete set of defaults for all visual glyphs used across the interface. Users pick a preset in
[symbols]and optionally override individual fields on top of it.Also added derives
PartialEqandserde::DeserializeonFontStyleso heading and title font styles can be configured from toml.Related to #424
- 7d53d0a Surface config errors as warning toasts by @erikjuhani
Previously config parsing errors were silently swallowed. Now we return warnings alongside the config so they can be shown to the user. Invalid config files produce an
InvalidConfigerror with the TOML parser's message. TheUserConfigNotFoundcase is still silently ignored since most users won't have a config file.Additionally toasts now support word-wrapped multi-line messages via
textwrapand compute their height dynamically instead of using a fixed constant. This lets longer error messages display fully rather than getting truncated. Toast stacking inrender_toastsaccounts for variable heights.Signed-off-by: Erik Kinnunen erik.kinn@gmail.com
- 7d3e968 Add auto-detection for symbol preset based on terminal capabilities by @erikjuhani
Extend Preset with an Auto variant that detects terminal capabilities at startup by inspecting TERM, LC_ALL, LC_CTYPE and LANG environment variables to choose between Unicode and Ascii presets.
Signed-off-by: Erik Kinnunen erik.kinn@gmail.com
Changed
- a67ebed Add
rust-versionto basalt Cargo manifest by @erikjuhani
Setting
rust-versionlets Cargo emit a clear error when someone tries to build with a toolchain older than 1.91.0 and enables version-aware dependency resolver behavior.Signed-off-by: Erik Kinnunen erik.kinn@gmail.com
- 9cdaa1c Use marker width instead of prefix width when rendering by @erikjuhani
Related to #424
Signed-off-by: Erik Kinnunen erik.kinn@gmail.com
- 7cb70c5 Use symbols through note editor rendering by @erikjuhani
All render functions now take
&Symbolsparameter so application symbols are read from config instead of being hardcoded.List markers cycle through the configured list based on nesting depth.
VirtualDocumentstores aSymbolsinstance and uses it for the title font style and horizontal rule.Additionally add support for cycling through list markers so different depth levels of list indentation have different markers. Can define n-amount of symbols.
Related to #424
Signed-off-by: Erik Kinnunen erik.kinn@gmail.com
- e6d8edf Use symbols in explorer by @erikjuhani
Replaces hardcoded symbols in the explorer with configurable ones. Unselected files also render
symbols.unselectedinstead of blank space so the collapsed view is consistent across presets.Added more emphasis on selected note with bold and underline styles.
Related to #424
Signed-off-by: Erik Kinnunen erik.kinn@gmail.com
- de90cfd Use symbols config in Outline by @erikjuhani
Replaces hardcoded glyphs with configurable symbols.
Related to #424
Signed-off-by: Erik Kinnunen erik.kinn@gmail.com
- dd49a8a Use symbols in app and set default preset by @erikjuhani
Config is now loaded once in
App::startand passed intoApp::newso thatconfig.symbolsis available when constructing component state. The default config setspreset = "unicode"inconfig.toml.Related to #424
Signed-off-by: Erik Kinnunen erik.kinn@gmail.com
- b4e22bf Use border style from symbols config by @erikjuhani
Previously border style was hardcoded in code, however, this is now also controllable from the symbols map.
Signed-off-by: Erik Kinnunen erik.kinn@gmail.com
- 1801eea Add configurable toast icons per symbol preset by @erikjuhani
Toast icons were hardcoded. This commit adds toast_success, toast_info, toast_error and toast_warning fields to the symbol config so each preset can define appropriate icons. ASCII uses simple text characters, Unicode keeps the existing icons, and NerdFont uses its own glyph variants. The icon is resolved at render time from the active symbols config.
Signed-off-by: Erik Kinnunen erik.kinn@gmail.com
0.12.3 (Mar, 10 2026)
Added
- eb66637 Add support for sequential keys by @erikjuhani
This change adds support for a sequence (or chord) of keys like 'gg' or 'bn' or 'gth'.
The keys maps are separated into Single keys (which can contain modifiers) or a 'sequence' of keys called Chord.
Additionally added support for uppercased chars in key bindings, which means that users can now use shift + char to run commands.
Organized and structured the key code parsing, so it's more readable and the 'special' cases are clearly represented.
- 50d3b96 Add sequence-aware key dispatch to app by @erikjuhani
This commit adds key sequence handling to basalt, which means that users can create key bindings with a key sequence like
ggorciw.
- Replace single-key lookup with
pending_keys: Vec<Keystroke>onAppState,handle_eventandhandle_key_eventtake&mut AppStateto drive accumulation and prefix/exact matching- Add
ConfigSection::sequence_to_messageandis_sequence_prefix- Remove old
key_to_message/handle_active_component_event, editing bypass is now inlined inhandle_key_eventhandle_editing_eventin input and note_editor now takeKeyEventby value for consistencyRelated to #400 Related to #212
- 2cc478d Add scroll-to-top and scroll-to-bottom commands by @erikjuhani
Adds ScrollToTop/ScrollToBottom to the note editor and explorer message enums, wired through new command variants: note_editor_scroll_to_top, note_editor_scroll_to_bottom, explorer_scroll_to_top, explorer_scroll_to_bottom.
Note editor ScrollToBottom uses cursor_jump to the last block rather than cursor_down(usize::MAX), which silently does nothing because saturating_add clamps to usize::MAX and skip(lines.len()) exhausts the iterator.
- 0a1dfc9 Add vim_mode config flag and vim.toml preset by @erikjuhani
When vim_mode = true in the user config, a vim.toml preset is merged between the base config and the user config, so users can still override individual bindings. The preset adds:
- ctrl+f / ctrl+b for half-page scrolling in note editor, explorer, and help modal (replacing ctrl+d / ctrl+u)
- gg / G for jump to top/bottom in note editor, explorer, and outline
- ae58ea4 Add vim Normal/Insert sub-modes to note editor by @erikjuhani
Add Normal/Insert sub-modes within EDIT mode.
ienters Insert mode,Escreturns to Normal for hjkl navigation,Escagain exits to READ.Fix block navigation in edit mode: track
editing_blockexplicitly to prevent layout oscillation, commit text_buffer before switching blocks, fixmodified()after block switch, and use AST source ranges for correct cursor positioning when entering code blocks.
- 687ab46 Add create untitled note and folder commands
Bind
nto create a new untitled note andNto create a new untitled folder in the explorer. Both commands select the newly created item in the explorer after creation.
Changed
- 0c9883c Replace default keybindings with vim preset when vim_mode is enabled by @erikjuhani
Instead of merging vim.toml bindings on top of the defaults, vim mode now replaces the entire key_bindings set for each section it defines. This ensures vim-style bindings like
ggandGare the only way to scroll to top/bottom, without leftover default bindings like ctrl+shift+up/down.
- 697b856 Normalize lowercase+SHIFT keystrokes to uppercase by @erikjuhani
Some terminals send 'g'+SHIFT instead of 'G'+SHIFT. Normalize this in
Keystroke::fromso key bindings match regardless of terminal behavior. Also fixFrom<&KeyEvent>to go through the normalization path.
Fixed
-
90528cb Fix integer overflow in explorer by @erikjuhani
-
b98cdf5 Fix vim_mode config flag by @erikjuhani
Fix inverted vim_mode condition that loaded vim config when disabled...
Add w/b word motion bindings for input modal in vim.toml and rename VIM_CONFIG_STR to VIM_CONFIGURATION_STR for consistency
- cda1b25 Fix cursor movement skipping blocks with no accessible content lines by @erikjuhani
When moving up/down, if the target line has no content (e.g. a synthetic line in a visual code block), search in the opposite direction as a fallback. This fixes ScrollToTop not working when the first element is a code block, and ScrollToBottom not reaching the last line.
- ef9f76d Fix first line not rendering in edit mode and vim save
When entering edit mode before layout,
enter_insertcouldn't find blocks and created a spurious empty paragraph node, hiding the real first line. Guard the empty-node fallback to only fire for genuinely empty files.Also commit the text buffer when exiting vim insert mode so that save writes the updated content.
- fea931b Fix Explorer lexicographical order in item sorting
When you had items with number suffixes like 1, 2, 3, 100, the standard rust comparison and ordering would not work as expected, as users are usually expecting natural ordering. What happened was that we received: "item 1, item 10, item 2" instead of "item 1, item 2, item 10".
Added natord crate for natural sort ordering, and now explorer sorts the items in expected order "item 1, item 2, and item 10".
- e80e4e2 Fix faulty app state after renaming
In some conditions after rename the state would be left in "limbo" and "frozen" state, which meant that users could not navigate normally anymore. This happened because we didn't correctly change to Explorer pane after exiting from rename input.
- d7fb1c5 Fix stale rendering after exiting vim insert mode
When using vim mode with NORMAL and INSERT modes, when creating writing new nodes for example a heading and then exiting would result in visual bug that appeared as if the current node merged with the next one, e.g. a paragraph after our newly created heading looked liked it was merged into it.
Fixed the visual bug by accessing the ast_nodes directly, since virtual_document in this case would have "stale" data.
0.12.2 (Feb, 21 2026)
Added
- 57017bb Add toast notification system
Introduces toast module with level-based notifications (info, warn, error, success) that auto-expire via a 250ms tick loop. Toasts render in the top-right corner with colored borders and icons per level.
Includes snapshot tests for all toast variants and unit tests for expiry behavior.
Related to #83
- 7bd859b Add
Batchmessage variant for sequential message dispatch
Enables multiple app messages to be dispatched and fully processed in sequence, which is needed to combine actions like saving a note and showing a toast notification in a single update cycle.
Related to #83
- 99b79b1 Add toast notifications for file save feedback
Show a success toast when a modified file is saved and an error toast when saving fails, replacing the previous silent no-op error handling.
Fixes #83
Fixed
- 5f93090 Fix viewport not scrolling with cursor in edit mode by @erikjuhani
Call
ensure_cursor_visibleafter cursor movement and text editing operations (insert_char,delete_char,cursor_left,cursor_right,cursor_word_forward,cursor_word_backward) so the viewport follows the cursor. These calls were already present incursor_upandcursor_downbut missing from the other methods.Fixes #316
- 5fd1359 Fix input modal position when viewport is scrolled by @erikjuhani
Account for the list scroll offset when calculating the input modal y-position in
ToggleInputRename. Previously, the position used the absolute selected index, which placed the modal outside the visible area when the list was scrolled.
- a4ed28f Disable smart punctuation by @erikjuhani
Smart punctuation transforms characters like ' and " to curly variants like β and β. The latter variants have different byte lengths. This had an effect that made the source offset not match with the rendered offset causing issues like inability to move the cursor downwards if the current paragraph contained characters that were transformed into 'smart' variants due to overlapping offsets it the length difference caused.
The fix was to disable the smart punctuation, and come back to it at a later date and do the change holistically. This needs most likely some architectural change to allow smart punctuation to work. The smart punctuation should be treated as virtual variant that only has an effect in the rendering part of the text.
This fixes: #371
- 2f83c49 Fix editing when file is empty by @erikjuhani
When opening an empty file and entering edit mode, no text or cursor was visible until pressing ESC. This was caused by three issues:
- No AST nodes existed for empty files, so layout produced no virtual lines and typed text was invisible
- Cursor rendering was gated on non-empty content, which is only updated on exit from edit mode
render_rawproduced no content lines for empty content, leaving the cursor with no valid positionFix by creating a placeholder paragraph node when entering insert mode on an empty document, allowing cursor rendering in edit mode, and producing a content line in
render_rawfor the empty content case.
0.12.1 (Jan, 26 2026)
Changed
- 3f81196 Swap the sort symbol to a more common one by @erikjuhani
The previous π tetragram for centre symbol had multiple issues between different terminal emulators and recently the update of unicode-width that changed classification on some symbols making the width differ, from the previous version.
Without this change I cannot update to newer version of unicode-width.
- 9716042 Remove ~beta suffix from version string
Obsolete feature as the 0. major version should tell enough about the instability of this application.
Fixed
- 001fe3e Fix cursor movement for multi-byte unicode characters by @erikjuhani
The cursor now correctly handles multi-byte characters (emojis, unicode symbols) when moving left/right and when calculating visual positions. Previously, the editor assumed 1 byte per character, causing the cursor to land in the middle of multi-byte sequences.
Key changes:
- Use byte lengths instead of character counts for source range tracking
- Convert between byte offsets and character boundaries properly
- Update
insert_charanddelete_charto account for variable byte widths- Fix
source_offset_to_virtual_columnto use byte indices instead of char indicesFixes #314
- 225184b Fix cursor movement through empty lines in code blocks by @erikjuhani
The cursor could not move upwards past empty lines in code blocks in both edit and read modes.
In edit mode,
virtual_position_to_source_offsetincorrectly returnedsource_range.endfor empty lines becausecur_colincluded synthetic span widths. Addedcontent_colto track only content character widths.In read mode, the Visual rendering of code blocks didn't account for newlines when calculating source ranges, causing empty lines to have empty ranges (e.g., 5..5). Now uses
line_range()which properly adds 1 for newlines.Fixes #321
- 3f31151 Update all wiki-links in notes after rename
Now all wiki-links will be updated after renaming a note in basalt. We call
update_wiki_linksin the RefreshVault message handler to automatically update links across the vault when a note is renamed.Also refreshed the note editor content after rename to reflect any wiki-link changes in the currently open note, otherwise the content would not be refreshed properly.
Fixes #307
0.12.0 (Jan, 18 2026)
Added
- 342fbd6 Add input modal with note and directory rename functionality by @erikjuhani
[!CAUTION] BEWARE! This rename implementation does not cover updating the wiki-links. If you use the rename on notes that are referenced as wiki-linksβthese links will be broken after and needs to be manually corrected.
Add an input modal that provides dynamic modal text editing. The modal features a text input widget and cursor navigation, which supports character-by-character and word-based movement. The vim-like modes are limited. Only aforementioned movement and text editing.
The modal is integrated with the explorer pane, and is available by pressing 'r' (default key binding) on the selected item. The rename operation leverages the
rename_noteandrename_dirfunctions added in affec53 and a347325, the vault is 'reloaded' after rename.
Dependencies
- 0b7e9ab Update Rust crate ratatui to 0.30.0 by @renovate-updater[bot]
datasource package from to crate ratatui 0.29.0 0.30.0
Fixed
- 5f9d342 Fix sorting to match Obsidian sorting
Fixes #69. Previously, when user sorted the vault items the folders would also be sorted according to the same rules as notes, however, this is not how obsidian sorts. Obsidian sorts only files by default, not directories. Directories are initially sorted A-z, and then kept in that order when sorting files.
0.11.2 (Dec, 21 2025)
Changed
- 88ec357 Update basalt-core version to 0.7.0
Use direct definitions in the respective crates instead of using workspace dependencies for basalt-core and basalt-widgets.
Fixed
- 8744562 Fix nested task list rendering to properly indent subtasks by @erikjuhani
The parser now correctly nests subtasks within their parent task nodes rather than treating them as siblings. The task_kind field changed from Option to Vec to track nested task states, similar to item_kind.
Nested task lists are now properly rendered following the same implementation as in the list items code.
- 5b54928 Fixes 'sticky' symbols when switching between read and edit by @erikjuhani
The sticky key effect was visible for example with task lists when tasks were intended with tabs in the source. These tab characters would never replace the existing symbols from the buffer. The sticky symbols issue was fixed by replacing the tab characters with two spaces.
0.11.1 (Dec, 08 2025)
Fixed
- 63b5e9f Use basalt-core 0.6.3 version in basalt
basalt-core 0.6.3 fixes vault json deserializer for
tsfield and sets it as optional. It was previously set as required. If thetsfield was missing it would crash basalt.
0.11.0 (Nov, 30 2025)
Basalt author and maintainer here! Wanted to write a few words before the regular changelog.
Phew, this took longer than expected, but here we are. The editor feature does not offer feature parity with the tui-textarea that was being used previously, however, it can properly wrap the text while writing, which frankly, I find quite pleasing.
If you encounter any bugs or additional strangeness please open an issue! The editor was made by me and most likely contains errors. Use with caution! :)
I'm taking a small break from basalt for the advent of code puzzles! So expect slower development during December.
This release fixes the following issues: #105, #104, #95
Demo:

Added
- ba8f3a0 Introduce virtual document structure with rendering by @erikjuhani
I decided to implement a virtual document, which is essentially the virtually rendered version of the markdown document. This virtual document is a collection of virtual blocks, virtual lines and virtual spans.
Virtual lines and spans are turned into Ratatui variants to render them in terminal.
Virtual spans are separated into two concepts, synthetic and content.
- Synthetic spans are elements that are not calculated as part of the markdown source.
- Content spans on the other hand are elements that are calculated as part of the markdown source.
This separation enables more fluid use cases and easier management codewise for more rendered content, like text wrapping symbols, additional emphasize lines or spans, etc.
Rendering is a collection of functions that are turned into virtual blocks. These virtual blocks map directly into top level markdown nodes.
All rendered functions wrap the text with the given max width. The text wrapping is a generalized wrapping function that can be now run for "any" markdown node that is defined in the ast module.
- 943256b Add Cursor module by @erikjuhani
Cursor module is responsible for keeping up with the cursor state. Cursor can be switched between two modes, read and edit.
Each mode behaves a bit differently, read only considers the virtual elements, and edit mode considers the source content.
For now only the read mode variant is properly implemented.
The rendering is handled with a separate stateful CursorWidget component that takes the cursor state as an input and draws the cursor accordingly.
In read mode the cursor is drawn as a full-width line cursor.
- b26f0ff Add soft break parsing to markdown parser
Also add empty_line() helper function to TextSegment struct. This creates a new empty line with "\n" as the content.
This empty line can then be split in the render functions, but still keep the content inside a single markdown node (e.g. paragraph).
- 5222b7e Add text wrapping helper utility
The text wrapper module exposes
wrap_preserve_trailing, which, as name implies, keeps the trailing whitespace.I'm using the
textwrap::WordSeparatorto find and iterate over the words in the text, and then determine if the word fits in the current line by using the passed max width variable. Thetextwrapcrate itself did not ship with a premade wrapping utility that would have preserved the whitespace, at least, I did not find such utility.
- bf2c86d Add a simple viewport abstraction
The viewport abstraction wraps the ratatui layout structure Rect and uses additional layout data structures like Size and Offset.
- 5b83d5f Add
charsandchar_indicesmethods to virtual span
These helper methods will allow easier access to the underlying chars and their byte indices.
Changed
- ee4976f Replace old editor with new implementation by @erikjuhani
The old note editor variant was hard to maintain and was lacking proper structure. Adding new ast nodes or elements was a cumbersome process.
The new variant uses logical structures like virtual document and separate rendering functions to achieve a more cohesive end result.
The editor now requires to have a viewport in order to render anything properly. This is a requirement for example to decide the correct wrapping width for text elements.
This change also introduces fix for scrolling. Now scrolling works properly and cursor is always visible in the viewport. This fixes the issue #104.
The rendering is simplified drastically due to the use of more logical structures.
In this commit, only read mode is enabled and the edit mode support is missing.
- 88be5fa Return a reference instead of owned RichText
No reason to not return a reference, and we avoid allocation.
- 739704a In virtual span width() returns both content and synthetic width
Previously only content width was taken into account, however, this does not work as intended as the synthetic width needs to be calculated as well to find for example the correct offset for cursor column.
- fa59ed5 Remove unused methods from virtual line
Also simplified and improved the existing methods. For example virtual spans now retuns a slice instead of owned Vec.
- d00f9d4 Implement custom text editor
This custom implementation replaces tui-textarea with proper text wrapping and better WYSIWYG experience. Also the custom implementation allows for more granular control over how elements are, rendered and positioned.
I tried to mimic the previous functionality in a way so as little is lost as possible feature wise, obviously, since this is made from scratch the feature parity is far off still.
Additionally there is some known issues, like cursor positioning is incorrect with unicode symbols in source content. This can be observed by wrong end position when moving the cursor to the right most end. The cursor appears as if it is stuck, but that is due to wrong count somewhere, which should be fixed, but in a different commit.
For now the implementation is very limited and implements only a restricted set of features: Editing markdown nodes, saving changes to file, moving by words and scrolling by half pages.
This commit has quite many changes, and, unfortunately the nature of how this refactor was introduced, was difficult to separate the commits atomically and cleanly to smaller pieces.
But the most notable ones are:
Cursor changes, which include the cursor movement by columns and words and proper cursor positioning from the source offset location.
Editor state changes to allow insertion, deletion and saving of files, and source range shifting, which is related to the editor functionality, which essentially shifts the end of the source range, if the source ranges are not shifted properly and if the text buffer exceeds the range start of next node the text buffer would be replicated and rendered in-place of the next node.
Render changes, which introduce a new consolidated text wrapping and handling for newline characters in both visual and raw rendering modes. The new consolidated text wrapping uses the new whitespace preserving text wrap function. Additionally source offset for rendered virtual lines were fixed. Also added unicode-width dependency for accurate unicode character width calculations in render.
Dependencies
- 2985a13 Update Rust crate etcetera to 0.11.0 by @renovate-updater[bot]
datasource package from to crate etcetera 0.10.0 0.11.0
New Contributors
- @erikjuhani made their first contribution in #191
- @renovate-updater[bot] made their first contribution
- @istudyatuni made their first contribution
Full Changelog: https://github.com/erikjuhani/basalt/compare/basalt/v0.10.4...basalt/0.11.0
0.11.0 (Unreleased)
This release adds new improved note editor with proper text wrapping for all markdown elements (excluding code blocks).
The new improved and refactored editor code should enable faster feature creation.
Added
Changed
0.10.4 (Oct, 6 2025)
This release adds note name support in the editor pane. Follows a similar approach as in the Obsidian app itself. The note name can not be changed yet in basalt.
Additionally contains some fixes and slight changes to headings to make them work better with the new note name implementation.
Added
Fixed
Changed
- Change markdown heading level 1 and 2 to more subtle
- Only text is crossed over for "hard checked" tasks
0.10.3 (Sep, 15 2025)
This release adds the support to easily hide and expand the explorer pane (file tree). Expanding and hiding is done with h, l and arrow left, and arrow right.
When explorer is expanded a βΉ symbol is shown for clarity of the current state.
Added
- Support expandable explorer commands in Explorer widget
- Add hide_pane and expand_pane explorer commands
Fixed
0.10.2 (Sep, 13 2025)
Deprecated the following config commands:
- "note_editor_experimental_set_edit_mode"
- "note_editor_experimental_set_read_mode"
- "note_editor_experimental_exit_mode"
Use these instead:
- "note_editor_experimental_set_edit_view"
- "note_editor_experimental_set_read_view" and
- "note_editor_experimental_exit"
Changed
0.10.1 (2025-08-31)
Added
Changed
0.10.0 (2025-08-21)
Added
Changed
0.9.0 (2025-07-30)
Added
0.8.0 (2025-06-25)
Added
- Add user configuration file support for customizable key bindings
- Adds a 'config' field to the AppState, which is based on a toml file (#25)
Fixed
0.7.0 (2025-06-15)
Changed
Added
- Add visiblity and visiblity helper methods to HelpModal
- Add visibility and helper methods to VaultSelectorModal
- Add active field to MarkdownView to indicate active state
0.6.1 (2025-06-07)
Fixed
0.6.0 (2025-06-01)
Added
Fixed
0.5.0 (2025-05-25)
Fixed
- Support deeper block quotes with proper prefix recursion
- Add two space indentation to list items
- Fix code block rendering
Changed
0.4.1 (2025-05-25)
Changed
0.4.0 (2025-05-25)
Fixed
- Update basalt-core to version 0.5.0, which potentially fixes #44
Check basalt-core CHANGELOG here.
0.3.7 (2025-05-22)
Added
Fixed
0.3.6 (2025-05-21)
Fixed
0.3.5
Fixed
0.3.4
Added
- Refactor Markdown event parser (#28)
- Add support for
LooselyCheckedtask kind (#29) - Add support for ordered lists
- Add text wrapping for paragraphs