All notable changes to Ferrite will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Work in progress on master; not yet tagged.
- CSV rendered cell editing (MVP) — Double-click to edit cells in the CSV/TSV Rendered view for small files; RFC 4180 serialization, undo integration, keyboard commit/cancel. Large lazy-parsed files still prompt to edit in Raw view. See
docs/technical/viewers/csv-viewer.md. - Video embed parsing — Detect
{{video URL}}and bare YouTube URLs in their own paragraph;VideoEmbedAST node with allowlist and round-tripsource_text. Playback rendering (wry / thumbnail fallback) is follow-up work. Seedocs/technical/markdown/video-embed-parsing.md.
- Windows single-instance foreground (#147) — Opening a file from Explorer into an already-running Ferrite window now raises the existing window more reliably. The secondary process reads
instance.pidand callsAllowSetForegroundWindow(primary_pid)before forwarding paths; the primary also sendsViewportCommand::RequestUserAttention(Informational)when focus alone is insufficient. Thanks @Star-sumi (PR #148). Seedocs/technical/platform/single-instance.md.
0.3.0 - 2026-05-22
Platform refresh: export, code run, Mermaid first wave, rendered edit session (WYSIWYG block switching), accent, quick-note workflow, Phosphor icons — on eframe / egui 0.34.2 with Rust 1.92 MSRV. See docs/technical/platform/eframe-egui-034-upgrade.md and the v0.3.0 regression matrix (includes 0.34 delta, Task 89; rendered editing RS-1…RS-7, Tasks 94–105).
- eframe / egui stack upgraded to 0.34.2 — Platform bump (0.28 → 0.31 → 0.34 for v0.3.0). skrifa + vello_cpu default text backend; Phosphor 0.12; Windows glow renderer retained. Migration: viewport rects (
screen_rect→viewport_rect/content_rect), Popup API for menus/dropdowns, Tooltip API, ScrollArea edge-fade disabled globally for visual parity. Seedocs/technical/platform/eframe-egui-034-upgrade.md. - HarfRust validation under egui 0.34 — Complex-script cursor/selection via cluster shaping;
shape_line_clusters,validate_cluster_byte_ranges, 32 shaping unit tests. Word wrap + complex script still uses egui galley only (documented limit). - Mutex / deadlock audit — Cross-thread paths verified (single-instance, terminal, code-run workers, global caches); no
egui::Mutexin Ferrite code. - MSRV raised to Rust 1.92 —
rust-toolchain.toml,package.rust-version, CI workflows read the pinned toolchain. - v0.3.0 regression matrix updated — 0.34-specific delta checks added to
docs/technical/platform/v0.3.0-regression-matrix.md; Windows manual pass recorded (89.3–89.7).
- eframe / egui stack upgraded to 0.31.x (Task 57) — Dependency bump from 0.28 with API migrations across themes, fonts, editor, markdown, terminal, and UI. See
docs/technical/platform/eframe-egui-031-upgrade.md. Intended to address platform input/windowing issues tied to older winit (e.g. #106, #111); confirm on target OS builds before closing. - Cross-platform regression matrix for the egui 0.31 upgrade (Task 58) —
docs/technical/platform/v0.3.0-regression-matrix.mddefines stable test IDs (LAU/FILE/KBD/IME/TRM/DLG/WIN/MMD/FNT/THM) covering every egui 0.31 risk surface across Win11 / macOS-AS / macOS-Intel / Linux-X11 / Linux-Wayland with explicit release-gate criteria. Executed on Win10 as a Win11 proxy; macOS and Linux rows deferred to CI / external contributors until filled in.
- Native PDF export (Tasks 60–61) — File → Export → PDF… with options for page size, margins, optional page break before H1, and link annotations. Implemented with krilla + krilla-svg (see
docs/technical/planning/pdf-export-pipeline.md,docs/technical/viewers/pdf-export.md). - Print preview (Task 62) — Reuses the same PDF render path as export; writes a temp PDF and opens it in the in-app PdfViewer tab (
docs/technical/viewers/print-preview.md). - Themed HTML export (Task 63) — Stronger parity with in-app rendering: theme-aware CSS, Mermaid as inline SVG, syntect-highlighted code blocks, export options dialog (
docs/technical/viewers/themed-html-export.md,docs/technical/viewers/document-export.md).
- Code execution settings (Task 64) — Editor → Code execution: master enable, shell/Python toggles, timeout (default 30s, max 300s). See
docs/technical/config/code-execution-settings.md. - Run on fenced blocks (Tasks 65–66) — Run control in rendered/split preview for supported languages; background worker; inline ANSI-colored output (
ansi_render/ vte-style SGR); exit status; insert output as fenced block. Seedocs/technical/markdown/code-block-run.md. - Timeout and Stop (Task 67) — Hard timeout and user Stop; clear labels for timeout vs cancel (
docs/technical/markdown/code-block-cancellation.md). - First-run consent (Task 68) — Modal before first Run; queue payload until user accepts; can be skipped via Settings (
docs/technical/markdown/code-execution-consent-dialog.md).
Mermaid — first wave (#4)
- Insert → Mermaid… (Tasks 69–70) — Toolbar templates per diagram type; About/Help syntax section and snippets aligned with insert (
docs/technical/markdown/mermaid-insert-toolbar.md,docs/technical/mermaid/mermaid-syntax-help.md). - Inline validation (Task 71) — Parse-time errors: warning header in preview, last-good diagram fallback, squiggles in raw editor (
docs/technical/mermaid/mermaid-inline-validation.md). - Flowchart shapes & style (Task 72) — Extra shapes and
style/ classDef plumbing (docs/technical/mermaid/flowchart-shapes-and-style.md). - State diagrams: fork/join & history (Task 73) —
<<fork>>/<<join>>bars and[H]/[H*]glyphs (docs/technical/mermaid/state-pseudostates-fork-join-history.md).
Mermaid — flowchart edge routing & layout polish (#83, FC-83a)
The native flowchart renderer (no mmdr / SVG fallback) gained several rendering passes that close the bulk of the FC-83a regression versus Mermaid Live. See docs/technical/mermaid/flowchart-edge-obstacle-routing.md and docs/technical/mermaid/flowchart-layout-algorithm.md.
- Edge–node obstacle avoidance — Forward edges that would otherwise pass through an unrelated node now detour around the obstacle. New
flowchart/utils.rshelpers (segment_intersects_rect,path_intersects_any,bezier_intersects_any,collect_node_obstacles,union_rect_bounds) feed a multi-strategy router inflowchart/render/edges.rs(route_forward_edge→try_orthogonal_route→route_via_side_corridor). Unit-tested inobstacle_tests. - Painter sizing from actual content —
render_flowchartnow allocates from real node/subgraph bounds vialayout_content_sizeand adds horizontal padding on the side(s) where back-edges actually loop (back_edge_horizontal_padding;BACK_EDGE_LOOP_MARGIN + NODE_OBSTACLE_PADDING + (lanes-1)·BACK_EDGE_LANE_SPACING), so feedback loops are not clipped without leaving a spurious left gutter when all loops run on the right (FC-83a). - TD/BT layer centering — Sugiyama cross-axis placement now centers each layer within the widest layer (
max_cross_size), matching LR/RL behaviour, instead of centering in the full containeravailable_width(which left a large empty strip on the left when the painter only allocated actual node bounds). Post-layout bounds are normalized so content starts at the layout margin. - Back-edge side channels — Replaced the fixed ±40 px cubic-bezier loops with stable orthogonal routes that hug the graph bounding box at
BACK_EDGE_LOOP_MARGIN = 24 px. Loop side is picked from source position; horizontal exits detour above/below same-row nodes when needed. - Parallel back-edge lanes —
compute_back_edge_lanesassigns distinctBackEdgeLane { side_sign, lane_index }slots so multiple loops targeting the same node on the same side stay visually separate (FC-83a:E → BandF → Bno longer merge onto one bus). Lane spacingBACK_EDGE_LANE_SPACING = 36 px. - Inner back-edge direct path — Lane-0 back-edges (
try_inner_back_edge_direct_path+inner_back_edge_path_candidates+try_inner_back_edge_tight_loop) exit the source at the top-outer corner, rise vertically along the source's outer edge, and enter the target at its side-centre — instead of going horizontal-first across the trunk. Stepped-outward variants clear sibling branches; tight-loop fallback stays just outside the source. Pinned byfc_83a_inner_e_to_b_goes_up_first. - Branch parent snap (alone-on-layer) —
align_branch_nodes_to_childreninflowchart/layout/sugiyama.rsshifts decision nodes with 2+ forward children onto the cross-axis barycenter of those children, matching dagre/Mermaid.js behaviour. The shift is gated to nodes that are alone on their layer so it cannot pull a branch parent onto a sibling (regression-tested via FC-83adecide). - Same-layer sibling spacing safety net — New
resolve_layer_overlapspass walks every layer in cross-axis order and enforcesnode_spacing.x(or.yfor LR/RL) between adjacent siblings via iterative half-and-half push. Fixes the coffee-machine repro intest_md/test_flowcharts.md(previouslyCoverlappedHby ~68 px andDoverlappedGby ~79 px at 800 px width) and protects against future regressions from subgraph clustering or custom node sizing. Pinned bytest_layout_coffee_machine_all_nodes(per-layer no-overlap assertion).
- User-configurable Ferrite accent — Color picker in Settings → Appearance and Welcome; drives headings, selection, tabs, view mode segment, productivity hub chrome, status bar (LSP line, git branch); markdown links keep the standard link color. See
docs/technical/ui/theme-system.md. - Productivity Hub polish — Card layout, Pomodoro emphasis, floating × re-docks to sidebar (does not hide the hub), stable docked widths via clipped child UI, detached window respects user resize without content-driven growth or a fixed 560px width floor, scrollbar margin vs resize handle (
docs/technical/productivity/productivity-panel.md).
- macOS Gatekeeper troubleshooting (#130) — Unsigned CI
.appon macOS 15.x: docs atdocs/install/macos.md, index + release checklist links; quarantine / Open Anyway workarounds documented. - Custom font picker stability (Intel macOS) (#133) — Deferred font load until explicit combo selection to avoid spurious error toast (
docs/technical/fonts/custom-font-picker-deferred-load.md).
- Quick note workflow (on by default, Settings → Files) — Pathless “scratch” tabs no longer block quit when modified; closing a modified untitled tab still prompts (Save / Don't save / Cancel); empty untitled tabs close silently; double-click an untitled tab to set a display name. Turn off in settings to restore classic save prompts for pathless tabs. Unsaved text persists via session recovery when Restore previous session on startup is enabled. Clean exit keeps
recovery/content (only the crash snapshot file is cleared). Seedocs/technical/config/quick-note-workflow.md. - Workspace file index — Quick File Switcher (Ctrl+P) and Search in Files (Ctrl+Shift+F) now search the entire workspace on a background thread, not only folders expanded in the sidebar. Incremental file batches and an animated progress bar (“Indexing… N files found”) on large trees; index rebuilds when files are created, deleted, or renamed. Same walk rules as the file tree (
node_modules,.git, etc.). Seedocs/technical/files/workspace-file-index.md.
- Split-view live sync — In markdown Split, optional alignment between the Ferrite raw editor and rendered preview while scrolling. Uses source line + fraction anchors (not scroll percentage) so code blocks and Mermaid stay aligned across different preview heights. After ~120 ms idle, one snap updates the other pane; top/bottom (within 5 px) snap to document edges. Detects wheel, scrollbar drag, and keyboard via offset delta. Controls on the semantic minimap footer: Sync (master, persists to settings) and 2-way (default on: preview scroll also moves raw; off = raw → preview only). Settings:
sync_scroll_enabled(default off),sync_scroll_bidirectional. Seedocs/technical/sync-scrolling.md. - Mode-toggle sync (Ctrl+E) — With sync enabled, switching Raw ↔ Rendered preserves position via the same hybrid top/bottom/line-mapping strategy (interpolation within large blocks). Split mode continues to use real-time sync instead of toggle-time conversion.
- Rendered height fixup — When viewport culling remeasures block heights (e.g. code blocks), scroll ratio is preserved on the next frame after scrolling stops to avoid layout “nudges” unrelated to sync.
Consolidated WYSIWYG editing architecture replacing per-widget focus/defer hacks. One block (heading, paragraph, list item, formatted block, or table cell) is active at a time; switching commits the previous buffer and opens the target in one click. Hub: docs/technical/markdown/rendered-edit-session.md; PRD: docs/ai-workflow/prds/prd-rendered-edit-session.md.
source_epochwidget identity (Tasks 95, 97) — Per-tab counter bumped on external invalidation (raw edits, file reload, undo/redo, find/replace, split raw pane) but not on rendered block commits. Rendered TextEdit ids scoped byeditor_id+source_epochinstead ofcontent_hash;content_hashretained for viewport culling only. Seerendered-widget-identity.md,Tab::source_epoch()insrc/state.rs.RenderedEditSessioncoordinator (Tasks 96–98) —BlockRef, tab-scoped session state machine (src/markdown/rendered_session.rs),switch_to_uicloses the previous block (SaveIfDirty) and queuesPendingActivationfor focus and cursor placement. Headings wired first; legacy heading-specificrendered_focuspaths removed.- Paragraphs & list items (Task 99) — Plain blocks share session buffers and the unified commit path; one-click cross-block switching among headings, paragraphs, and simple list items. Session buffers invalidate on
source_epochbump. - Formatted click-to-edit (Task 100) — Formatted paragraphs and list items use
BlockEditState.formatted_editing(styled display ↔ raw TextEdit); click enters edit with galley-based cursor mapping; Escape discards. RemovedFormattedItemEditStateand broken deferred blur logic. Seerendered-edit-session-formatted.md. - Tables on session model (Task 101) —
BlockRef::TableCellparticipates in block switching;signal_table_force_commitcommits the full table when leaving for a non-table block; Tab / Shift+Tab within a table preserves deferred table commit. Seerendered-edit-session-tables.md. - Split-view parity (Task 102) — Rendered pane in split shares the same session and
source_epochas rendered-only mode (rendered_editor_id(tab.id)); raw-pane edits bump epoch and invalidate session buffers (RS-6). Seerendered-edit-session-split-view.md. - Block-commit undo (Task 103) — Keystrokes inside an active rendered block stay off the undo stack until close/switch; one logical Ctrl+Z step per block commit via
src/markdown/rendered_commit_undo.rs. Seerendered-edit-session-undo.md. - Legacy cleanup & docs (Tasks 104–105) — Removed
rendered_focus.rsand remaining per-widget focus/commit dead paths. RS-1…RS-7 and TBLE-1…TBLE-3 acceptance tests documented inv0.3.0-regression-matrix.md§3.12.
- Spanish UI language — Español added to the Settings / Welcome language selector (
locales/es.yaml); system localees/es-*detection wired throughLanguage::Spanish.
- Phosphor icon font — Integrated
egui-phosphor0.12.0 (egui 0.34). Regular-weight glyphs registered at startup; helpersphosphor_font()/phosphor_rich_text()and a central mapping module (src/ui/phosphor_icons.rs). - App chrome — Ribbon, format toolbar, outline & productivity panels, terminal, settings & about, command palette, quick switcher, file tree, status bar, dialogs, title bar, tab close, theme toggle, and recovery UI now use Phosphor instead of emoji or mixed Unicode symbols.
- Preview & viewers — Markdown preview widgets (table alignment, list remove, code Run / Stop / status, mermaid diagram-type icons & warnings), JSON/YAML/TOML tree viewer, CSV row-count banner, Gantt done checkmarks, ER diagram PK/FK markers, and editor gutter fold carets.
- Locale cleanup — Removed duplicate emoji from UI strings in
en,de,es,ja, andzh_Hanswhere icons are drawn separately (tree viewer toolbar, outline stats heading, CSV header labels). - Unchanged by design: Git file-status badges, markdown callout body icons (Note/Tip/Warning content), and R/S/V view-mode segment letters.
- Build warnings — four-phase cleanup (Tasks 90–93) — After the egui 0.34 upgrade,
cargo buildemitted ~268 warnings (unused imports, unused locals, deprecated egui 0.34 APIs, anddead_code). Four hygiene passes brought the count to zero with no intended behavior changes: (1) Task 90 —cargo fixand manual unused-import cleanup inapp/,editor/ferrite/mod.rs,ui/mod.rs(~55); (2) Task 91 — unused variables, parameters, and unnecessarymutin central panel, title bar, Vim view, markdown widgets, terminal/productivity panels (~9); (3) Task 92 — egui 0.34 deprecated API migration (Ui::close_menu→close,child_ui→new_child,ComboBox::from_id_salt,Button::selectable, panelshow_inside,Frame::corner_radius, etc.) across terminal panel, central panel, settings, and app shell (~152); (4) Task 93 —dead_codeaudit: surgical removals, wiring fixes, and item-level#[allow(dead_code)]for intentional public API (~49). Triage policy documented inCONTRIBUTING.md§dead_codewarnings. - Minimum Rust version: 1.92 — Required by the egui 0.34 stack; use
rustup toolchain install 1.92.0or rely onrust-toolchain.toml. - Undo granularity in raw mode — Removed 500 ms time-based merging in
EditHistory. Eachrecord_operationscall (typically one per dirty editor frame while typing) is now its own undo step, so Ctrl+Z steps back incrementally instead of reverting an entire fast-typing burst in one action. Replace edits (delete + insert from the same diff) still undo atomically. Seedocs/technical/editor/undo-redo.mdanddocs/technical/editor/edit-history.md. - Rendered mode block-commit undo (Task 103) — Rendered WYSIWYG edits batch in session buffers until block close/switch; each commit produces one undo step (not one per keystroke). See
rendered-edit-session-undo.md. - Main ribbon toolbar always icon-only — Removed the left collapse/expand control and section labels ("File", "Edit", "Tools", structured-data type name, Export text). The toolbar is now a fixed compact icon bar (28px tall); Save and Export dropdowns still show full labels inside their menus. Tooltips and keyboard shortcuts are unchanged.
- UI icons: emoji → Phosphor — Replaced inconsistent emoji and one-off Unicode symbols across Ferrite's chrome and rendered-view controls with the Phosphor regular set for sharper, theme-consistent glyphs at all DPI scales. Mixed icon+text labels use separate Phosphor and proportional labels (or painter dual-font rendering) so glyphs render reliably; window titles stay text-only.
- CRITICAL: Smart-paste of mixed-script text crashes Ferrite (
STATUS_STACK_BUFFER_OVERRUN/0xc0000409) — Pasting any text whose first colon is followed by a multi-byte UTF-8 codepoint (e.g.Hebrew: שלום עולם,Bengali: আমি বাংলায়,Hindi: नमस्ते,note: 你好,emoji: 👨👩👧) aborted the process in release builds (panic = "abort"). Root cause wasFerriteApp::is_urlinsrc/app/input_handling.rsdoing&s[colon_pos..colon_pos + 3]to look for://, which panicked whencolon_pos + 3landed on a non-char-boundary inside a 2-/3-/4-byte UTF-8 codepoint. The smart-paste pipeline (consume_smart_paste) callsis_url/is_image_urlfor every paste event to decide whether to wrap the URL in a markdown link or image, so any paste containing a mixed-script line tripped the panic. Fixed by replacing the direct slice withs.get(colon_pos..colon_pos + 3) == Some("://"), which returnsNoneinstead of panicking on a non-char-boundary range. Pinned by 5 regression tests (is_url_does_not_panic_on_mixed_script_text,is_image_url_does_not_panic_on_mixed_script_text, plus URL/image-URL positive coverage). Tracked as Issue I-3 in the v0.3.0 regression matrix; release-gate-S1 → resolved. - Split / rendered view: only the first of consecutive fenced code blocks visible (#129) — Viewport/layout interaction hid later blocks until edits; fixed per
docs/technical/markdown/consecutive-fenced-blocks-fix.md(Task 83). - Empty markdown table cells hard to focus / edit (#131) — Hit targets and Tab / Shift+Tab navigation for empty cells (Task 84); see
docs/technical/markdown/table-cell-focus-navigation.md. - Rendered / split WYSIWYG: double-click required to switch blocks (Tasks 94–101) — After editing a heading, paragraph, list item, formatted block, or table cell, the first click on another block often only defocused the active
TextEdit; a second click was needed to focus the target. Root cause: scattered per-widget defer/commit logic racing with egui focus andcontent_hash-scoped widget id remaps. Fixed byRenderedEditSession::switch_to_ui: one click closes the active block (commit if dirty) and opens the target with correct cursor (RS-1, RS-4, RS-5). Seerendered-edit-session.md. - Cursor flash or disappear while typing in rendered headings (Tasks 95–97) —
ui.push_id(content_hash, …)remapped all TextEdit ids whenever the source changed during a focus transition, destroying caret state mid-session. Stable ids undersource_epochkeep the caret visible while typing (RS-2). Seerendered-widget-identity.md. - Formatted list/paragraph stuck in raw edit mode after clicking away (Task 94) — Click-to-edit blocks with inline formatting (
**bold**, etc.) could remain in raw TextEdit after blur becauseformatted_exit_should_savedeferred on the blur frame but was never re-checked. Phase 0 hotfix plus full session migration (Task 100): immediate save/exit on dismiss;formatted_editingcleared on switch (RS-3). Seerendered-edit-session-formatted.md. - Rendered / split tables: two clicks to edit another cell after typing — After editing a cell, the first click on another cell (same table or a different table in the file) often did nothing; a second click was required. Fixed initially with
TableGlobalFocusand pointer-down activation (Task 84); superseded in v0.3.0 by session-integrated table cells (BlockRef::TableCell,signal_table_force_commiton cross-block exit). Seetable-cell-focus-navigation.md,rendered-edit-session-tables.md. - Quick file switcher (Ctrl+P) search quality — Workspace quick open now tokenizes paths on
-,_,., and path separators so queries liketablesmatchtest_tables.mdandboxmatchestest_box_drawing.md. Search includes both indexed tree files and recent files (so unexpanded folders still match files you have opened recently). Replaced loose full-path fuzzy matching and the +100 recent-file score boost that ranked unrelated recent files above real matches. Seesrc/ui/quick_switcher.rs. - Frontmatter panel stale after tab switch — The FM tab could show the previous file’s fields, report “No frontmatter detected” on files that had YAML, or splice the wrong body when Add frontmatter was clicked. Caused by caching on
content_versionalone (each tab’s counter starts at0). Fixed by keying the cache on(tab_id, content_version)with regression tests. Seedocs/technical/ui/frontmatter-panel.md(Caching). - Rendered / split scroll “ghost snap” with sync off — Stale
pending_scroll_*from prior split sync or mode-toggle could still move the preview after scrolling stopped when Sync scroll was disabled. Turning sync off now clears tab pending targets and resets per-tabSyncScrollState; rendered mode only applies programmatic scroll when sync is on. Seedocs/technical/sync-scrolling.md. - Split 2-way sync: preview jumped up when scrolling to bottom — Rendered→raw top/bottom idle snap incorrectly wrote
tab.pending_scroll_offset, which the preview pane consumes on the next frame (raw max scroll is much smaller than preview max on code-block-heavy docs). Top/bottom snaps from the rendered pane now useSyncScrollState::set_raw_target()→EditorWidget::pending_sync_scroll_offset; middle-of-document sync still usespending_scroll_anchor. Raw→preview path unchanged (pending_scroll_offsetfor preview). Seedocs/technical/sync-scrolling.md§ Split-view scroll delivery. - Crash recovery dropped file opened on cold start — After a crash or forced exit, double-clicking a file while Ferrite was closed showed the session recovery dialog, but choosing Restore session only restored the previous session and did not open the file the user had just launched. Startup paths from CLI / file association are now deferred until the user answers the dialog, then opened afterward (existing tabs with the same path are focused, not duplicated). See
docs/technical/files/session-persistence.md. - Recovery / autosave files could bleed across tabs (cross-tab data loss) (Task 106) — Recovery JSON and
untitled_*.autosavefiles persist across launches, buttab_idis reset on every launch, so a leftover recovery file (e.g. an untitledasdasdbuffer withtab_id=10from a previous session) could silently apply to an unrelated path-backed tab assigned the same id this session — and a save would overwrite the real file. Recovery and autosave now require path-and-disk-hash identity (RecoveryContent.path/original_content_hash,AutoSaveMetadata.disk_content_hash) before they are applied. Mismatches are rejected and emitsession_recovery_identity_mismatchdiag events; legacy pre-task-106 files (no identity) keep the historical "tab id only" matching for upgraders. When the recovery file's identity matches but the buffer differs from current disk, the central panel now renders a non-blocking Recovered content differs from this file on disk banner above the editor with Keep Recovered / Reload from Disk actions (editing remains unblocked).prune_stale_recovery_filesextends tountitled_<id>.md.autosave. Seedocs/technical/files/session-persistence.md§ Identity-Gated Recovery. - CRITICAL: Restored recovery snapshots dropped all edits on the second crash cycle (Task 106.6 — disk-hash anchoring) — After clicking Restore on the session-recovery dialog, the new tab was built via
Tab::with_file(path, recovered_content), which sets bothtab.contentandtab.original_contentto the recovered buffer. That broke the tab's identity link to disk:is_modified()returnedfalse,disk_content_hash()hashed the recovered buffer, and the next crash snapshot wrote that wrong hash intorecovery/<id>.json::original_content_hash. On the next launch, Layer-3 identity gating inAppState::try_apply_recoverycorrectly rejected the poisoned recovery (disk hash didn't match the storedoriginal_content_hash), falling back to disk content — silently discarding every edit made since the previous Restore. Reproduces as: edit raw → kill → recover (ok) → edit rendered → kill → recover (jumps back to pre-first-edit, both edits gone).restore_from_session_resultnow splits the recovery branch: pureResolvedContent::Recoveredkeeps existing behaviour (disk == content or disk unreadable, both safe), whileResolvedContent::RecoveredWithDiskDivergenceconstructs the tab fromon_disk_contentand swaps in the recovered buffer viaset_content— sooriginal_contentstays anchored to disk,disk_content_hash()matches the live file, and the next snapshot'soriginal_content_hashis correct. Bonus: Ctrl+Z after Restore now walks back to disk content as one logical step. Regression tests:test_restore_with_divergence_anchors_original_to_disk,test_restore_then_edit_keeps_disk_hash_anchorinsrc/state.rs. Seedocs/technical/files/session-persistence.md§ Disk-hash anchoring across recovery cycles. - Export menu double icons (PDF, Print preview) — Locale strings still contained emoji while the ribbon already prefixed Phosphor icons; stripped duplicate glyphs from export menu labels in all locale files.
- Outline panel tab selector layout — Phosphor icons in tab labels broke hit-testing when allocated via nested
allocate_ui_at_rect; fixed with painter-based dual-font tab labels (paint_tab_label) so icons and text stay centered and clickable. - Mermaid flowcharts shifted right with a large left gap (#83, FC-83a) — TD/BT diagrams with feedback loops (e.g.
test_md/test_mermaid_issue_83.md) appeared right-aligned inside the diagram frame. Root cause: layers were centered inavailable_widthwhile the renderer sized to actual node bounds, plus symmetric back-edge padding offset content left. Fixed by centering TD/BT layers onmax_cross_size, normalizing layout to the margin, and applying back-edge padding only on sides that need loop clearance. Pinned bytest_fc_83a_layout_has_all_nodes(min_x at margin) andfc_83a_back_edge_padding_is_right_only. - Multi-cursor copy / cut only copied the primary selection — With multiple cursors, each with a range, Ctrl+C (and cut) put only the primary line on the clipboard.
selected_text()now joins every non-empty selection (top-to-bottom, newline-separated, VS Code-style); copy/cut handlers usehas_any_selection(); cut marks all affected lines dirty for syntax highlighting. Regression test:test_selected_text_multi_cursorinsrc/editor/ferrite/editor.rs. - Search in Files panel grew to full window height — Opening Search in Files (Ctrl+Shift+F) started small then expanded frame-by-frame as results loaded (egui
Window/Resizegrows tolast_content_sizeeach frame, which reads as a slow animation even withanimation_time = 0). Results now scroll inside a fixed-height region; max height capped at 480px (default 320px); window fade in/out disabled. Seesrc/ui/search.rs,src/ui/window.rs(search_panel_constraints). - Detached Productivity Hub fought resize and snapped width — Shrinking the floating hub felt animated because wide content re-expanded the window every frame and
max_sizeenforced a 560px width floor from dock width. Removed the floor; clipped scrollable inner content (same pattern as the docked sidebar); viewport-based max size only; fade in/out disabled. A separate OS window on another monitor (true pop-out) is not in v0.3.0; terminal pop-out (show_viewport_immediate) is the precedent for a follow-up. Seesrc/ui/productivity_panel.rs. - CSV rendered view: long cell text overflowed column bounds — The 50-character cap did not guarantee fit within the fixed column pixel width (common with long numeric strings), so text spilled into neighbouring columns or past the table edge before ellipsis appeared. Rendered cells now use
truncate_cell_to_pixel_width()(font-metrics binary search +...) and a painter clip rect while keeping the existing per-cellallocate_exact_sizecolumn layout; hover tooltips still show the full value. Seesrc/markdown/csv_viewer.rs(render_row_cells),docs/technical/viewers/csv-viewer.md. - Per-document view mode (Raw / Split / Rendered) not restored on reopen — Closing a tab and opening the same file again always opened in the global Default view mode (e.g. Raw), even after switching to Split. Root cause:
open_fileignored savedTabInfoinlast_open_tabs, closing a tab dropped that file’s entry on the next settings save, and title-bar view changes were not persisted. Reopen now restoresview_modeandsplit_ratioper path; closed tabs upsert intolast_open_tabs; settings merge open tabs without erasing closed-file state. Seedocs/technical/view-mode-persistence.md. - Quick note workflow: closing untitled tabs with content did not prompt to save — With Quick note workflow enabled (default), save prompts were suppressed for all pathless tabs, so closing a modified untitled tab discarded content silently even though quitting the app correctly stayed frictionless.
Tab::should_prompt_to_savenow takes aSavePromptContext: tab close (×, Ctrl+W) still shows the unsaved-changes dialog (Save / Don't save / Cancel) for modified untitled buffers; app exit continues to skip the dialog and rely on session recovery. Saved files on disk are unchanged. Seedocs/technical/config/quick-note-workflow.md. - Document nav buttons visible above modal overlays — The scroll-to top/middle/bottom controls (shown on hover in raw and rendered editors) painted on top of the Quick File Switcher (Ctrl+P), command palette, and Search in Files panel because both used
egui::Order::Foreground. Nav buttons now useOrder::Middle(below modal overlays) and are suppressed while any of those panels is open. Seesrc/ui/nav_buttons.rs,src/app/central_panel.rs. - Rendered / split: Ctrl+Home and Ctrl+End jumped within the active block — Inline
TextEditwidgets in rendered preview consumed those keys, moving the caret to the start/end of the current block instead of scrolling the document. Intercepted at theScrollArealevel inMarkdownEditor::show_rendered_editor(Raw mode was already correct via FerriteEditor). Contributed by @moabtools in PR #137. - Status-bar Help (
?) vs bottom-right resize (I-1, WIN-5) — Dragging from the bottom-right corner to resize no longer opened About/Help on release. Foreground resize click guards (consume_clicks_in_resize_zones) andWindowResizeState::blocks_widget_clicks()prevent edge widgets from stealing resize clicks. Seedocs/technical/platform/window-resize.md. - Integrated terminal: CJK paste / input local-echo (I-2, TRM-3) — Pasting or typing CJK text (e.g. Chinese) into the integrated terminal showed
????on the prompt line while the shell still received correct UTF-8 and error output displayed the real characters (Windows PSReadLine/cmd echoed with the legacy console code page). Fixed by initializing UTF-8 when spawning shells insrc/terminal/pty.rs(PowerShell:[Console]::InputEncoding,[Console]::OutputEncoding,$OutputEncoding,chcp 65001; cmd:chcp 65001), and lazy-loading CJK fonts when the terminal sends CJK input so echoed glyphs render in the monospace grid. Open a new terminal tab to pick up the startup script — existing sessions are not retrofitted. Seedocs/technical/terminal/terminal-cjk-wide-chars.md. - Rendered view: scroll jump when toggling task list checkboxes — Clicking
- [ ]/- [x]checkboxes mid-document could nudge the preview up or down even though the user had not scrolled. Root cause: the one-character source change invalidated viewport culling (content_hashmismatch → bootstrap remeasure with differenttotal_height), whilepointer.any_down()during the click started the scroll-cooldown window that suppressed height fixup. Fixed by reusing culling layout when block(start_line, end_line)structure is unchanged, refreshingcontent_hashwithout remeasuring, and treating only wheel input or scrollbar drag as active scroll (not checkbox clicks). Seedocs/technical/markdown/task-list-checkbox.md,docs/technical/markdown/rendered-view-viewport-culling.md,docs/technical/sync-scrolling.md.
0.2.9 - 2026-04-23
Hotfix release for four critical v0.2.8 regressions. No new features — upgrade strongly recommended.
- Crash in Split / Rendered view on empty documents (#127) — The WYSIWYG renderer's viewport-culling bootstrap used an inclusive range
first_vis..=last_visthat, when the document had zero blocks, iterated once and indexeddoc.root.children[0], aborting with "index out of bounds". Switched to a half-open range so empty documents render as a no-op. This was reproducible by launching Ferrite with Split set as the default view mode, or by clicking the Split/Rendered button on any freshly-created untitled tab. - CRITICAL: No unsaved-changes indicator and no save prompt on close, leading to silent data loss — Edits made through the raw FerriteEditor wrote the new content into
tab.contentbut never bumpedcontent_version, so the cachedis_modified()kept returning its initialfalse. Result: the title bar never showed the*dirty mark, Ctrl+W / window close skipped the "Save changes?" dialog, and auto-save never fired. Fixed by routing raw-mode edits throughprepare_undo_snapshot_hashed+record_edit_from_snapshot, and by centralizingcontent_versionbumps insiderecord_edit_from_snapshot/set_contentso every edit path (raw, rendered, tree viewer) invalidates the cache. - CRITICAL: Undo / redo not working — "Nothing to undo" after typing — The FerriteEditor maintains its own internal edit state, but
handle_undo/handle_redo(the Ctrl+Z / Ctrl+Y keyboard handlers) read fromtab.edit_history, which received no operations from raw-mode edits. Fixed by diffing the pre-edit snapshot against the post-edit content on every frame where FerriteEditor reportsis_content_dirty()and recording the resulting ops ontotab.edit_history. - Selection invisible in Light mode (#121) — FerriteEditor draws the selection rectangle on top of the text galley, so the fill has to stay partly transparent (otherwise selected text would disappear behind a solid rectangle). In Light mode the theme's selection colour
(215, 230, 250)composited at ~40% alpha over white collapsed to essentially white, making the selection invisible. BumpedBaseColors::light().selectedto a more saturated blue(100, 170, 245)that stays visible at reduced alpha while still looking at home as a light-theme widget highlight. - Document side panel tab labels overlapping at default width — The Outline/Stats/Links/FM/Hub tabs split
available_width / 5per tab, which clipped every label at the previous 200 px default. Raised the default panel width to 300 px and the minimum to 260 px so all five tab labels render in full. Existing users with a persistedoutline_widthbelow 260 are auto-migrated to 260 on next launch by the settings validator.
0.2.8 - 2026-04-14
Command Palette (#59)
- Alt+Space command launcher - Searchable command palette with fuzzy search across all available actions. Shows recently used commands first when empty, category-grouped browsing, keyboard shortcut hints per command. Configurable shortcut (default Alt+Space, alternative Ctrl+Shift+P selectable on Welcome page). Replaces the need for traditional menus — all ribbon and keyboard actions are discoverable through the palette.
- Platform-specific Alt+Space suppression - On Windows, a thread-level keyboard hook (
WH_KEYBOARD) intercepts Alt+Space before it reaches the OS window manager, preventing the system menu (Restore/Move/Size/Close) from appearing. No-op on macOS/Linux. - Deferred command dispatch - Palette commands are dispatched after render (same phase as keyboard shortcuts) to prevent mid-render state mutations that caused crashes with commands like Toggle View Mode.
- Open/Close Workspace - Added to command palette as palette-only commands (no default keyboard shortcut).
- Platform-specific Alt+Space suppression - On Windows, a thread-level keyboard hook (
Tab Interactions (#118)
- Middle-click to close tabs - Middle-clicking a tab closes it, matching standard behavior in browsers and other tabbed applications.
Rendered View Performance (#105)
Addresses slow/unusable rendered view on large documents (6k+ lines, 50k+ words).
- Markdown AST caching - Cache parsed
MarkdownDocumentusing blake3 content hash. Only re-parses when content actually changes instead of 60×/second. - Rendered view viewport culling -
.show_viewport()with 500px overscan buffer. Off-screen blocks getui.allocate_space()instead of full rendering. Reduces per-frame work from O(N blocks) to O(visible blocks). - Block-level height cache - LRU cache of measured heights per rendered block keyed on content hash. Enables accurate scrollbar positioning with viewport culling.
- Lazy block height estimation - Heuristic heights for unmeasured blocks, render budget cap (max 20 blocks/frame), progressive refinement. Initial lag under 100ms for 10K+ block files.
- Uniform height mode for large files - Auto-enabled for 100K+ line files: O(1) line positioning, no O(N)
cumulative_heightsvector, force-disabled word wrap above threshold. - Smarter LineCache invalidation and scaling - Targeted
invalidate_range(start, end)evicts only affected lines instead of full cache clear. Dynamicmax_entries(visible_lines)scales cache with viewport. 80%+ hit rate for unchanged regions after edits. - Per-frame O(N) elimination - 7 per-frame O(N) operations on
tab.contentcached viacontent_versioncounter:TextStats::from_text(),tab.title()/is_modified()(blake3 hash), save button, CJK/complex script font detection, auto-save tab loop, frontmatter panel content clone, MarkdownEditor content clone. - Background thread file loading - Files ≥5MB load on a background thread with progress bar, spinner, MB loaded/total, and cancellation on tab close. UI remains responsive at 60fps during load.
- CSV raw view per-frame allocation fix -
show_raw_view()calledself.content.to_string()every frame. Fixed with blake3 hash-guardedraw_view_textcache. - TreeViewer parse and raw view caching - Two blake3-guarded caches: raw view text cache (skip per-frame
to_string()), parsed tree cache (skip per-frameparse_structured_content()). Supports JSON/YAML/TOML. - Central panel undo content clone elimination - Removed per-frame
tab.content.clone()for undo recording; all modes useTab::edit_historywith blake3 hash-based snapshot elision (prepare_undo_snapshot_hashed). (v0.3.0: time-based undo grouping removed — see 0.3.0 Changed.)
Depends on: Phase 1 font loading from v0.2.7
- HarfRust integration for FerriteEditor - Integrated HarfRust (pure-Rust HarfBuzz port, v0.5.2+) for correctly positioned, contextually-formed glyphs for Arabic, Bengali, Devanagari, Tamil, and other complex scripts.
- Shaped galley cache - Extended
LineCacheto store shaped text runs (glyph IDs + positions) keyed on content+font+width. LRU eviction with invalidation on content/font/viewport changes. - Grapheme-cluster-aware cursor - Replaced character-based cursor movement with grapheme-cluster-aware navigation using
unicode-segmentation. Correct stepping over Bengali conjuncts, Korean jamo, emoji ZWJ sequences. - Shaped text measurement - Word wrap, line width, scroll offset, cursor/mouse positioning, and selection rendering all use shaped advance widths for complex-script lines.
Image & PDF Viewer Tabs (#108)
- Image viewer tabs - Open PNG, JPEG, GIF, WebP, BMP files in a dedicated viewer tab with zoom (Ctrl+scroll), fit-to-window, metadata display (dimensions, format, file size).
- PDF viewer tab - Open PDF files using hayro (pure Rust PDF renderer). Page navigation, zoom, texture caching per page.
- Strict line breaks setting - "Strict line breaks" toggle in Settings → Editor (default: off). When enabled, single newlines render as hard
<br>breaks (Obsidian model). Also available on the Welcome page.
- macOS .md file association (#102) - Added
UTImportedTypeDeclarationsblock toinfo_plist_ext.xmlto properly declare thenet.daringfireball.markdownUTI. This enables opening markdown files from Finder via "Open With Ferrite" or double-clicking when Ferrite is the default application. - Windows IME candidate box positioning (#103, #15) - Applied
layer_transform_to_global()toIMEOutputcoordinates so the OS receives correct screen coordinates for the candidate popup. - Double-dash setext headings and line collapsing - Extended
fix_false_setext_headings()to handle--(not just-). Preprocessing for\n--line breaks while preserving---horizontal rules and YAML frontmatter. - No space between paragraphs in rendered view (#109) - Added proper paragraph spacing after paragraph blocks. Cumulative heights updated for accurate scrollbar.
- Trailing spaces on plain text paragraphs - Plain paragraph WYSIWYG editing dropped trailing spaces due to per-frame re-initialization from AST. Fixed with persistent egui edit buffer (keyed by
node.start_line). - Search highlight positioning in markdown tables + z-order - Fixed find/render coordinate mapping for table cells. Resolved z-order stacking where Jump to menu rendered above Find and Search panels.
- Search highlight misalignment after document edits - Recompute match positions on buffer mutations. Version/hash-based auto-refresh for highlight state.
- Terminal CJK double-width character rendering (#110) - Added
unicode-widthcrate.put_char()advances cursor by 2 for wide chars with continuation markers. Renderer draws wide chars spanning 2 cell widths. Selection snaps to wide char boundaries. - Windows 11 borderless window UI offset (#112) - Added
.with_transparent(true)toViewportBuilderas DWM compositing workaround for Intel HD 4600 GPU rendering offset in borderless mode. - Scrollbar not resetting when switching documents (#113) - Scoped central-panel editor/preview widget IDs with
tab.idto prevent eguiScrollAreastate leaking across tab switches. - Strikethrough and inline formatting lost in tables (#117) - Table cells containing
~~strikethrough~~,**bold**,*italic*, or***bold italic***markdown lost their formatting during parsing/serialization becausetext_content()stripped inline markup. Replaced withserialize_inline_content()in bothserialize_tableandTableData::from_nodeto preserve formatting through round-trip. Additionally, table cells now render inline markdown as formatted rich text (bold, italic, strikethrough, inline code) using a customLayoutJob-based parser. Click a cell to switch to raw markdown editing; click away to see rendered formatting. Supports full nesting (e.g.,**~~bold struck~~**,***~~triple~~***). - Linux Cinnamon file dialog detection (#116) - Linux Mint Cinnamon (
XDG_CURRENT_DESKTOP=X-Cinnamon) was not recognized as a native desktop, causing unnecessary portal fallback. Addedx-cinnamonto native desktop detection list, updated portal install instructions to recommendxdg-desktop-portal-xapp/gtkfor Cinnamon, and fixed dialog cancellation being misclassified as a portal failure. - Custom font crash on Linux (#114) - Selecting certain system fonts (font collections, corrupt files, unsupported formats) crashed the app via epaint panic. Added TTF/OTF magic-byte validation rejecting
.ttccollections, WOFF, Type 1, and corrupt data. Wrapped font loading incatch_unwindas safety net. On failure, gracefully falls back to Inter font with toast notification. Also fixed HarfRust text shaping for custom fonts (FONT_CUSTOMcase was missing fromttf_bytes_for_font_id_shaping).
0.2.7 - 2026-03-11
- Wikilinks support (#1) -
[[target]]and[[target|display]]syntax with relative path resolution, spaces in filenames, click-to-navigate, same-folder-first tie-breaker, ambiguity prompting - Backlinks panel - Panel showing files linking to the current document; graph-based indexing for workspaces >50 files; click-to-navigate; detects both
[[wikilinks]]and[markdown](links)
- GitHub-style callouts -
> [!NOTE],> [!TIP],> [!WARNING],> [!CAUTION],> [!IMPORTANT]with color-coded rendering, custom titles, and collapsible state (> [!NOTE]-)
- Ctrl+Scroll Wheel zoom (#85) - Ctrl+Mouse Wheel mapped to
egui::gui_zoom(same as Ctrl++/Ctrl+-). AddedZoomIn/ZoomOut/ResetZoomas shortcut commands with keyboard bindings.
- Manual Check for Updates - Settings → About section with button to check GitHub Releases API; shows up-to-date, update available with download link, or error; user-initiated only (offline-first)
- Security hardening - Response URL validated against GitHub releases prefix; TLS via rustls (pure Rust)
- Lazy font loading for complex scripts - Extends the CJK lazy-loading system to cover 22 Unicode ranges across 11 script families: Arabic (5 sub-ranges), Bengali, Devanagari, Thai, Hebrew, Tamil, Georgian, Armenian, Ethiopic, other Indic (Gujarati, Gurmukhi, Kannada, Malayalam, Telugu), and Southeast Asian (Myanmar, Khmer, Sinhala). System fonts are loaded on demand when script characters are detected in file content or IME input (~1-5MB per script vs ~15-20MB for CJK). No new dependencies.
- Script detection utility -
detect_complex_scripts()andneeds_complex_script_fonts()with 17 unit tests covering all script families and boundary cases; used to trigger lazy font loading. - Complex script font preferences - Settings → Appearance → Additional Scripts section for pre-selecting fonts per script (Arabic, Bengali, Devanagari, Thai, Hebrew, Tamil, Georgian, Armenian, Ethiopic, Other Indic, Southeast Asian). User preferences are tried first before platform defaults; persisted in config; fonts load on-demand when files with those scripts are opened.
- Large file detection - Files >10MB on open show non-blocking performance warning toast
- Lazy CSV row parsing - Large CSV/TSV files (≥1MB) now use byte-offset row indexing instead of parsing all rows into memory. Only visible rows (~200) are parsed on demand with viewport caching. For a 1M-row CSV, reduces additional memory from ~100-200MB to ~8MB. Small files (<1MB) now use cached full parse (previously re-parsed every frame)
- Single-instance protocol - Double-clicking files (file tree or OS/Explorer) opens them as tabs in the existing Ferrite window instead of spawning new processes; lock file + TCP IPC with background accept thread for instant response
- PortableApps.com Format packaging - Full PAF-compliant portable distribution (
FerriteMDPortable_x.y.z_English.paf.exe). Stores all settings inData\settings\via the PAF launcher — no registry writes, no AppData modifications. Compatible with the PortableApps.com Platform menu. FERRITE_DATA_DIRenvironment variable - New config directory override via env var, enabling external launchers (PortableApps.com, custom wrappers) to redirect settings storage. Checked beforeportable/folder detection; fully backward-compatible with existing portable and standard installs.- Automated PAF build in CI - Release workflow now builds and signs the
.paf.exeinstaller alongside the portable zip and MSI. Version, icons, and metadata are derived automatically from the git tag.
- Official Nix flake - First-class
flake.nixfor reproducible builds and development shells. Supportsnix run github:OlaProeis/Ferrite,nix build .#ferrite, andnix developwith Rust toolchain + platform dependencies. Targets x86_64-linux, aarch64-linux, x86_64-darwin, aarch64-darwin. Includes declarative NixOS/Home Manager usage example. Contributed by @liuxiaopai-ai (PR #92). - Nix CI workflow - GitHub Actions workflow (
.github/workflows/nix.yml) evaluates package outputs for all supported systems and runsnix flake checkon Linux.
- File associations - Optional per-extension file type registration (.md, .markdown, .txt, .json, .yaml, .yml, .toml, .csv, .tsv) via OpenWithProgids; adds Ferrite to "Open With" menu and Windows Default Apps settings without overriding existing defaults
- Explorer context menu - Optional "Open with Ferrite" right-click entry for files and "Open Folder with Ferrite" for directories (including folder background)
- Add to System PATH - Optional PATH entry so
ferritecan be run from any terminal; cleanly removed on uninstall - Desktop shortcut - Optional desktop shortcut alongside the existing Start Menu shortcut
- Feature selection UI - Installer now uses WixUI_FeatureTree with a customization page where users can toggle each feature group independently
- Launch after install - "Launch Ferrite" checkbox on the installer exit dialog (checked by default)
- Table background & layout fix - Replaced pre-painted row backgrounds (which used pre-measured heights causing misalignment) with the Shape::Noop placeholder technique (same approach as egui's Frame). Backgrounds are now painted after content using actual rendered row dimensions, eliminating gaps, overlap, and text rendering outside row bounds. Fixed cell layout direction from inherited
left_to_rightto explicittop_downso vertical padding works correctly. - Column resizing - Drag vertical column separators to resize adjacent columns. Cursor changes to resize-horizontal on hover; visual guide line during drag. Custom widths persist in egui memory per table and scale proportionally on window resize. Double-click a separator to reset all columns to auto-calculated widths.
- Vim mode - Optional Vim-style modal editing with Normal/Insert/Visual modes. Essential Vim commands: hjkl movement, dd (delete line), yy (yank), p (paste), /search, v/V (visual selection). Mode indicator in status bar ([NORMAL]/[INSERT]/[VISUAL]). Toggle in Settings → Editor. Ctrl+ shortcuts still work globally when Vim mode is active.
- Welcome view on first run - Welcome tab on first launch with configuration for theme, language, editor settings (word wrap, line numbers, minimap, bracket matching, syntax highlighting), max line width, CJK font preference, and auto-save. Only shown when no CLI paths and no session-restored tabs. Contributed by @blizzard007dev (PR #80).
Frontmatter Editor (#94)
- Visual frontmatter panel - FM tab in outline panel for editing YAML frontmatter as key-value pairs with form-based interface, bidirectional sync with source
- Basic field type support - String, date, and list (tags with chip UI) field types with appropriate input widgets
- Format toolbar moved to editor bottom - Markdown formatting buttons (bold, italic, code, headings, lists, etc.) moved from ribbon to a collapsible toolbar at the bottom of the raw editor area. Visible in Raw and Split modes for markdown files. Collapse/expand via chevron toggle.
- Side panel toggle strip - Replaced separate Outline and Productivity Hub ribbon buttons with a thin toggle strip on the right edge of the editor. Click to open/close the side panel (Outline, Statistics, Backlinks, Productivity Hub tabs).
- German and Japanese in Settings - Deutsch and 日本語 now available in Settings → Appearance → Language (locale files already existed)
- Keep text selected after formatting (#72) - Bold, Italic, and other formatting operations now preserve both selection and editor focus. Users can chain formatting operations (e.g. Bold → Italic) without reselecting text.
- Default maximum line width changed to 100 characters - The default
max_line_widthsetting is now 100 characters (was "Off"). This provides a comfortable reading width out of the box, especially for zen mode, while still allowing users to change it to Off, 80, 120, or a custom width.
- Flowchart modular refactor - Split monolithic
flowchart.rs(3600 lines) into 12 focused modules underflowchart/directory:types.rs,parser.rs,layout/(config, graph, subgraph, sugiyama),render/(colors, nodes, edges, subgraphs),utils.rs. Zero behavior changes, all 83 tests pass.
- Instant file opening from Explorer - Redesigned single-instance IPC for near-zero latency. Moved instance check to before config/icon loading so the secondary process exits in <100ms (was 3-5s). Replaced per-frame non-blocking poll with a dedicated background accept thread that wakes the UI immediately via
ctx.request_repaint(), eliminating idle repaint delays (was up to 500ms). Added explicitshutdown(Write)for immediate EOF delivery. End-to-end: <50ms (was 3-10s).
- View mode bar always visible - The view mode segmented control (Raw/Split/Rendered) now appears for all editor tabs, not just markdown/structured/tabular files. File types that don't support split view (e.g.
.rs,.py,.txt) show a compact two-mode segment (Raw | Rendered). When default view mode is Split and a non-split-capable file is opened, the tab automatically falls back to Raw mode.
- Window control button redesign - Close (×), Minimize (–), Maximize/Restore, and Fullscreen buttons are now drawn with crisp manually-painted icons (line segments, no font glyphs), rounded hover backgrounds (4 px radius), and a more compact footprint (36 × 22 px, down from 46 × 28 px). All four buttons now have consistent rounded-rect hover styling.
- Close button - Icon drawn as two diagonal line segments for pixel-accurate rendering; switches to white on red hover.
- Maximize button - Rectangle icon with a thicker top edge (2 px) to suggest a window title bar; restore icon unchanged.
- Fullscreen button - Replaced broken arrow icon (was rendering as ×) with proper corner-bracket icons: expand ⌜⌝⌞⌟ when windowed, compress when in fullscreen.
- NE corner resize re-enabled - Top-right corner (
NorthEastdirection) can now be used to resize the window. The 12 px right margin on the button group keeps the 10 px corner grab zone button-free, so resize and close never conflict.TITLE_BAR_BUTTON_RIGHT_MARGINconstant documents the invariant.
- Linux file dialog error handling (#97) - Detect xdg-desktop-portal failures on Linux (Hyprland, Sway, i3, etc.) and show helpful error modal with distro-specific install instructions (pacman, apt, dnf) and "Copy Install Command" button, instead of silent failure
- macOS Release .app Bundle (#93) - Release CI now packages proper
.appbundle viacargo-bundleinstead of raw binary, fixing Gatekeeper blocking on macOS - Task List Checkbox Rendering (#95) - Task list checkboxes now render as proper UI elements instead of ASCII
[ ]/[x]in rendered view; bullet point markers suppressed for task list items - Light mode text invisible - All
RichText::strong()labels (section headers in Settings, Terminal, Files, About, and other panels) were invisible in light mode. Root cause: egui'sstrong_text_color()returnswidgets.active.fg_stroke.color, which was set toColor32::WHITEfor the pressed-button state. This bypassesoverride_text_color, rendering white-on-white text. Fixed by settingactive.fg_stroketocolors.text.primaryin the light theme. - Images not displaying in rendered mode - Markdown
images now render in Rendered/Split view; path resolution relative to document and workspace root; PNG, JPEG, GIF, WebP support; graceful placeholders for missing/unsupported files - CJK rendering after restart with explicit preference (#76) - Preload user's preferred CJK font at startup when preference is explicit (non-Auto), so restored tabs render correctly without tofu. Verified:
preload_explicit_cjk_font()loads the appropriate font,bump_font_generation()increments counter,editor.rsinvalidates line cache (Task 45) - CJK fonts load on language switch - Switching to Chinese or Japanese in the Welcome panel (or Settings) now lazily loads the required CJK font immediately, so translated UI labels render correctly instead of showing squares
- Latin-only names in Language and CJK selectors - Language and CJK Regional Preference dropdowns now use Latin-only display names (e.g. "Chinese (Simplified)", "Japanese") so they render correctly before CJK fonts load
- Syntax highlighting per-frame re-parsing -
highlight_line()was called every frame before cache check, causing lag on long lines; now checks cache first and only parses on cache miss - Scrollbar position with word wrap - Scrollbar thumb position now uses cumulative y-offsets from height cache instead of uniform line height; scrollbar accurately reflects position
- Scrollbar drag reverse mapping - Dragging scrollbar now uses
y_offset_to_line()binary search instead of uniform division for accurate jumps - Scrollbar jumping - Replaced per-frame
rebuild_height_cachewith dirty-flag approach; smoothed scrollbar height to avoid abrupt changes as wrap info is discovered - Crash on large selection delete with word wrap - Fixed capacity overflow panic when deleting large selections; added
saturating_sub, viewport clamping, andtruncate_wrap_info()for stale entries - List item text not wrapping in preview (#82) - Long list item text in rendered/split view extended as a single line beyond the pane edge instead of wrapping. Root cause: all 4 list item
TextEditwidgets usedsinglelinemode which fundamentally cannot wrap. Changed tomultilinewith customLayoutJoblayouter (matching the paragraph wrapping pattern),desired_rows(1), and newline stripping to prevent Enter from inserting line breaks within a list item. - Empty list item causes heading mis-render (#82) - Typing
-(dash-space, no content yet) after a paragraph caused the paragraph above to render as a large heading. Root cause: comrak interprets a single-followed by optional whitespace as a setext heading underline (valid per CommonMark spec), but in a markdown editor this is almost always the start of a list item. Addedfix_false_setext_headings()post-processing inparser.rsthat detects single-dash setext headings and converts them back to Paragraph + List(Item). - Windows IME backspace deleting editor text (#91) - When using Chinese (or Japanese/Korean) input methods on Windows, pressing Backspace during IME composition deleted already-committed characters in the editor instead of only modifying the pinyin/romaji in the composition window. Root cause: egui forwards raw
Key::Backspaceevents alongside IME preedit updates, and the editor processed both. Fixed by suppressing allKeyandTextevents whileime_enabledis true (active composition session). Affects Microsoft Pinyin, Xiaolanghao/Rime, and all other IME input methods on all platforms. - Crash when opening binary files as text - Opening image files (PNG, JPEG, etc.) or other binary data as text documents caused a panic: "byte index is not a char boundary". Root cause: (1) Binary files were not detected before opening, (2) The
count_links()andcount_images()functions instats.rsused unsafe byte string slicing that panicked on invalid UTF-8 boundaries. Fixed with: (1) Newis_binary_content()function instate.rsthat detects binary data using null byte detection and non-printable character ratio heuristics, (2) Safe string slicing usingget()instead of direct byte indexing ineditor/stats.rs. Binary files now show a user-friendly error message instead of crashing. - Extra spaces when copying from rendered mode - Copying text from syntax-highlighted code blocks or inline-formatted paragraphs in rendered/split view inserted phantom spaces at every token boundary (e.g.
json. loads( f. read())instead ofjson.loads(f.read())). Root cause: each syntax-highlighted segment and inline node was rendered as a separateui.label(), and egui's defaultitem_spacing.x(~8px) added gaps between them. Fixed by settingitem_spacing.x = 0.0in code block horizontal layouts (widgets.rs) and inline contenthorizontal_wrappedlayouts (editor.rs). - Session recovery dialog after "Don't Save" - Closing the app with unsaved changes and choosing "Don't Save" caused the session recovery dialog to appear on next launch, also closing any active workspace. Root cause:
create_lock_file()was called beforeload_session_state()at startup, socheck_and_clear_lock_file()always found the just-created lock file and setis_crash = trueon every launch. Combined with the session file recordinghas_unsaved_content = truefor discarded tabs, this falsely triggered the recovery prompt. Also meant crash detection was completely broken (lock file never persisted through runtime). Fixed by reordering: load session state first (checks previous run's lock file), then create lock file for the current session.
0.2.6.1 - 2026-02-06
Patch release: First code-signed release. Integrated terminal workspace, productivity hub, major app.rs refactoring into ~15 modules, and numerous bug fixes.
Contributed by @wolverin0 in PR #74 — first major community contribution!
- Multiple terminal instances - Create and manage multiple terminal sessions with tabs and shell selection (PowerShell, CMD, WSL, bash)
- Tiling & splitting - Create complex 2D grids with horizontal and vertical splits
- Smart maximize - Temporarily maximize any pane to focus on work (Ctrl+Shift+M)
- Layout persistence - Save and load your favorite terminal arrangements to JSON files
- Theming & transparency - Custom color schemes (Dracula, etc.) and background opacity
- Drag-and-drop tabs - Reorder terminals with visual feedback
- AI-ready indicator - Visual "breathing" animation when terminal is waiting for input (perfect for AI agents)
Also contributed by @wolverin0 in PR #74
- Productivity panel - Quick-access panel for common editing, navigation, and workflow tasks
- Tab drag reorder - Tabs can now be reordered by dragging with visual drop target indicator and
swap_tabs()state method - File watcher auto-reload - Externally modified files are now automatically reloaded when the tab has no unsaved changes; shows toast notification. If tab has unsaved changes, shows a warning instead
- Undo after text formatting - Bold, italic, and other formatting operations now create discrete undo entries via
break_group()calls; Ctrl+Z reliably reverses only the format - Multiline blockquote rendering - Consecutive blockquotes separated by blank lines are now merged into a single continuous block with one border
- CJK first-line paragraph indentation (#20, #26) - Fixed first-line-only indentation for Chinese (2em) and Japanese (1em) paragraphs in rendered mode using egui
LayoutJobwithleading_space
- Code signing - Windows artifacts (exe, MSI, portable zip) are now digitally signed via SignPath.io with a production certificate from SignPath Foundation. No more "Unknown publisher" warnings from Windows SmartScreen.
- Memory diagnostics - Added
[MEM]log messages at startup showing memory usage at key initialization points (visible with--log-level info)
Major restructure: Split the monolithic 7,600+ line
app.rsinto ~15 focused modules undersrc/app/.
- New modules:
mod.rs,title_bar.rs,central_panel.rs,keyboard.rs,input_handling.rs,line_ops.rs,file_ops.rs,formatting.rs,navigation.rs,find_replace.rs,export.rs,dialogs.rs,status_bar.rs,helpers.rs,types.rs - See refactoring plan
- Duplicate Line (Ctrl+Shift+D) wrong position - Rewrote
handle_duplicate_line()to usecursor_position(line, col) synced from FerriteEditor instead of staletab.cursorschar index - Keyboard shortcut conflict: Ctrl+Shift+E (#46) -
ToggleFileTreeandExportHtmlwere both bound toCtrl+Shift+E. ChangedExportHtmltoCtrl+Shift+X - Maximize/restore button icon - Button icon disappeared on hover because text was painted under the hover background. Rewrote to use custom painter drawing
- Drag-drop image inserts at wrong position - Image markdown link was inserted at stale
tab.cursorsposition instead of actual editor cursor. Now usescursor_position(line, col) - Smart paste not working - Selection state was read from stale
tab.cursorsinstead of FerriteEditor. Now queries FerriteEditor directly viaget_ferrite_editor_mut() - Auto-save toggle inconsistency - Title bar toggle directly flipped
auto_save_enabledfield instead of callingtoggle_auto_save()which also clearslast_edit_time - Rendered mode raw editor stuttering - Switching from Rendered to Raw mode caused full FerriteEditor recreation, losing viewport/syntax state. Added
set_content()method for in-place buffer replacement - Keyboard shortcut conflict: Ctrl+Backtick -
FormatInlineCodeandToggleTerminalboth bound to same key. ChangedFormatInlineCodetoCtrl+Shift+Backtick - CJK font crash on startup (#63) - Fixed crash when a non-Auto CJK preference is persisted but the font cannot be loaded. Fonts now return
Nonegracefully. Minor: tofu (□) may appear in settings labels when no CJK documents are open (fonts load lazily) - Portable Windows startup crash (#57) - Validate persisted window position values on load. Corrupted values (NaN, infinity, out-of-bounds) are reset so the OS selects a safe default. Portable ZIP now always includes the
portable/folder
Reduced startup memory by ~80MB for users with CJK font preferences set.
- Lazy CJK font loading - CJK fonts now load on-demand when text containing those scripts is detected, instead of loading all 4 fonts (~80MB) at startup
- System locale detection - Automatically detects system language and preloads only the ONE CJK font the user likely needs (~20MB):
- Japanese locale (ja-JP) → Japanese font only
- Korean locale (ko-KR) → Korean font only
- Chinese Simplified (zh-CN) → SC font only
- Chinese Traditional (zh-TW) → TC font only
- Other locales → no preload (fully lazy)
- Settings change optimization - Changing CJK preference in settings no longer loads all fonts; only already-loaded fonts are preserved
Memory impact:
| Scenario | Before | After |
|---|---|---|
| English user, no CJK | ~130 MB | ~50 MB |
| Japanese user | ~130 MB | ~70 MB |
| User with explicit CJK pref | ~130 MB | ~50 MB (loads on-demand) |
0.2.6 - 2026-01-26
Major Release: Complete custom text editor (FerriteEditor) replacing egui's TextEdit. Enables editing of 100MB+ files with ~80MB RAM usage (previously 1.8GB+ for 4MB files).
Milestone achieved: 80MB file now uses ~80MB RAM (was 460MB+). Editing is smooth and responsive.
Complete ground-up reimplementation of the text editor:
- FerriteEditor widget - Custom text editor built with egui drawing primitives
- Virtual scrolling - Only renders visible lines + buffer, enabling 100MB+ file editing
- Rope-based buffer - O(log n) text operations via
ropeycrate for instant edits - Full selection support - Click-drag, Shift+Arrow, Shift+Home/End, double-click (word), triple-click (line), Ctrl+A
- Clipboard operations - Ctrl+A/C/X/V with proper selection handling
- Syntax highlighting - Viewport-aware per-line highlighting using syntect
- Search highlights - Find/replace integration with capped highlight count (1000 max visible)
- Bracket matching - Windowed O(window) algorithm (~200 lines around cursor), works at any file size
- Word wrap - Dynamic line heights with proper visual cursor navigation
- Undo/redo - Operation-based EditHistory on each tab, Ctrl+Z/Ctrl+Y (v0.3.0+: per-record grouping; v0.2.x used 500 ms merge)
- IME support - Chinese Pinyin, Japanese Romaji, Korean Hangul input
- Code folding - Fold regions with gutter indicators, navigation skips folds
- Multi-cursor - Ctrl+Click to add cursors, simultaneous editing
- Document navigation buttons - Top/Middle/Bottom jump buttons in editor corner
- Semi-transparent selection - Selected text remains readable through highlight
- Cursor blink - Standard ~500ms blink interval with theme-aware color
- Auto-focus new documents - Cursor ready to type immediately without clicking
- .txt files in Open dialog - Text files now visible in default filter
Memory & Performance (#45)
Critical fix: Opening a 4MB text file caused 1.8GB RAM usage and laggy editor
- Editor per-frame content clone - Fixed 240MB/second allocation from cloning document every frame. Now uses lazy undo snapshot pattern.
- Case-insensitive search allocation - Fixed full document copy for search. Now uses regex
(?i)flag for streaming search. - Search debouncing - Added 150ms debounce preventing search on every keystroke.
- Large file memory optimization - Files >1MB get hash-based modification detection, cleared original bytes, reduced undo stack (10 vs 100).
- Bracket matching O(N) fix - Was allocating entire buffer every frame (4.8GB/sec for 80MB file). Now uses windowed ~20KB extraction.
- Memory release on tab close - Memory properly freed when closing large file tabs.
- Text jumping to next line - Fixed cursor unexpectedly jumping when typing at end of line
- Cannot scroll to bottom - Fixed missing lines at bottom of large files with/without word wrap
- Outline/Minimap cursor placement - Fixed cursor landing several lines below clicked heading
- Search highlight alignment - Fixed highlight drift on wrapped lines in large files
- Box drawing characters - Fixed U+2500-U+257F rendering as squares (added JetBrains Mono fallback)
- File browser context menu icons - Fixed doubled/square icons in right-click menu
- Link hover gear icon removed - Click now edits, Ctrl+Click opens in browser
- Initial cursor visibility - New documents show blinking cursor immediately
- Cursor appearance - Theme-aware color, proper height matching line height
- Windows Start Menu icon - Fixed pixelated/low-res icon (proper multi-size .ico)
Improved maintainability by splitting 2735-line monolith into focused modules
- editor.rs refactored - Reduced from 2735 to 1551 lines (43% reduction)
- New modules extracted:
buffer.rs- Rope-based TextBuffer with efficient text operationscursor.rs- Cursor and Selection types with multi-cursor supporthistory.rs- EditHistory with operation-based undo/redoview.rs- ViewState for virtual scrolling and viewport trackingline_cache.rs- LRU galley cache for efficient renderingselection.rs- Selection rendering, word boundaries, select_allhighlights.rs- Search/bracket highlight renderingfind_replace.rs- Replace operations with undo supportmouse.rs- Click position to cursor conversionsearch.rs- Search match management APIinput/- Keyboard and IME input handlingrendering/- Cursor, gutter, and text rendering
- Pattern: Rust's
impl FerriteEditordistributed across modules
- Format toolbar connected to FerriteEditor buffer operations
- Outline panel and minimap integrated with new scroll system
- Font settings dynamically update editor rendering
- Line numbers toggle works without restart
- File save preserves encoding through FerriteEditor
- Editor-Preview scroll synchronization — Addressed in v0.3.0 (split live sync + mode-toggle hybrid). See
docs/technical/sync-scrolling.md. Optional follow-ups: soft follow during scroll, mapping warmup for heavy Mermaid. - Large file preview disablement - Preview disabled message for >5MB files
- SignPath code signing - Awaiting organization approval
- Added
ropeycrate for rope-based text buffer - New
src/editor/ferrite/module structure with 15+ submodules LARGE_FILE_THRESHOLD(1MB) andLARGE_FILE_MAX_UNDO(10) constants- Hash-based modification detection for large files
- Lazy undo snapshot pattern with
pending_undo_state - Search debounce with
find_search_pendingandfind_search_requested_at - ViewState tracks wrapped line heights for proper scrolling
- LineCache with LRU eviction (200 entries max)
0.2.5.3 - 2026-01-24
- Flathub submission files - Added
.desktopand.metainfo.xmlfiles for Flathub packaging atassets/linux/
- SignPath integration - Windows artifacts (exe, MSI, portable zip) will be code signed via SignPath.io free tier for open source once organization approval is complete. This helps prevent Windows Defender false positives and establishes trust with users.
- CI/CD signing workflow - Signing is integrated into GitHub Actions release workflow and will run automatically on tagged releases once approved.
- View Mode Segmented Control - Replaced single-letter toggle button (R/S/V) with a polished pill-shaped segmented control showing all three view modes at once. Users can now click directly on the mode they want (Raw, Split, Rendered) with clear visual feedback for the active mode. The control adapts to file type: 3 modes for markdown/CSV, 2 modes for JSON/YAML/TOML. Visible in both normal and Zen mode.
- App logo in title bar - Added Ferrite logo with transparent background to the title bar for better brand visibility.
- Extended syntax support - Added 100+ additional language syntaxes via
two-facecrate, including PowerShell (.ps1/.psm1/.psd1), TypeScript/TSX, Zig, Svelte, Vue, Terraform, Nix, and many more. Previously unsupported languages now get proper syntax highlighting instead of plain text. - Syntax theme selector - New dropdown in Appearance settings to choose from 25+ syntax highlighting color themes including Dracula, Nord, Catppuccin (Mocha/Latte/Frappe/Macchiato), Gruvbox (light/dark), Solarized (light/dark), One Half, GitHub, VS Code Dark+, and more. Set to "Auto" to match the app theme.
- Alt-tab/taskbar visibility on Wayland - Fixed Ferrite window not appearing in alt-tab switcher or taskbar on Linux desktop environments (KDE Plasma, GNOME) running Wayland. Added
app_idto ViewportBuilder for proper window identification.
- Find/Replace replace icon - Fixed the replace icon (↳) showing as a square box in the Find and Replace panel. Changed to a universally-supported arrow character (→).
- Tree viewer context menu icon - Fixed the context menu button (⋯) in JSON/YAML/TOML tree viewer showing as a square. Changed to simple dots (...) for reliable rendering.
- Font atlas pre-warming - Added additional symbols (⇄⇅↳↵…⋯) to the font atlas pre-warm list to ensure they render correctly from startup.
- Recent files menu position - Fixed the recent files/folders popup menu appearing below and covering the filename button in the status bar. Menu now appears above the button using proper anchor positioning.
- Linux folder opening freeze - Fixed critical 10+ second UI freeze when opening workspace folders on Linux (especially Fedora/KDE Plasma). Root causes:
- notify crate misconfiguration - Was configured with
default-features = false, features = ["macos_kqueue"]which disabled the inotify backend on Linux, forcing fallback to slow polling-based file watching that had to walk and stat entire directory trees. - Synchronous recursive directory scanning -
Workspace::new()scanned the entire directory tree recursively on the main UI thread before showing anything. Now uses lazy loading: only the root directory is scanned initially, subdirectories are scanned on-demand when expanded.
- notify crate misconfiguration - Was configured with
- Line breaks in list items (#41) - Fixed hard line breaks (
\at end of line) within list items showing as a square box instead of rendering as a proper line break. - Git deleted file icon rendering - Fixed git "deleted" status icon showing as a square box in the file tree. The previous icon character (✕) was not supported by the embedded Inter font. Changed to standard ASCII minus character (-) for reliable cross-platform rendering.
- Blockquote/table overflow - Added horizontal scrolling for tables and blockquotes when content exceeds container width. Previously, wide content would expand the layout and break max line width for all subsequent content. Now wide tables scroll horizontally while the rest of the document respects the configured line width setting. Code blocks and mermaid diagrams already have internal horizontal scroll handling.
- PowerShell file rendering collapse - Fixed critical bug where PowerShell and other files without syntax definitions would collapse all content to a single line after initial render. Root cause: the fallback path for unsupported languages used
code.lines()which strips newline characters. Fix usesLinesWithEndingsto preserve newlines in plain text rendering. - View mode segment not clickable - Fixed issue on Linux where clicking the view mode segment (R/S/V buttons) in the title bar would initiate window drag instead of switching modes. Increased the drag exclusion zone width to fully cover all title bar controls.
- Inter font missing box-drawing characters - Fixed box-drawing characters (─│┌┐└┘ etc.) rendering as squares when using Inter font. The embedded Inter font doesn't include Unicode box-drawing block (U+2500-U+257F). Added JetBrains Mono as fallback font for Inter to provide these characters.
0.2.5.2 - 2026-01-20
- Delete Line shortcut (#29) - Cmd/Ctrl+D deletes current line (configurable in settings) - thanks @abcd-ca!
- Move Line Up/Down (#29) - Alt+Up/Down swaps current line with adjacent line - thanks @abcd-ca!
- macOS file type associations (#30) - Ferrite appears in Finder's "Open With" menu for .md, .json, .yaml, .toml, .txt files - thanks @abcd-ca!
- Windows portable build - True portable mode (
ferrite-portable-windows-x64.zip) withportablefolder for self-contained operation. All settings stored next to executable - perfect for USB drives. - Windows MSI installer - Proper Windows installer (
ferrite-windows-x64.msi) with Start Menu shortcut, application icon, and clean uninstall support via Windows Settings. Built with WiX Toolset. - Linux RPM package - Native package (
ferrite-editor.x86_64.rpm) for Fedora, RHEL, CentOS, Rocky Linux, and other RPM-based distributions. Includes desktop entry and icon integration.
- I18n audit & cleanup - Comprehensive audit of hardcoded strings, replacement with translation keys
- Orphaned key removal - Removed ~200 unused translation keys from locale files
- Locale file sync - All locale files now have consistent structure matching en.yaml
- New language support - Added Estonian and Norwegian Bokmål via Weblate community translations
- Ctrl+X cutting entire document - Fixed egui bug where Ctrl+X with no text selection would cut the entire document. Now correctly does nothing when nothing is selected.
- Linux window drag stuck mouse - Fixed critical bug where dragging the custom title bar on Linux caused the mouse to get "stuck" in drag mode. Root cause: egui's widget-level drag tracking desynchronized with the window manager after
ViewportCommand::StartDrag. Fix bypasses egui's drag state machine entirely, using raw input detection (primary_pressed()) for immediate, reliable window drag initiation. - Split mode cursor position (#29) - Line operations now work correctly in Split view; rendered pane no longer overwrites cursor position - thanks @abcd-ca!
- macOS modifier tooltips (#28, #29) - Tooltips now show "Cmd+E" on macOS instead of hardcoded "Ctrl+E" - thanks @abcd-ca!
- Semantic minimap highlight accuracy - Use byte offsets matching search behavior for correct highlight positioning
Note: Opening files via "Open With" or dragging onto app icon not yet supported on macOS due to winit#1751. Workaround: use
open -a Ferrite file.mdor File > Open.
0.2.5.1 - 2026-01-17
- Tiered idle repaint system - Implements intelligent repaint scheduling based on user interaction time:
- Active (animations/dialogs): Continuous repaint at 60 FPS
- Light idle (0-2 seconds): 100ms interval (~10 FPS)
- Deep idle (2+ seconds): 500ms interval (~2 FPS)
- User interaction tracking - Detects keyboard, mouse, and scroll activity to determine idle state
- Scroll animation awareness - Continuous repaint during sync scroll animations
- Automatic encoding detection - Files are now automatically detected for encoding on open using
encoding_rsandchardetngcrates. No more garbled text when opening legacy files. - Common encoding support - Full support for Latin-1, Windows-1252, ISO-8859-x, Shift-JIS, EUC-KR, GBK, and other common encodings beyond UTF-8.
- Status bar indicator - Current file encoding displayed in status bar with click-to-change dropdown menu for manual encoding selection.
- Preserve encoding on save - Files are saved back in their original encoding by default, not forced to UTF-8.
- CJK font lazy loading - CJK fonts (Korean, Japanese, Chinese) are now loaded on-demand when CJK characters are detected in documents, rather than at startup. This reduces idle memory usage from ~250MB to ~72MB for non-CJK users.
- Granular per-language CJK loading - Each CJK language font is loaded independently based on detected script (Hangul → Korean font, Hiragana/Katakana → Japanese font, Han characters → user's preferred Chinese variant). Opening a Korean document only loads Korean fonts (~30MB), not all CJK fonts (~180MB).
- Custom memory allocators - Platform-specific high-performance allocators:
mimallocon Windows,jemallocon Linux/macOS. Reduces heap fragmentation and memory usage, especially for long-running sessions. - Viewer state cleanup - Fixed memory leak by cleaning up
tree_viewer_states,csv_viewer_states, andsync_scroll_statesHashMap entries when tabs are closed. - egui temp data cleanup - Stale
SyntaxHighlightCacheand per-tab temporary data cleared from egui memory on tab close.
- Galley-based click mapping - Use egui's Galley for accurate click-to-character index conversion in rendered/split view click-to-edit.
- Formatting marker mapping - Map displayed text positions to raw markdown positions accounting for
**,*,`,~~, and[links](url)syntax. - Text wrapping support - Handle wrapped lines correctly by using actual text rect width for measurement.
- Bold font measurement - Use bold font for measurement when content starts with bold markers for better accuracy.
- Unified scroll calculation - Single function for all scroll-to-line operations (find, search-in-files, outline, minimap) ensuring consistent positioning.
- Fixed off-by-one errors - Consistent 0-indexed vs 1-indexed line number handling across all navigation functions.
- Fresh line height - Ensure actual rendered line height is used instead of stale/default values when calculating scroll positions.
- Large file accuracy - Scroll navigation now works correctly in files with 3000+ lines; previously target lines could be hundreds of pixels off or completely out of view.
- Semantic minimap highlight fix - Fixed highlight offset when clicking items in semantic minimap/outline panel. The highlight now correctly marks the target line by using byte offsets (matching search behavior) instead of character offsets.
- Session restore option - New setting to disable tab restoration on startup. When disabled, app starts with a single empty tab instead of restoring previous session.
- Fixed 10% idle CPU usage - Application now uses <1% CPU when truly idle (previously ~10% even with no user interaction)
- Window title optimization - Only send viewport title command when title actually changes, avoiding per-frame overhead
- Disabled repaint on widget change - Set
options.repaint_on_widget_change = falseto prevent unnecessary repaints - Animation time optimization - Removed conflicting animation_time override in ThemeManager
Intel Mac CPU Usage (#24)
- Removed verbose debug logging - Eliminated
[LIST_ITEM_DEBUG]statements in rendered mode that generated ~50,000 log lines per 22 seconds. - Fixed continuous repaint in Rendered mode - Root cause:
Sense::hover()on scroll area content caused ~60fps repaints bypassing idle throttling. Changed toSense::focusable_noninteractive()for proper ~10fps idle throttling.
- New file dirty flag - New untitled files no longer prompt to save when closed if they haven't been modified.
- CJK first-line indentation - Paragraph indentation now correctly applies only to the first line, not the entire paragraph.
- Workspace close button alignment - X button to close workspace panel shifted left to prevent overlap with resize handles.
- Linux close button - Fixed issue where window close button couldn't be clicked due to hit-testing/overlay interference.
- New
last_interaction_timefield tracks user activity for idle detection - New
get_idle_repaint_interval()returns appropriate interval based on idle duration - New
had_user_input()detects keyboard, mouse, and scroll events - Enhanced
needs_continuous_repaint()to check for scroll animations - Explicit vsync and run_and_return settings in NativeOptions
- Added FPS diagnostic logging (debug builds only) for idle CPU optimization verification
- New
CjkScriptDetectionstruct anddetect_cjk_scripts()function for granular script identification - Per-language
AtomicBoolflags track which CJK fonts have been loaded CjkLoadSpecdetermines fonts to load based on detected scripts and user preferences- Platform-conditional allocator setup in
main.rswith feature flags - Documentation: Idle Mode Optimization
- Adjusted release build profile - Changed
ltofrom "fat" to "thin",opt-levelfrom "z" to "3", disabled symbol stripping to reduce Windows Defender false positives. - Documentation - Added "Antivirus False Positives" section to README explaining the issue and workarounds.
0.2.5 - 2026-01-16
- Modular refactor - Split 7000+ line
mermaid.rsintosrc/markdown/mermaid/directory with separate files per diagram type - Edge parsing fixes - Fix chained edge parsing (
A --> B --> C), arrow pattern matching, label extraction - Flowchart direction fix - Respect LR/TB/RL/BT direction keywords in layout algorithm
- Node detection fixes - Fix missing nodes and improve branching layout in complex flowcharts
- YAML frontmatter support - Parse
---metadata blocks withtitle:,config:etc. (MermaidJS v8.13+ syntax) - Parallel edge operator - Support
A --> B & C & Dsyntax for multiple edges from one source - Rendering performance - AST and layout caching with blake3 hashing for complex diagrams
- classDef/class styling - Node styling via
classDefandclassdirectives - linkStyle edge styling - Edge customization via
linkStyledirective - Subgraph improvements - Layer clustering, internal layout, edge routing, title expansion, nested margins
- Asymmetric shape rendering - Flag/asymmetric node shape with proper text centering
- Viewport clipping fix - Prevent diagram clipping with negative coordinate shifting
- Crash prevention - Infinite loop safety, panic handling for malformed input
- Dual editable panes - Split view rendered pane is now fully editable, matching full Rendered mode behavior
- Both panes edit the same content with changes syncing instantly
- Full undo/redo support for edits in either pane
- Git status auto-refresh - Automatic refresh of file tree git badges on file save, window focus, periodic interval (10 seconds), and file system events
- Debounced refresh - 500ms debounce prevents excessive git2 calls during rapid operations
CSV Support (#19)
- CSV/TSV viewer - Native table view for CSV and TSV files with fixed-width column alignment
- Rainbow column coloring - Alternating column colors for improved readability
- Delimiter detection - Auto-detect comma, tab, semicolon, pipe separators
- Header row detection - Intelligent detection and highlighting of header rows
Internationalization (#18)
- i18n infrastructure - YAML translation files in
locales/directory with rust-i18n integration - Weblate integration - Community translations via hosted.weblate.org/projects/ferrite
- String extraction - UI strings moved to translation keys
CJK Writing Conventions (#20)
- Paragraph indentation - First-line indentation setting for Chinese (2 chars), Japanese (1 char), or custom
- Rendered view support - Apply
text-indentstyling to paragraphs in preview mode
- Keyboard shortcut customization - Users can rebind shortcuts via settings panel; stored in config.json
- Custom font selection (#15) - Select preferred font for editor and UI; important for CJK regional glyph preferences
- Main menu UI redesign - Modernized main menu with improved layout and visual design
- Windows fullscreen toggle (#15) - Dedicated fullscreen button (F10) separate from Zen mode (F11)
- Header labels - Display actual H1/H2/H3 text in minimap instead of unreadable scaled pixels
- Content type indicators - Visual markers for code blocks, mermaid diagrams, tables, images
- Density visualization - Text density shown as subtle horizontal bars between headers
- Mode toggle - Settings option to choose "Visual" (pixel) or "Semantic" (labels) mode
- Drag & drop images - Drop images into editor → auto-save to
./assets/→ insert markdown link - Table of Contents generation - Insert/update
<!-- TOC -->block with auto-generated heading links (Ctrl+Shift+U) - Document statistics panel - Tabbed info panel with word count, reading time, heading/link/image counts
- Snippets/abbreviations - User-defined text expansions (
;date→ current date,;time→ current time) - Recent folders - Recent files menu now includes workspace folders
- New Ferrite logo - Orange geometric crystal icon
- Platform icons - Windows
.ico, macOS.iconset, Linux PNGs (16-512px) - Window icon - Embedded 256px icon replaces default eframe "E" logo
- Search highlight drift - Fixed find/search highlight boxes drifting progressively further from matched text; caused by byte vs character position mismatch in UTF-8 text
- Config.json persistence (#15) - Fixed window state dirty flag; settings now persist correctly across restarts
- Session restore reliability - Workspace folders and recent files now persist correctly across restarts with atomic file writes
- Recent files persistence - Recent files list now saves immediately on file open, pruning stale paths
- Line width in rendered/split view (#15) - Line width setting now respects pane boundaries with proper centering behavior
- Quick switcher mouse support - Fixed mouse hover/click not working (item flickering but not selecting)
- Table editing cursor loss - Table cells no longer lose focus after each keystroke in Rendered/Split modes; edits are buffered and committed when focus leaves (deferred update model)
- Zen mode rendered centering - Content now centers properly in rendered/split view when Zen mode (F11) is active
- Windows top edge resize (#15) - Window can now be resized from all edges including top
- macOS Intel CPU optimization (#24) - Idle repaint scheduling reduces CPU usage on Intel Macs
- Split
mermaid.rsinto modular structure:src/markdown/mermaid/withmod.rs,flowchart.rs,sequence.rs, etc. - Added
GitAutoRefreshstruct for managing refresh timing and focus tracking - Added
had_focus_last_frameandcontent_modifiedfields toTableEditStatefor focus tracking - Added blake3 hashing for Mermaid diagram caching
- Added 11 unit tests for git auto-refresh logic
- Added comprehensive technical documentation in
docs/technical/
- Mermaid diagram toolbar (#4) - Toolbar button to insert mermaid code blocks (deferred to v0.3.0)
- Mermaid syntax hints (#4) - Help panel with diagram type syntax examples (deferred to v0.3.0)
- Simplified Chinese translation - Waiting for community contributor (deferred)
- Mermaid code cleanup - Flowchart.rs modular refactor and documentation (deferred to v0.2.6)
- Executable code blocks - Run code snippets in preview (deferred to v0.2.6)
- Content blocks/callouts - GitHub-style
[!NOTE]admonitions (deferred to v0.2.6)
0.2.3 - 2025-01-12
- Go to Line (Ctrl+G) - Quick navigation to specific line number with modal dialog and viewport centering
- Duplicate Line (Ctrl+Shift+D) - Duplicate current line or selection with proper char-to-byte index handling
- Move Line Up/Down (Alt+↑/↓) - Rearrange lines without cut/paste, cursor follows moved line
- Auto-close Brackets & Quotes - Type
(,[,{,", or'to get matching pair with cursor in middle; selection wrapping and skip-over behavior - Smart Paste for Links - Select text then paste URL to create
[text](url)markdown link; image URLs createsyntax
- Configurable line width (#15) - Limit text width for improved readability with presets (Off/80/100/120) or custom value; text centered in viewport
- macOS Intel cross-compilation - CI now cross-compiles for Intel Macs from ARM64 runner
- Task list rendering - Task list items with inline formatting now render correctly; fixed checkbox alignment and replaced interactive checkboxes with non-interactive ASCII-style
[ ]/[x]markers (interactive editing planned for v0.3.0) - macOS Intel support (#16) - Fixed artifact naming for Intel Mac builds; separate x86_64 build via
macos-13runner - Linux close button cursor flicker - Fixed cursor rapidly switching between pointer/resize near window close button by adding title bar exclusion zone (35px) for north-edge resize detection and cursor caching
- Added 7 new technical documentation files in
docs/technical/ - Extended keyboard shortcut system with pre-render key consumption for move line operations
0.2.2 - 2025-01-11
- Command-line file opening (#9) - Open files directly:
ferrite file.md,ferrite file1.md file2.md, orferrite ./folder/ - Version and help flags (#10) - Support for
-V/--versionand-h/--helpCLI arguments - Configurable log level (#11) - New
log_levelsetting in config.json with CLI override (--log-level debug|info|warn|error|off)
- Default view mode setting (#3) - Choose default view mode (Raw/Rendered/Split) for new tabs in Settings > Appearance
- CJK character rendering (#7) - Multi-region CJK support (Korean, Chinese, Japanese) via system font fallback (PR #8 by @SteelCrab 🙏)
- Undo/redo behavior (#5) - Fixed scroll position reset, focus loss, double-press requirement, and cursor restoration
- UTF-8 tree viewer crash - Fixed string slicing panic when displaying JSON/YAML with multi-byte characters (Norwegian øæå, Chinese, emoji)
- Misleading code folding UI (#12) - Fold indicators now hidden by default (setting available for power users); removed confusing "Raw View" button from tree viewer toolbar
- Large file editing - Deferred syntax highlighting keeps typing responsive in 5000+ line files
- Scroll performance - Galley caching for instant syntax colors when scrolling via minimap
- Ubuntu 22.04 compatibility (#6) - Release builds now target Ubuntu 22.04 for glibc 2.35 compatibility
- Added CLI reference documentation (
docs/cli.md) - Added technical docs for log level config, default view mode, and code folding UI changes
0.2.1 - 2025-01-10
- Sequence Diagram Control Blocks - Full support for
loop,alt,opt,par,critical,breakblocks with proper nesting and colored labels - Sequence Activation Boxes -
activate/deactivatecommands and+/-shorthand on messages for lifeline activation tracking - Sequence Notes -
Note left/right/oversyntax with dog-ear corner rendering - Flowchart Subgraphs - Nested
subgraph/endblocks with semi-transparent backgrounds and direction overrides - Composite/Nested States - State diagrams now support
state Parent { ... }syntax with recursive nesting - Advanced State Transitions - Color-coded transitions, smart anchor points, and cross-nesting-level edge routing
- Flowchart Branching - Sugiyama-style layered graph layout with proper side-by-side branch placement
- Cycle Detection - Back-edges rendered with smooth bezier curves instead of crossing lines
- Smart Edge Routing - Decision node edges exit from different points to prevent crossing
- Edge Declaration Order - Branch ordering now matches Mermaid's convention (later-declared edges go left)
- Text Measurement - Replaced character-count estimation with egui font metrics for accurate node sizing
- Node Overflow - Nodes dynamically resize to fit their labels without clipping
- Edge Labels - Long labels truncate with ellipsis instead of overflowing
- User Journey Icons - Fixed unsupported emoji rendering with text fallbacks
- Extended
mermaid.rsfrom ~4000 to ~6000+ lines - Added technical documentation for all new features in
docs/technical/
0.2.0 - 2025-01-09
- Split View - Side-by-side raw editor and rendered preview with resizable divider and per-tab split ratio persistence
- MermaidJS Native Rendering - 11 diagram types rendered natively in Rust/egui (flowchart, sequence, pie, state, mindmap, class, ER, git graph, gantt, timeline, user journey)
- Editor Minimap - VS Code-style scaled preview with click-to-navigate, viewport indicator, and search highlights visible in minimap
- Code Folding - Fold detection for headings, code blocks, and lists with gutter indicators (▶/▼) and indentation-based folding for JSON/YAML
- Live Pipeline Panel - Pipe JSON/YAML content through shell commands with real-time output preview and command history
- Zen Mode - Distraction-free writing with centered text column and configurable column width
- Git Integration - Visual status indicators in file tree showing modified, added, untracked, and ignored files (using git2 library)
- Auto-Save - Configurable delay (default 15s), per-tab toggle, temp-file based for safety
- Session Persistence - Restore open tabs on restart with cursor position, scroll offset, view mode, and per-tab split ratio
- Bracket Matching - Highlight matching brackets
()[]{}<>and markdown emphasis pairs**and__with theme-aware colors
- Rendered Mode List Editing - Fixed item index mapping issues, proper structural key hashing, and edit state consistency (Tasks 64-69)
- Light Mode Contrast - Improved text and border visibility with WCAG AA compliant contrast ratios, added separator between tabs and editor
- Scroll Synchronization - Bidirectional sync between Raw and Rendered modes with hybrid line-based/percentage approach and mode switch scroll preservation
- Search-in-Files Navigation - Click result now scrolls to match with transient highlight that auto-clears on scroll or edit
- Search Panel Viewport - Fixed top and bottom clipping issues with proper bounds calculation
- Tab Context Menu - Reorganized icons with logical grouping for better visual clarity
- Added ~4000 lines of Mermaid rendering code in
src/markdown/mermaid.rs - New modules:
src/vcs/for git integration,src/editor/minimap.rs,src/editor/folding.rs,src/editor/matching.rs,src/ui/pipeline.rs,src/config/session.rs - Comprehensive technical documentation for all major features in
docs/technical/
- Multi-cursor editing (Task 72) - Deferred to v0.3.0, requires custom text editor implementation
0.1.0 - 2025-01-XX
- Multi-tab file editing with unsaved changes tracking
- Three view modes: Raw, Rendered, and Split (Both)
- Full undo/redo support per tab (Ctrl+Z, Ctrl+Y)
- Line numbers with scroll synchronization
- Text statistics (words, characters, lines) in status bar
- WYSIWYG markdown editing with live preview
- Click-to-edit formatting for lists, headings, and paragraphs
- Formatting toolbar (bold, italic, headings, lists, links, code)
- Sync scrolling between raw and rendered views
- Syntax highlighting for code blocks (syntect)
- GFM (GitHub Flavored Markdown) support via comrak
- JSON file editing with tree viewer
- YAML file editing with tree viewer
- TOML file editing with tree viewer
- Tree viewer features: expand/collapse, inline editing, path copying
- File-type aware adaptive toolbar
- Open folders as workspaces
- File tree sidebar with expand/collapse
- Quick file switcher (Ctrl+P) with fuzzy matching
- Search in files (Ctrl+Shift+F) with results panel
- File system watching for external changes
- Workspace settings persistence (.ferrite/ folder)
- Modern ribbon-style toolbar
- Custom borderless window with title bar
- Custom resize handles for all edges and corners
- Light and dark themes with runtime switching
- Document outline panel for navigation
- Settings panel with appearance, editor, and file options
- About dialog with version info
- Help panel with keyboard shortcuts reference
- Native file dialogs (open, save, save as)
- Recent files menu in status bar
- Toast notifications for user feedback
- Export document to HTML file with themed CSS
- Copy as HTML to clipboard
- Windows executable with embedded icon
- Linux .desktop file for application integration
- macOS support (untested)
- Comprehensive technical documentation
- Optimized release profile (LTO, symbol stripping)
- Makefile for common build tasks
- Clean codebase with zero clippy warnings
- Built with Rust 1.70+ and egui 0.28
- Immediate mode GUI architecture
- Per-tab state management
- Platform-specific configuration storage
- Graceful error handling with fallbacks
- 0.2.8 - Command palette, LSP integration (Phases 1-2), HarfRust text shaping, image/PDF viewer tabs, rendered view performance (AST caching, viewport culling, lazy estimation), per-frame O(N) elimination, background file loading, strict line breaks, middle-click close tabs, custom font crash prevention, 13 bug fixes
- 0.2.7 - Wikilinks & backlinks, Vim mode, welcome view, GitHub-style callouts, check for updates, Ctrl+Scroll zoom, keep text selected after formatting, frontmatter editor, format toolbar & side panel toggles, lazy CSV parsing, large file detection, single-instance protocol, MSI installer overhaul, Nix/NixOS flake support, Unicode complex script font loading (Phase 1), Linux portal dialog error handling, macOS .app bundle CI, task list checkbox rendering, flowchart refactoring, window control redesign, word-wrap scroll fixes, 20+ bug fixes
- 0.2.6.1 - First signed release, integrated terminal workspace, productivity hub, app.rs refactoring (~15 modules), CJK memory optimization, 8+ bug fixes
- 0.2.6 - Custom text editor with virtual scrolling (critical for large files), memory optimization fixes
- 0.2.5.3 - Windows code signing (SignPath), View Mode Segmented Control, app logo in title bar, extended syntax highlighting (100+ languages), syntax theme selector (25+ themes), list line break fix, table overflow fix, PowerShell rendering fix
- 0.2.5.2 - Delete Line shortcut, Move Line Up/Down, macOS file associations, Windows portable build, MSI installer, Linux RPM package, Linux window drag fix, I18n cleanup, new language support
- 0.2.5.1 - Multi-encoding support, memory optimization (250MB → 60-80MB), CPU optimization (10% → <1% idle), cursor positioning improvements, Intel Mac CPU fix, bug fixes
- 0.2.5 - Mermaid refactor, CSV viewer, semantic minimap, i18n, CJK indentation, custom fonts, snippets, TOC generation, drag-drop images, document statistics, main menu redesign, split view editing, bug fixes
- 0.2.3 - Editor productivity release (Go to Line, Duplicate Line, Move Line, Auto-close, Smart Paste, Line Width)
- 0.2.2 - Stability & CLI release (CJK fonts, undo/redo fixes, CLI arguments, default view mode)
- 0.2.1 - Mermaid diagram improvements (control blocks, subgraphs, nested states, improved layout)
- 0.2.0 - Major feature release (Split View, Mermaid, Minimap, Git integration, and more)
- 0.1.0 - Initial public release