Skip to content

Background 04 forge and notes

BuyItLandon edited this page Oct 17, 2026 · 2 revisions

Research note dated 2026-10-09. Read for reasoning, not current fact; later decisions in docs/decisions.md and docs/architecture-decisions.md win.

Read with D28 (CodeMirror 6 in a small local web view for Notes and Forge) and the Notes and Forge epics, issues #35 and #42. Notes 07 and 08 correct parts of the earlier notes, and note 03 assumes Tauri.

Moved from docs/research/04-forge-and-notes.md (original at af3de53) on 2026-10-16.

Forge and Notes: editor, syntax, language servers, git, terminal, run control and notes

Date: 2026-10-09

Questions:

  1. Editor component in the web view: CodeMirror 6, Monaco or something else, compared on large files, split views, theming, licence, bundle size, accessibility, IME, and how each takes highlighting and LSP data from Rust.
  2. Syntax: Tree-sitter in Rust sending highlight spans, against CodeMirror's Lezer grammars in the web view; latency, coverage, incremental parsing, what "language-agnostic" needs, and how Zed and Helix ship grammars and queries.
  3. LSP client in Rust: crates, and how Zed, Helix and Lapce pick, install and combine language servers.
  4. Git: git2 (libgit2) against gix (gitoxide) for log with graph, diff, hunk staging, commit, branches and conflict state; licences and Windows builds.
  5. Terminal: portable-pty and ConPTY with xterm.js; throughput over Tauri IPC; shell detection.
  6. Run control: discovering tasks from Cargo, npm, pyproject, justfile and Makefile without per-language settings; whether a debugger (DAP) fits v1.
  7. Notes storage and parsing: comrak, pulldown-cmark and markdown-rs with wikilinks and frontmatter; file watching and outside edits.
  8. Notes editor: Obsidian-style live preview on CodeMirror 6 against WYSIWYG on ProseMirror, TipTap or Milkdown, with plain markdown files as the source of truth.
  9. Wikilinks and backlinks: resolution, renames that update links, and the shared index.
  10. Database views over frontmatter: how Obsidian Bases, Dataview and Notion model them, and the smallest v1.

Two scope notes from the owner, received during the work: add a short section on Rust-native equivalents (toolkits themselves are in 06-all-rust-stack.md), and judge every choice on performance (typing latency, large files, highlighting cost, terminal throughput, LSP round trips, memory, startup), with numbers where primary sources have them. The web view stays the primary case.

Terms. LSP (Language Server Protocol) is the JSON-RPC protocol an editor uses to get completions, diagnostics and navigation from a separate language server process. DAP (Debug Adapter Protocol) is its debugging counterpart. Tree-sitter is a C library that builds a concrete syntax tree and re-parses incrementally after edits; queries (.scm files) map tree nodes to highlight names, outline items and so on. Lezer is CodeMirror's JavaScript parser system, inspired by Tree-sitter. A decoration in CodeMirror is styling or a widget attached to a document range without changing the text. A rope is a tree of text chunks that makes edits in large documents cheap. ConPTY is Windows' pseudoconsole API, which lets a program host a shell as a terminal would. Reftable is Git's newer binary format for storing branch and tag references. IME (input method editor) is the OS component used to type Chinese, Japanese, Korean and similar scripts. IPC numbers for Tauri are in 03-core-architecture.md, section 2, and are only referenced here.

Short answer

  • Use CodeMirror 6 for both Forge and Notes; not Monaco. CodeMirror renders only the viewport, demonstrates a document of several million lines, and keeps highlighting huge files under a work budget (20 ms per edit, 3 s of background parsing per 30 s, nothing past 100,000 characters beyond the viewport), while Monaco's text model (shared with VS Code) switches tokenization off above 20 MB or 300,000 lines. CodeMirror's basic setup is 119 KB gzipped against Monaco's 3.1 MB across 96 chunks, external highlighting and diagnostics are first-class decorations, and both are MIT. Monaco's real advantages (a built-in diff editor, models shared between split editors, VS Code-grade accessibility) do not outweigh that.
  • Tree-sitter in Rust is the single parser for code, and the keystroke never waits for it. CodeMirror applies each keystroke locally and maps existing highlight decorations through the edit; Rust re-parses incrementally (Zed allows itself 1 ms on the main thread before moving the parse to a background thread) and sends back only the spans that changed inside the viewport, small enough for Tauri's fast path. Tree-sitter covers hundreds of languages against Lezer's 19 full grammar packages plus 110 older line-based modes, and Rust needs the tree anyway for the outline, context rail and index. If Phase 0 shows the round trip over WebView2 exceeds one frame, run the same grammars as WASM in the web view (web-tree-sitter) rather than switching to Lezer.
  • Ship grammars compiled into the binary and keep the language list as data. Helix's per-language configuration is the model to copy. Take queries from the grammar repositories (MIT) or Helix (MPL-2.0), never from Zed's built-in languages, which are GPL-3.0.
  • The LSP client lives in Rust, as a thin JSON-RPC layer over each server's stdin and stdout (Zed, Helix and Lapce all wrote their own), with types from lsp-types or the metamodel-generated gen-lsp-types. Rust filters and sorts large completion lists before anything crosses into the web view. v1 uses servers the user has installed, Helix-style, with Helix's ordered server list and per-feature filters; Zed-style auto-download comes later.
  • Git: the spec's "use libgit2" has two primary-source problems. libgit2 cannot read reftable repositories, which made Zed delete git2 on 2026-06-02 (Git 3 will create reftable repositories by default), and its maintainers decline to run hooks. Recommendation: use the git command line for every write and network operation (commit with hooks, checkout, merge, push and fetch with the user's credential helpers), and an in-process reader behind a trait for the fast read paths. git2 is the complete reader today; gix is pure Rust and faster where measured (Turborepo cut libgit2's single-threaded status cost by 25%) but lacks high-level checkout, merge and add. The owner has to approve this departure from the spec.
  • Terminal: portable-pty (ConPTY) plus xterm.js 6 with its WebGL renderer. xterm.js parses 5 to 35 MB/s, below the roughly 50 MB/s binary IPC reported on Windows, so xterm.js sets the ceiling. Send coalesced raw byte buffers once per frame, apply watermark flow control, and detect shells the way VS Code's source does.
  • Run control reads the project, not settings: cargo metadata, package.json scripts, just --dump --dump-format json, make -qp, [project.scripts] and the common pyproject task tables, plus .vscode/tasks.json import as Zed does. Defer the debugger: Zed's DAP debugger took 8 months, 977 commits and over 25,000 lines.
  • Notes: markdown files stay the truth, edited in CodeMirror 6 live preview as Obsidian does (its editor is CodeMirror 6), never re-serialised by a WYSIWYG model. Rust indexes with pulldown-cmark (wikilinks since 0.13.0, YAML metadata blocks, byte offsets), and every change Syrinx makes to a file (rename, property edit) is a byte-range patch. notify plus notify-debouncer-full handle outside edits.
  • Links and views: a link table in SQLite drives backlinks and rename rewrites, resolved by Obsidian's rule (file name if unique, else path). The v1 database view is one saved table over a folder or tag, with frontmatter columns, sort, simple filters and inline edits written back losslessly, possibly in a subset of Obsidian's documented .base format.
  • Rust-native equivalents exist only as parts. Ropes, shaping, Tree-sitter, alacritty_terminal and gpui-component's code editor are reusable; an editor's selection, IME and accessibility layers and any rich markdown editor would have to be built, and Zed's editor crates are GPL-3.0. Not worth it for v1.

Findings

1. The editor component

CodeMirror 6. The view renders "only ... that plus a margin around it" and offers visible ranges so extensions skip off-screen work (guide; high). A demo loads "a document of a few million lines"; highlighting lags far down because the parser "limits the amount of work it does" and stops when idle (million-line example; high). The budgets in source: 20 ms of parsing immediately per document change, at most 3,000 ms of background parsing per 30,000 ms, and no eager parsing beyond 100,000 characters past the viewport (language.ts; high). The homepage claims screen reader and keyboard-only support, bidirectional text and native mobile selection under MIT (codemirror.net; high that it claims this). @codemirror/view 6.43.14 shipped 2026-10-07; the 2025–2026 changelog carries more than ten composition (IME) fixes and VoiceOver announcement fixes, so IME is maintained but still moving (changelog; high). It uses the EditContext API only on Android by default and a contenteditable element elsewhere (editorview.ts; high). Split views of one document do not sync by themselves: the official example forwards each transaction to the other view and keeps one undo history (split example; high). Theming is CSS generated per extension, with &light and &dark variants, so CSS custom properties from Syrinx's five palettes apply directly (guide; high). A diff and merge view exists as @codemirror/merge 6.12.2 (npm; high).

Monaco is generated from VS Code's sources, MIT, with no mobile support; 0.56.0 split its ESM build into tree-shakeable entry points, AMD is deprecated, and 0.57.0 is current (README, changelog; high). Its text model marks a file as large above 20 MB or 300,000 lines, stops syncing it to workers above 50 MB, and flags heap-heavy operations above 256 million characters (textModel.ts; high); VS Code's notes say large files lose tokenization, folding and similar features to halve memory (VS Code 1.15; medium, 2017). Its piece-tree buffer uses memory "very close to the original file size", where the old line array needed about 600 MB for a 35 MB, 13.7-million-line file (text buffer blog; high). Input goes through EditContext by default wherever the browser supports it, which includes WebView2 (editorOptions.ts; high). Models are created separately from editors (createModel), it has createDiffEditor, and external tokens and errors arrive through semantic-token providers and setModelMarkers (standaloneEditor.ts, standaloneLanguages.ts; high). That model split is why two Monaco editors can show one buffer, as VS Code's split editors do (medium).

Size. Bundlephobia measures codemirror 6.0.2 (the basic setup) at 373 KB minified and 119 KB gzipped, and monaco-editor 0.57.0 at 96 assets totalling 13.2 MB minified and 3.1 MB gzipped, the main editor chunk alone 4.2 MB and 1.1 MB (bundlephobia API, Monaco; high for the measurement, which includes every Monaco language and worker). In a desktop app that is script parse time and heap at tab open, not download. No primary typing-latency benchmark of either editor was found (searched; low).

Taking data from Rust. CodeMirror takes highlighting as mark decorations in a state field updated by effects, diagnostics through @codemirror/lint, completions through an async completion source, and hovers through hoverTooltip (reference manual; high). Its official @codemirror/lsp-client (6.3.0, MIT) speaks LSP over any transport with send and subscribe, maps server responses through edits made while a request was in flight, and was tested mainly against a TypeScript server; its repository moved from GitHub to code.haverbeke.berlin on 2026-04-15 (GitHub archive notice, announcement; medium). Monaco's LSP route, monaco-languageclient, is aligned to @codingame/monaco-vscode-api and VS Code 1.138, a much heavier stack (repository; high).

2. Syntax: Tree-sitter in Rust or Lezer in the web view

Lezer is "heavily inspired by tree-sitter"; its author did not use Tree-sitter because it "is written in C, which is still awkward to run in the browser" and "generates very hefty grammar files" (Lezer post; high). It re-parses incrementally by reusing nodes and always produces a tree (lezer.codemirror.net; high). Coverage: @codemirror/language-data maps 31 entries to 19 full lang-* packages and 110 to legacy stream modes, which are line-based tokenisers without a real tree (language-data.ts; medium, file truncated in view).

Tree-sitter aims to be "fast enough to parse on every keystroke"; its organisation maintains 24 parsers (tree-sitter; high), and nvim-treesitter lists about 323 (supported languages; medium, hand count). The Rust crate (0.27.1, 2026-10-08) re-parses with Tree::edit plus the old tree, and its wasm feature adds WasmStore to load grammars compiled to WASM through wasmtime (docs.rs; high). Zed re-parses synchronously for up to 1 ms on the main thread and finishes on a background thread when that runs out (buffer.rs; high). No primary per-keystroke latency numbers were found for either parser (low).

How Zed and Helix ship grammars. A Zed extension names each grammar by repository and rev in extension.toml, puts config.toml and query files per language (highlights, injections, outline, brackets, indents, textobjects, runnables and more), and may provide "any number of language servers" (language extensions; high). Extension grammars are compiled to WASM so a parser cannot crash Zed; only lexing runs in WASM, "the least expensive part", and the rest runs natively (Zed blog; high). Helix declares [[grammar]] sources by git URL and revision, fetches and compiles them with hx --grammar fetch and build, which needs a C/C++ compiler (languages, building from source; high), and replaced its highlighter with the tree-house crate in 25.07, fixing bugs such as locals not highlighting when their definition is off-screen (changelog; high). tree-house 0.4.0 is MPL-2.0 (crates.io; high).

Licences of queries. Zed's built-in languages crate is GPL-3.0-or-later (Cargo.toml; high); Helix is MPL-2.0, nvim-treesitter Apache-2.0 (its queries use Neovim-specific predicates), and grammar repositories such as tree-sitter-rust ship MIT highlights.scm, injections.scm and tags.scm (GitHub API; high).

Language-agnostic therefore means a data-driven registry, not code per language: detection (globs, extensions, first line), grammar and queries (highlights, injections, locals, outline, folds, indents), comment tokens, an ordered list of language servers with feature filters, and a formatter, with plain text plus word completion as the fallback. That is what Helix's languages.toml and Zed's config.toml encode (sources above; high).

3. LSP client in Rust

Crates. lsp-types 0.97.0 dates from 2024-06-04 and is MIT (crates.io; high); Zed and Lapce pin their own forks and Helix vendors helix-lsp-types (Zed Cargo.toml, Lapce Cargo.toml, Helix Cargo.toml; high). async-lsp 0.2.4 (2026-04-24, MIT or Apache-2.0) is built on tower and has a main loop and sockets for "either Language Servers or Language Clients" (docs.rs; high), but pins lsp-types 0.95 (Cargo.toml; high). tower-lsp (0.20, 2023) writes servers, its community fork tower-lsp-server 0.23 moved to gen-lsp-types 0.11, generated from the official metamodel, and rust-analyzer's lsp-server 0.10 is a synchronous server scaffold (crates.io and repositories; high).

Choosing servers. Helix lists language-servers per language; each feature goes to the first server in the list that supports it, except diagnostics, code actions, completion and document and workspace symbols, which merge across all; only-features and except-features restrict a server; the request timeout defaults to 20 s (languages; high). Helix does not install servers: "install the language servers for your preferred programming languages" (install; high). Zed downloads servers automatically or uses one on the PATH, orders them with a language_servers list where "..." means all others and !name excludes one, and a user list replaces the default (configuring languages; high). Lapce plugins are WASI modules run by wasmtime that download a server (its Rust plugin fetches rust-analyzer from GitHub releases) and call start_lsp (lapce-rust, lapce-proxy Cargo.toml; high).

Round trips. Every request crosses web view to Rust to server and back whichever side speaks LSP, so the hop count is equal; a Rust client wins because it can merge several servers, cut a completion list to the visible top 50 before it crosses IPC (Tauri's fast path is under 8 KiB of JSON, 03-core-architecture.md), and feed symbols to the index (reasoning; medium).

4. Git: git2, gix or the git command line

git2 0.21.0 (2026-05-18) bundles libgit2 1.9.3, made the ssh, https and cred features opt-in, and added experimental SHA-256 (changelog; high). libgit2 is "GPLv2 with linking exception": linking into a combined program is unrestricted, modifying libgit2 itself stays GPL (COPYING; high). On Windows libgit2-sys compiles libgit2 with the cc crate and MSVC and uses WinHTTP for HTTPS, so OpenSSL is only needed on Unix (libgit2-sys; high). It covers revwalk, diffs, blame, branches, index conflicts, repository state and, through Repository::apply with a per-hunk callback, hunk staging (ApplyOptions; high); gitui, a terminal Git client on libgit2, stages and reverts hunks and lines (gitui; medium, mechanism not inspected).

Two gaps. libgit2's maintainers will not run hooks: "libgit2 is not going to run external commands" (issue #964, PR #3004; high). And libgit2 1.9 cannot read reftable repositories: Zed merged "Remove git2 (libgit2) dependency" on 2026-06-02 because status, branches and diffs "appeared broken" in such repositories, moving everything to the git binary and dropping about 30,000 lines of vendored C (Zed PR #53453; high). libgit2 merged reftable support on its main branch on 2026-05-06 for an unreleased v2.0 (PR #7117, changelog; high). Git plans reftable as the default for new repositories in Git 3; the newest Git is 2.56.0 (BreakingChanges, tags; high).

gix 0.89.0 (2026-10-08, MIT or Apache-2.0) is "usable ... possibly incomplete" (README; high). Done: discovery, rev-walk, tree-tree and index-worktree diffs with renames, blob diffs, commit from tree, clone and fetch, credentials. Not done: checkout, reset, merge, rebase and stash orchestration, adding files with .gitignore handling, push, hooks and writing config (crate-status.md; high); native reftable support is still being built (PR #2965, #3011; medium). Speed: Turborepo found libgit2's status "single-threaded" and 30–70% of turbo run; replacing it with gix-index plus parallel stat cut that step from 397.8 to 296.9 ms (Turborepo #11950; high).

How Zed does it now. Long-lived git cat-file --batch processes read blobs (avoiding one spawn per read), hunk staging writes new index content with hash-object and update-index --cacheinfo, and its commit graph reads git log --format=%H%x00%P%x00%D and lays out lanes itself (repository.rs; high). Push, pull and fetch use the system git since March 2025, so users without Git cannot push from Zed (commit; medium, mirror). No library computes graph lanes; Syrinx builds that either way (medium).

5. Terminal

PTY. portable-pty 0.9.0 (MIT, from WezTerm) gives native_pty_system, CommandBuilder, a master with reader, writer and resize, and a child handle (docs.rs; high). On Windows it needs ConPTY ("Windows 10 October 2018 or newer"), prefers a conpty.dll shipped beside the app over the system one, and opens with the resize-quirk and Win32-input-mode flags (pseudocon.rs; high). WezTerm's last tagged release is from February 2024 but its PTY code was still changing in September 2026 (GitHub API; high).

Web view side. xterm.js 6.0.0 (2025-12-22) added synchronised output (DEC mode 2026), ESM builds and VS Code's scroll bar (release; high); it is 82 KB gzipped, with a separate WebGL renderer addon (bundlephobia, npm; high). It processes "5 - 35 MB/s", discards data past a 50 MB buffer, and its guide recommends HIGH and LOW watermarks with HIGH under about 500 KB for responsive keys (flow control; high). Tauri channels deliver InvokeResponseBody::Raw as an ArrayBuffer, while Channel<Vec<u8>> becomes a JSON array of numbers (Channel; high). A maintainer relayed about 200 ms for 10 MB of binary IPC on Windows, roughly 50 MB/s (discussion #11915; medium). So IPC is not the bottleneck; xterm.js is (reasoning; medium).

Shells. VS Code's source finds Git Bash from git.exe on the PATH (two levels up), then Program Files, Program Files (x86) and %LocalAppData%\Program, trying Git\bin\bash.exe and Git\usr\bin\bash.exe, plus Scoop; it lists WSL distributions with wsl.exe -l -q read as UTF-16LE with a 1 s timeout, skips docker-desktop, and launches wsl.exe -d <name>; Cygwin and MSYS2 at default paths are flagged unsafe (terminalProfiles.ts, profiles docs; high). Windows Terminal generates WSL and PowerShell profiles itself and documents Git Bash as a manual profile (dynamic profiles; high).

6. Run control and debugging

Discovery. Zed reads global and .zed/tasks.json, tasks from language extensions (runnables.scm captures place run buttons), runnable shell scripts and .vscode/tasks.json (tasks, language extensions; high). VS Code auto-detects Gulp, Grunt, Jake and npm only (tasks; high). The project files give the rest without settings: cargo metadata --no-deps --format-version 1 lists each target's kind (bin, example, test, bench) and promises compatibility within a format version (cargo metadata; high); just --dump --dump-format json prints the justfile as JSON and --summary lists recipe names (just README; high); make -qp prints the rule database without building (GNU make; high). Python has no single runner: uv's issue "Using uv run as a task runner" is open with 258 comments, and its users point to Poe the Poet ([tool.poe.tasks]) and taskipy (uv #5903; high). JetBrains stopped distributing Fleet, its language-agnostic editor, on 2025-12-22 because it "did not succeed as a standalone product" against IntelliJ (JetBrains; high).

Debugger. DAP runs an adapter over stdin and stdout (or a socket) with Content-Length framing; the client sends initialize, then launch or attach, waits for initialized, sets breakpoints and sends configurationDone, and the adapter can ask the client to runInTerminal (DAP overview; high). Zed shipped its debugger on 2025-06-18 after "8 months of development, 977 commits, and 25k+ lines of code", noting "over 70 DAP implementations", each with "its own quirks" (Zed blog; high). Rust DAP crates are thin (dap 0.4.1-alpha1 from 2023, dap-types 0.0.1); Zed forks dap-types and Helix wrote helix-dap (crates.io, Cargo manifests; high).

7. Notes storage and parsing

Parsers. pulldown-cmark 0.13.4 (MIT) is a pull parser with "a bare minimum of allocation and copying", has ENABLE_WIKILINKS (added in 0.13.0, February 2025) and YAML or +++ metadata blocks, and into_offset_iter gives each event's byte range (Options, README, releases; high). comrak 0.56.0 (BSD-2-Clause) builds an AST with wikilinks in both title orders, a front matter delimiter passed through unchanged, task lists, footnotes, alerts and math, plus sourcepos (Extension, Render; high). markdown-rs 1.0.0 (MIT) produces mdast with toggleable constructs including frontmatter, GFM, math and MDX, but no wikilinks (Constructs; high). For frontmatter, serde_yaml is deprecated; serde-saphyr 1.3.0 and yaml-rust2 0.13 read YAML, and yaml-edit 0.3.2 calls itself "a lossless parser and editor" (crates.io; high for status, low for yaml-edit's maturity).

Watching. notify 8.2.0 picks the platform watcher; its known problems are editors that save by replacing files, network drives with no events, missed events on very large trees and Linux watch limits (notify; high). notify-debouncer-full 0.7.0 matches rename pairs, can track file IDs on Windows and macOS to stitch renames, and drops duplicate creates and modify-after-create (docs; high). Zed's buffer keeps a has_conflict flag beside its saved modification time for edits on disk while unsaved changes exist (buffer.rs; medium).

A warning from Logseq. Logseq moved to a SQLite "DB version", citing "Poor performance with large graphs", data loss with sync, renames that must update every referencing file, and that creating a block means rewriting the whole file (Logseq; high), and now offers only a read-only Markdown Mirror of DB graphs (Logseq DB changelog; medium). Its pain was multi-device sync and block-level writes, which Syrinx v1 does not have (reasoning; medium).

8. The Notes editor

Obsidian states "Obsidian uses CodeMirror 6 (CM6) to power the Markdown editor", and changing Live Preview means writing a CodeMirror extension (editor extensions; high), whose decorations style, insert widgets, or "hide or replace part of the document" while the text is unchanged (decorations; high). @lezer/markdown 1.8.0 is incremental CommonMark with extensions that define nodes and inline parsers, which is how wikilinks get parsed in the editor (README; high).

The WYSIWYG editors parse markdown into their own document model and write it back. prosemirror-markdown tokenises with markdown-it, its default parser handles "unextended CommonMark, without inline HTML", and its serializer writes nodes back as CommonMark (README; high). TipTap's official @tiptap/markdown (3.31.4, MIT on npm) parses with MarkedJS, is "an early release", may lose comments and allows "one child node per cell" in tables (docs, npm; high); a 3.29.0 fix restored blank lines dropped after headings and tables (release; medium). Milkdown is "a plugin-driven WYSIWYG markdown Editor, inspired by Typora, built on top of prosemirror and remark", MIT (repository; high). So a WYSIWYG model normalises whatever it does not represent (list markers, blank lines, hard wraps, unknown syntax) on save, while a CodeMirror document is the file's bytes (reasoning from the above; high).

9. Wikilinks and backlinks

Obsidian writes wikilinks by default, links headings with # and blocks with #^id (an Obsidian-only syntax), changes display text with |, and can update links automatically when a file is renamed (links; high). Its API resolves a link to "the best match", and generates link text from the file name when that name is unique and the full path otherwise; resolvedLinks and unresolvedLinks count links per source file (MetadataCache; high). Backlinks show linked mentions (real links) and unlinked mentions (plain-text occurrences of the note's name), with excluded files left out (backlinks; high). markdown-oxide, an Apache-2.0 Rust language server for Obsidian-style vaults, provides backlinks, completions and reference counts and is a useful source to read (markdown-oxide; high). The shared index is designed in 03-core-architecture.md: note titles join the per-keystroke catalogue and bodies go to tantivy.

10. Database views over properties

Obsidian Bases is a core plugin for "database-like views of your notes", with table, list, cards, kanban and map views, editable values, and embedding in a code block (Bases; high). It arrived in 1.9.0 early access on 2025-05-21 and in public builds by 1.9.10 on 2025-08-18 (1.9.0, 1.9.10; high). A .base file is YAML with filters (and, or, not, comparisons and functions such as file.hasTag, file.inFolder), formulas, properties, summaries and views (type, name, limit, order, groupBy) (syntax; high). Properties have seven types (text, list, number, checkbox, date, date and time, tags), and one name has one type across the vault (properties; high). Dataview indexes frontmatter, inline key:: value fields, tasks and implicit file fields, queries them with LIST, TABLE, TASK and CALENDAR, and "is not meant to edit your notes" (Dataview; high). Notion adds select, status, person, files, formula, relation, rollup, automatic timestamps, button, ID and place (Notion; high).

11. Rust-native equivalents (short)

Zed's editor, rope, text, terminal, markdown and language crates are GPL-3.0-or-later; only GPUI is Apache-2.0 (Zed manifests; high), and Zed on Windows still has no screen reader support (issue #41138; high). gpui-component 0.7.1 (Apache-2.0), part of Longbridge's GPUI Kit, claims a code editor with "Stable performance at 200K lines with Tree-sitter highlighting and LSP diagnostics, completion, and hover", and renders (but does not edit) Markdown (repository; medium, own claim). Lapce draws with Floem (MIT), whose editor uses lapce-xi-rope, parley and vello; Lapce's last release is v0.4.6 from January 2026 (Floem Cargo.toml, releases; high). Helix is a terminal program whose core uses ropey 1.6.1 and tree-house (MPL-2.0); its last release is 25.07.1 (Cargo.toml; high). Reusable crates: ropey 1.6.1 or crop 0.4.3 for text, cosmic-text 0.19 or parley 0.12 for shaping and layout, tree-sitter, and alacritty_terminal 0.26 (Apache-2.0, used by Zed and Lapce), which models the grid and runs the PTY loop but leaves drawing to the caller (docs.rs; high). No mature rich-text markdown editor exists in Rust; writ on GPUI is a small project (docs.rs; low). Syrinx would build selection, IME, accessibility, live preview and the diff view itself.

What this means for Syrinx v1

Forge

Build first, in this order.

  1. Buffer and highlighting. A Rust document actor per open file (ropey, Tree-sitter tree, version number) and a CodeMirror 6 view that applies keystrokes locally and sends versioned change sets to Rust without waiting. Rust re-parses, runs highlight queries over Tree::changed_ranges intersected with the viewport plus margin, and returns span diffs under 8 KiB; CodeMirror maps old decorations through edits so colours never jump. Grammars for about 20 languages are compiled in (Rust, Python, TypeScript and JavaScript, TSX, JSON, TOML, YAML, Markdown, HTML, CSS, Bash, PowerShell, SQL, Go, C and C++, Java, C#, Lua), listed in a languages.toml modelled on Helix's and overridable per project.
  2. Terminal: portable-pty, one reader thread per PTY, output coalesced to one raw ArrayBuffer per frame, xterm.js with the WebGL addon, watermarks at about 256 KB and 64 KB, and VS Code's shell-detection algorithm.
  3. Git reads in process behind a GitRead trait (status, diff gutter, log, blame, conflict state; git2 now, gix where it is measurably faster), with lanes for the graph computed in Rust from parent lists. Git writes through git (commit, stage hunk via hash-object and update-index, checkout, branch, merge, push, fetch), with long-lived cat-file --batch processes. Diffs in @codemirror/merge.
  4. LSP: one server per language from PATH or settings, then Helix-style ordered lists with feature filters; Rust merges, filters and trims results.
  5. Run control from Cargo, npm, just, make and pyproject readers, launched in a terminal tab; then split groups (one CodeMirror view per pane, synced per the official example) and the context rail.

Defer: the debugger (DAP), automatic server installs, WASM grammars loaded at runtime, interactive rebase, a three-way merge editor, a minimap and semantic tokens.

Risks. Keystroke-to-highlight latency over WebView2 is unmeasured; if p95 exceeds one 16 ms frame, highlight in the web view with web-tree-sitter (same grammars and queries) and keep Rust's tree for the index. CodeMirror and Rust copies of a document can diverge, so every change carries a version and a periodic checksum. Grammars need MSVC at build time. Query files must come from MIT, Apache or MPL sources. Test Japanese and Chinese IME in WebView2 before shipping. Using git assumes Git for Windows is installed.

Notes

Build first. A vault is a folder of .md files. A Rust indexer (pulldown-cmark plus serde-saphyr) fills SQLite tables for notes, headings, links (source, target text, resolved path, byte range) and properties, feeding both search tiers from 03. The editor is CodeMirror 6 with @lezer/markdown, a wikilink extension and live-preview decorations that reveal syntax near the cursor, all in the web view because they must follow the cursor without an IPC hop; the outline panel reads CodeMirror's syntax tree for the open note and Rust's table for others. Then backlinks (linked from the table, unlinked mentions from tantivy), renames that patch every link's byte range after a preview with undo, outside-edit handling (debounced watcher, content hash to ignore Syrinx's own writes, silent reload when clean, a conflict banner when dirty), and one table view.

The v1 table view: one saved view per file, over a folder or tag; columns are frontmatter keys typed by a vault-wide property type (Obsidian's seven); sort by one column; filters of the form property, operator, value joined by AND; inline edits written back as byte-range patches to the frontmatter. Storing it as a subset of Obsidian's .base YAML (filters, order, one table view) costs little and keeps vaults portable. Defer formulas, summaries, relations and rollups, cards, kanban and calendar views, block references, embeds and any WYSIWYG mode.

Risks. The editor's Lezer parser and Rust's pulldown-cmark can disagree on edge cases; only Rust's result feeds data, so a mismatch affects display, not links. Resolving duplicate file names needs a defined rule (unique name, else nearest path) and a test corpus. A first index of a large vault must be incremental (modification time plus hash cache) or Logseq's performance complaint returns.

Performance budgets to measure (proposed, not from sources)

Keystroke to painted character under 16 ms; keystroke to updated highlight p95 under 16 ms on Windows; opening a 10 MB file under 300 ms (one raw transfer, about 200 ms at Windows' reported rate); 60 fps scrolling on a million-line file; cat of 100 MB still answering Ctrl C; completion popup within one frame of the server's reply; a Forge tab's script loaded lazily and interactive within 300 ms.

Open questions for the owner

  1. Git: may Forge use the git command line for writes and network operations (with hooks and the user's credential helpers), keeping libgit2 or gix only for reads? This departs from the spec's "use libgit2".
  2. May Forge require Git for Windows to be installed, as Zed does for push and pull, or should Syrinx bundle a git?
  3. Language servers: v1 uses only servers on the PATH or configured paths; should automatic downloads (Zed's model, with network access and updates) come later, or never?
  4. Debugger: confirm it is out of v1.
  5. Notes compatibility: should a vault stay usable in Obsidian (wikilinks by default, its property types, a .base subset), or is Syrinx free to diverge?
  6. Which scripts must IME support cover (Chinese, Japanese, Korean)? That sets the test matrix.
  7. Which Python task runners do your projects use (Poe the Poet, PDM, Hatch, plain [project.scripts])?
  8. What licence will Syrinx carry? It decides whether MPL-2.0 Helix queries are acceptable and confirms that GPL code from Zed stays out.

Sources

Editors: CodeMirror guide · home · million lines · split view · changelog · reference · language.ts · editorview.ts · lsp-client · lsp-client announcement · language-data · @codemirror/merge · Monaco README · changelog · textModel.ts · editorOptions.ts · standaloneEditor.ts · standaloneLanguages.ts · VS Code 1.15 · text buffer blog · monaco-languageclient · bundlephobia codemirror, monaco-editor, xterm

Syntax: Lezer · Lezer post · Tree-sitter · tree-sitter crate · nvim-treesitter languages · Zed language extensions, blog, buffer.rs, languages Cargo.toml · Helix languages, building, changelog · tree-house

LSP: lsp-types · async-lsp and Cargo.toml · tower-lsp · tower-lsp-server · gen-lsp-types · lsp-server · Zed languages config · Zed Cargo.toml · Helix install · Helix Cargo.toml · Lapce Cargo.toml, proxy, lapce-rust

Git: git2 changelog · libgit2-sys build.rs · ApplyOptions · libgit2 COPYING, changelog, #964, #3004, #7117 · Zed #53453 · Zed repository.rs · Zed system git commit · Git BreakingChanges · gitoxide README, crate-status, #2965 · Turborepo #11950 · gitui

Terminal: portable-pty · pseudocon.rs · xterm.js 6.0.0 · flow control · Tauri Channel · calling the frontend · Tauri #11915 · terminalProfiles.ts · VS Code profiles · Windows Terminal dynamic profiles

Run and debug: Zed tasks · VS Code tasks · cargo metadata · just · GNU make options · uv #5903 · Fleet · DAP overview · Zed debugger docs · Zed debugger blog

Notes: pulldown-cmark Options, README, releases · comrak Extension, Render · markdown-rs · notify · notify-debouncer-full · YAML crates on crates.io (serde_yaml, serde-saphyr, yaml-edit) · Logseq why, May 2026 · Obsidian editor extensions, decorations, links, backlinks, MetadataCache, Bases, Bases syntax, properties, 1.9.0, 1.9.10 · @lezer/markdown · prosemirror-markdown · TipTap markdown, npm, 3.29.0 · Milkdown · markdown-oxide · Dataview · Notion properties

Rust-native: Zed crates · Zed #41138 · gpui-component · Floem Cargo.toml · Lapce releases · alacritty_terminal · writ · versions and licences of ropey, crop, cosmic-text, parley, gpui, termwiz and vt100 from the crates.io API

Clone this wiki locally