Repository navigation
How the Markdown Editor Works
Markdown files open with a formatting toolbar and three views: the text, the text beside a live preview, or a rich text editor that edits the rendered page. For the user side, see Markdown Editor. The rich editor has its own chapter: How the Rich Markdown Editor Works.
Most repositories keep their README and docs in Markdown. Raw text is tiring to read, and checking a table or a diagram in a browser breaks the flow. Code editors show a preview next to the text. Previewing cloned files is also a security surface, since Markdown may carry raw HTML.
FileView.svelte decides per tab. isMarkdownPath (markdown/viewMode.ts) matches .md, .markdown and .mdx. The view mode starts from the markdownViewMode setting (default "split") and each file remembers its own choice for the session (sessionViewMode, rememberViewMode, never saved).
flowchart TD
FV["FileView (Markdown tab)"] --> TB["MarkdownToolbar"]
FV --> CM["CodeMirror text editor<br/>(the real file)"]
FV -->|"split, tab on screen"| MP["MarkdownPreview.svelte"]
MP --> LP["library MarkdownPreview"]
FV -->|"preview, tab on screen"| RV["RichMarkdownView.svelte"]
TB -->|"text modes: format.ts"| CM
TB -->|"preview mode: onRichFormat"| RV
LP --> DOM["previewDom.ts"]
DOM --> R["render.ts (markdown-it)"]
DOM --> S["sanitize.ts (DOMPurify)"]
DOM --> M["mermaid.ts"]
DOM --> L["links.ts"]
RV -->|"edits as source changes"| CM
The text editor always exists, hidden in the Rich Editor, so save, undo, dirty state and diffs keep one source of truth.
MarkdownToolbar.svelte runs pure commands from markdown/format.ts and formatLines.ts (toggleInline, toggleLink, setHeading, toggleLinePrefix, toggleCodeBlock, insertTable). Each returns one TransactionSpec: one undo step, every cursor. In Preview mode the buttons call onRichFormat instead. The view switch sits in FileView's path bar. A Markdown editor adds Cmd+B (bold), Cmd+I (italic, in place of Select Parent Syntax) and Cmd+K (link).
sequenceDiagram
participant Ed as Text editor
participant MP as MarkdownPreview
participant D as previewDom
participant R as render.ts
participant P as DOMPurify
Ed->>MP: docVersion bumped
Note over MP: wait 150 ms, up to 1 s when the last render was slow
MP->>D: render(source), only while the tab is on screen
D->>R: markdown-it, one segment per top-level block
R-->>D: segments with data-line
Note over D: keep equal segments at the start and end
D->>P: sanitize only the changed segments
P-->>D: nodes
D->>D: links, images and diagrams near the screen
-
render.ts(markdown-it with GitHub tables, strikethrough and autolinks) addsdata-lineto every block, GitHub heading ids with theuser-content-prefix and task checkboxes with their line.highlight.tscolors fenced code with the editor's Lezer grammars (plain above 100,000 characters). Mermaid fences become placeholders; images keep their path indata-gm-src, with nosrc. - Output comes in segments, one per top-level block. Raw HTML that spans blocks stays in one segment (
openTagBalance), so every segment is complete HTML. -
previewDom.tsreplaces only the changed stretch of segments; kept ones just get theirdata-lineshifted. -
sanitize.ts(DOMPurify) removes scripts, styles, forms and controls, frames, embeds, media, event handlers,style,srcsetandtarget, and every input except checkboxes. Ids getuser-content-. The preview iscontain: layout paint.
links.ts is pure: classifyLink gives external (http, https, mailto), anchor, file (inside the workspace, /path from the repository root) or blocked; classifyImage gives data, remote (never fetched), local or blocked with a reason. Local images come from the read_image_data_url command (src-tauri/src/commands/files.rs, with the read_data_url helper in src-tauri/src/images.rs): inside the workspace folder after resolving symlinks, PNG, JPEG, GIF, WebP or SVG, at most 10 MB. The CSP allows 'self', data: and the preview scheme for images; this preview uses data:.
Diagrams are drawn in a hidden frame that is removed when the last document with diagrams closes, so mermaid never stays in memory; see How Markdown diagrams work. Built-in themes use mermaid's looks, other themes pass CSS variables (themeVariables). Every SVG goes through sanitizeSvg, a second DOMPurify instance. watchTheme redraws shown diagrams after a theme change.
scrollSync.ts interpolates between the data-line blocks, with the document start and end as fixed points; editorScroll.ts reads and sets the editor position as a fractional line. The side the user scrolls leads for 150 ms, so nothing echoes back.
Memory follows the screen, not the file length:
-
Hidden tabs free their preview.
FileViewmountsMarkdownPrevieworRichMarkdownViewonly whileisActive, and keeps the place (onLeave,initialLine,richScroll). -
Diagrams and images draw near the screen only.
nearScreen.tshas aNearScreenclass: each preview or rich editor makes one, with oneIntersectionObserverand a margin of one screen. Far diagrams are freed byreleaseDiagram, which keeps their height; unused image data URLs are dropped. - The mermaid cache is capped at 24 entries and 2,000,000 characters and goes with the diagram frame.
-
Off-screen blocks are not painted:
content-visibility: autoin the library'smarkdown.cssandmarkdown-rich.css.
Headless Chrome, 40 diagrams: 1 to 2 SVGs and 442 to 644 elements instead of 40 SVGs and 8,562 elements.
| File | What it does |
|---|---|
src/lib/views/files/FileView.svelte |
Mode and its switch, split size, mounting, keys |
src/lib/views/files/MarkdownToolbar.svelte, MarkdownPreview.svelte
|
Toolbar; the app's preview side |
ui/web/src/lib/components/MarkdownPreview.svelte |
Render timing, scroll sync, clicks |
ui/web/src/lib/markdown/format.ts, formatLines.ts
|
Formatting commands |
ui/web/src/lib/markdown/render.ts, engine.ts, previewDom.ts, previewImages.ts, sanitize.ts
|
Rendering, the lazy chunk, segment updates, DOMPurify |
ui/web/src/lib/markdown/mermaid.ts, nearScreen.ts
|
Diagrams (through a hidden frame), near-screen drawing |
ui/web/src/lib/markdown/links.ts, slug.ts, highlight.ts
|
Links and images, heading ids, code colors |
ui/web/src/lib/markdown/scrollSync.ts, editorScroll.ts, previewScrollSync.ts
|
Scroll sync (the page styles are the UI library's markdown.css) |
src/lib/markdown/markdownContext.ts |
Dark page, links, images, diagram frame |
src-tauri/src/commands/files.rs, images.rs
|
read_image_data_url, local images as data URLs |
Lazy chunks. markdown-it and DOMPurify load on the first preview, mermaid on the first diagram, TipTap on the first Rich Editor. vite.config.js lists them in optimizeDeps so the dev server does not reload on first use.
Remote images are never loaded. Opening a file must not contact a server or reveal that you read it.
What stays after first use. markdown-it (about 2 MB) and TipTap keep their heap until the app quits. Mermaid lives in a disposable frame (How Markdown diagrams work).
Fast scrolling in WebKit. WebKit's WebContent process peaks at 400 to 650 MB while a 35 KB README is scrolled fast, and settles at 60 to 200 MB after. Plain HTML without our code does the same (runs are noisy: 98 MB and 444 MB for one setup), so it looks like WebKit's own. content-visibility: auto is a candidate fix still under test; re-measure in the real app with the MCP tools scroll_view and sample_memory or the memory log (Debugging).
-
src/lib/markdown/render.test.ts,format.test.ts,links.test.ts,highlight.test.ts,scrollSync.test.ts,slug.test.ts,viewMode.test.ts. -
src-tauri/src/images.rsunit tests andread_image_data_url_reads_workspace_images_onlyinsrc-tauri/src/commands/tests.rs.
- Update Markdown Editor and retake its
markdown-*.pngscreenshots when a button, mode or limit changes. - A new setting goes in Settings and Settings Reference.
- The MCP tool
set_markdown_modeswitches a tab's view for automated checks.
Cmd+B hid the sidebar instead of making text bold.
- The issue: the Bold tooltip says Cmd+B, but in the text editor it hid the sidebar.
- Why it happened: the Markdown keymap bound only Cmd+I and Cmd+K.
- The fix and why we chose it: it binds Cmd+B and prevents the default, so the sidebar key skips it (Menu Keys and Routing). One bold key in both editors, beats a tooltip per mode.
Memory grew with long Markdown files and open Markdown tabs.
- The issue: the memory readout kept climbing while a long file with many mermaid diagrams was open, and every open Markdown tab added more.
- Why it happened: every diagram was drawn and kept as SVG, every local image was loaded as a data URL, hidden tabs kept their whole rendered document, the mermaid cache had no limit, and mermaid left its temporary render elements in the page.
- The fix and why we chose it: the changes in Memory above, so memory depends on what is on screen.
These pages are generated from docs/wiki. To correct something, open a pull request against the source file; edits made here are overwritten.
Using Nibtree
- Getting Started
- Welcome Screen
- Workspaces
- Files Panel
- File Operations
- File Icons
- Rounded Panels
- Editor and Tabs
- Pin Reorder and Wrap Tabs
- Single Tab Title
- Split Editor
- New File and Unsaved Changes
- Unload Hidden Tabs
- Editing Code
- Code Appearance
- Markdown Editor
- Image and PDF Preview
- Search Everywhere
- Recent Files
- Navigation Bar
- Find and Replace
- Changes and Commits
- Sign In Prompts
- Commit Box Layout
- Repository Actions
- Commit Options
- Ignoring Files
- Diffs
- Inline Diffs
- Staging View
- Revert a Change
- Resolving Conflicts
- Merge Tool
- Blame
- History and Log
- Merge Commits
- Branches and Tags
- Branches Popup
- Remotes
- Stashes
- Shelf
- Git Menu
- Git Dialogs
- Interactive Rebase
- Worktrees
- AI Agents
- AI Tasks
- AI Usage
- Keep Computer Awake
- Submodules
- Git LFS
- GitHub
- Git Console
- Terminal
- Terminal Features
- Scripts
- Navigation
- Menus
- Settings
- Settings Search
- Color Themes
- Settings Terminal and Automation
- Settings Files
- Updates
- Status Bar and Help
- Notifications
- Memory Use
- Clear Cache
- MCP and CLI
- Git Mergetool
- Keyboard Shortcuts
- Keyboard Shortcuts Editor
- Troubleshooting
- Troubleshooting Git
Developers
- Developer Guide
- Architecture
- Project Layout
- Backend
- How Folder Watching Works
- Folder Watching Bugs We Fixed
- How Workspace Files Work
- How the Preview Scheme Works
- Backend Services
- How Terminal Features Work
- Terminal Bugs We Fixed
- Frontend
- How the Rich Markdown Editor Works
- Rich Markdown Bugs We Fixed
- Git Menu Links
- Menu Keys and Routing
- Frontend Modules
- Blame Bugs We Fixed
- Project Color And Title Bar
- One Diff View
- Diff Bugs We Fixed
- Merge Tool Layouts
- Merge Tool Bugs We Fixed
- Color Theme Bugs We Fixed
- How the Connected Diff Works
- Rounded Panels Bugs We Fixed
- Update Bugs We Fixed
- Status Bar Bugs We Fixed
- MCP and CLI Bugs We Fixed
- Repository Actions Bugs We Fixed
- How the Changes Tab Works
- CodeMirror Patterns
- How the Cursor Works
- How Editor Features Work
- How the Path Bar Works
- Commands and Events
- Settings Reference
- Commands Workspace and Changes
- Commands Branches and Remotes
- Commands History and Extras
- Commands App and Tools
- Testing
- Test Suites
- Debugging
- Debugging with MCP
- Measuring Setting Memory
- How Terminal Memory Is Measured
- How Markdown Diagrams Work
- Releases and CI
- Publishing the Wiki
- Versioning and Changelog
- Browser Version Packages
- Browser Release Checks
- Docker Image
- Platforms and Signing
- Windows Support
- Windows Bugs We Fixed
- Windows MCP and CLI
- Docs and Screenshots
- UI Library
- UI Library MCP
- UI Library Bugs
- UI Library Migration
- Contributing
How features work
- Welcome screen
- Workspaces and folders
- Files panel
- File operations
- File icons
- Editor and tabs
- Pin, reorder and wrap tabs
- Single tab title
- Split editor
- New File and unsaved changes
- Unload hidden tabs
- Editing code
- Code appearance
- Markdown editor
- Image and PDF preview
- Search Everywhere
- Recent Files
- Navigation Bar
- Find and replace
- Changes and commits
- Sign-in prompts
- Commit box layout
- Repository actions
- Commit options
- Ignoring files
- Diffs
- Inline diffs
- Staging view
- Revert a change
- Resolving conflicts
- Merge tool
- Blame
- History and log
- Merge commits
- Commit tabs
- Branches and tags
- Branches popup
- Remotes
- Stashes
- Shelf
- Git menu
- Git dialogs
- Interactive rebase
- Worktrees
- AI agents
- AI usage
- Keep computer awake
- AI tasks
- Submodules
- Git LFS
- GitHub account
- Git Console
- Back and Forward
- Integrated terminal
- Scripts tool window
- Menu bar
- Settings
- Search in Settings
- Color themes
- Rounded panels
- Updates and release channels
- Status bar and help links
- Notifications
- Memory readout and memory log
- Clear Cache
- MCP server and command line tool
- git mergetool mode