Most of what I open in a day is not code. It is prompts, specs, plans, agent output, knowledge files, the occasional config. Markdown everywhere, half of it written by a machine, and all of it needs a quick look or a quick edit before I move on.
Nothing I tried fits. Notepad++ is not my taste. Sublime is fast, but closing a pile of scratch tabs means a save dialog for every one of them. VS Code drags an IDE's worth of noise into a single markdown file. Obsidian is for people whose life is in their vault. Typora got one thing right: it treats a document the way a browser treats a page. You open it to read it, not to manage it.
Writ extends that to how I work: resident and summoned, not launched. One hotkey and the window is there, holding everything I dumped into it before. Buffers persist on their own, search reaches all of them, and files render in place, offline. Read, change, dismiss.
- Global hotkey summons the window from anywhere:
Cmd+Shift+Spaceon macOS,Ctrl+Shift+Spaceon Windows and Linux - Autosave on every keystroke; buffers persist across restarts; crash recovery restores the last session
- Search everywhere on
Cmd+Shift+F: commands, settings, and every buffer, open or from history, in one palette. With a workspace folder open it adds file names, and greps file contents on each query. Prefixes:>commands,#content,:go to line - Split-pane live preview: Markdown, HTML, Mermaid diagrams, and KaTeX math, rendered fully offline with scroll sync
- Command palette on double-tap
Shift - CodeMirror 6 editor with language auto-detection, live Markdown typography, and formatting shortcuts
Cmd+click(Ctrlelsewhere) openshttp,https, andmailtolinks from the editor, underlined only while the modifier is held; a relative link like[spec](./notes/spec.md)opens in Writ when it resolves inside the workspace. A link clicked in the preview names its destination host and asks before opening- Right-click gets Writ's own menu rather than the webview's, everywhere but the preview pane: spelling corrections, link actions, clipboard, rewrite actions on a selection, and a workspace search seeded with it
- Spell check runs locally, so nothing leaves the machine. Double-click a flagged word for corrections in place: accept one, fix all, or add the word to your dictionary. Code, URLs, and tokens like
APIoruseSignalstay unflagged. Off by default - Rewrite a selection: proofread, rephrase, polish, improve prompt (instructed to leave
{{placeholders}}intact), or your own instruction. A local model (Ollama) by default, or any OpenAI-compatible endpoint with your own key, kept in the OS keychain on macOS and Windows and in memory for the session elsewhere. Off until you turn it on; Writ asks before the first send to a host off your machine, and only the text you rewrite is sent - Prompt fill: placeholder variables, a live token estimate, copy as prompt
- Text transforms such as Tidy Whitespace, built from small composable passes
- Workspace folders with a file tree, plus a watched inbox that opens new files as they arrive
writCLI for opening files from the terminal; register Writ as the default app for text, config, and source files on macOS- Browser-style tabs with reopen-closed, light and dark themes, editor and preview font zoom
- Local-only storage, no account, no telemetry; self-updates verify a signed manifest and can be turned off
Each of these is recorded in docs/adr/; the short version:
- Text on disk, metadata in SQLite. Each buffer's text is a plain file under
~/.writ/buffers/;writ.dbcarries the metadata and the full-text index. That split is what makes autosave-per-keystroke, restart persistence, and instant full-text search possible. Files you open still save back to their own path; the buffers directory is the scratch layer where most text starts and much of it ends. - Resident, not launched. The app starts hidden and keeps running in the background, so the hotkey shows a window instead of booting a program. Cold start time stops mattering because it happens once.
- Keyboard first. Every command, setting, and buffer is reachable from the palette. The mouse is optional.
- The preview trusts nothing. Markdown, HTML, Mermaid, and KaTeX render from runtimes bundled into the app, and the preview blocks all network access.
- The core does not know Tauri exists.
writ-core,writ-storage,writ-render, andwrit-pluginare plain Rust crates with no Tauri dependency; the shell is a thin adapter. The boundary is enforced by the build, not by convention. - Built to catch what other tools produce. The CLI, the watched inbox, and default-app registration all serve the same case: something else made a file, and Writ is where it opens, rendered and searchable.
flowchart LR
classDef entry fill:#4f46e5,color:#fff,stroke:none
classDef data fill:#312e81,color:#e0e7ff,stroke:none
classDef crate fill:#eef2ff,color:#1e1b4b,stroke:#c7d2fe
classDef zone fill:none,stroke:#818cf8,stroke-dasharray:3 3
HK([global hotkey]):::entry
CLI([writ CLI]):::entry
INBOX([watched inbox]):::entry
ASSOC([default app for .md, .log, .toml]):::entry
subgraph FRONT [frontend · SolidJS]
direction LR
UI[components] --> ST[stores] --> SV[services]
end
subgraph SHELL [src-tauri · thin adapter]
direction LR
CMD[IPC commands]
EVT[event emitter]
FSW[file watcher]
end
subgraph CORE [pure Rust · no Tauri imports]
direction LR
WC[writ-core<br>policy]:::crate
WR[writ-render<br>markdown · mermaid · katex]:::crate
WS[writ-storage<br>persistence]:::crate
end
HK & CLI & INBOX & ASSOC --> SHELL
SV -- invoke --> CMD
EVT -. events .-> SV
FSW -. fs changes .-> EVT
CMD --> WC
WC --> WR --> PV[offline preview<br>network blocked]
WC --> WS
WS --> DB[(SQLite<br>WAL + FTS5)]:::data
WS --> FS[(files on disk)]:::data
class FRONT,SHELL,CORE zone
![]() |
![]() |
![]() |
The landing page has a live editor you can try in the browser.
| Action | Shortcut |
|---|---|
| Toggle window | Cmd+Shift+Space |
| New tab | Cmd+T |
| Close tab | Cmd+W |
| Switch tabs | Cmd+[ / Cmd+] |
| Reopen closed tab | Cmd+Shift+T |
| Command palette | Shift+Shift |
| Search everywhere | Cmd+Shift+F |
| Toggle sidebar | Cmd+S |
| Rename tab | Double-click tab |
| Find in document | Cmd+F |
Writ keeps its state in ~/.writ: buffer text as plain files under buffers/, metadata and the search index in writ.db, anything piped into the writ CLI in piped/, plus config.toml and logs/.
brew install --cask ibrahemid/writ/writ # macOS 12 or later
winget install -e --id ibrahemid.Writ # Windows
curl -fsSL https://github.com/ibrahemid/writ/raw/main/install.sh | sh # Linux
yay -S writ-bin # ArchOr grab a .pkg or .dmg (macOS 12 or later), .msi, .AppImage, or .deb from Releases.
Prerequisites: Rust 1.89+, Node.js 20+, pnpm 9+, and the Tauri v2 platform prerequisites for your OS.
git clone https://github.com/ibrahemid/writ.git
cd writ
pnpm install
cargo tauri devFor a release build:
cargo tauri buildThe installer or app bundle is written to src-tauri/target/release/bundle/.
| Layer | Technology |
|---|---|
| Desktop shell | Tauri v2 |
| Frontend | SolidJS + Vite |
| Editor | CodeMirror 6 |
| Storage | Plain files + SQLite (WAL mode, FTS5) |
| Core logic | Rust: writ-core, writ-storage, writ-render, writ-plugin |
See docs/ARCHITECTURE.md for the full system design and docs/adr/ for architecture decision records.
Contributions welcome. See CONTRIBUTING.md for the development workflow, coding conventions, and pull request process. Security issues go through SECURITY.md.
MIT. See LICENSE.


