A fast, lightweight, read-only Markdown viewer.
markdown go markdown-reader markdown-viewer desktop-app
Go 31%
CSS 19%
Svelte 13%
TypeScript 8%
Python 7%
Shell 7%
NSIS 6%
JavaScript 4%
3%
Just 2%
HTML <1%

README.md

Sheaf #

Sheaf renders a Markdown file into a clean, instant, read-only window. Point it at a README before you clone, put it beside your editor while you write, or browse through notes without touching them. Either way you get a calm, typographically comfortable document with no editing chrome, no toolbars, and no buttons competing with your prose.

Why I built this #

I kept bouncing between a browser tab, a code editor's cramped preview, and a full-featured Markdown editor just to read documents. Each one spent screen space and attention on tools for writing, even when I only wanted to read.

I needed a dedicated, trustworthy surface I could open as easily as I bind a key. You bind Sheaf to .md files system-wide, you get a viewer that starts fast, stays out of the way, and never opens a document for editing. Every feature decision flows from that.

Usage #

sheaf README.md        # open a file directly
sheaf                  # launch, then Open (or Ctrl+O)
wl-paste | sheaf -     # render Markdown piped on stdin

Supported extensions:

  • .md
  • .markdown
  • .mdown
  • .mkdn
  • .mkd
  • .mdwn
  • .mdtxt
  • .mdtext
  • .markdn

Features #

  • GFM-style Markdown: tables, fenced code blocks, strikethrough, autolinks, footnotes, heading anchors, smart punctuation
  • Syntax highlighting for fenced code blocks, tokenized on the Go backend in the Digital Rust theme; languages without a recognized lexer render as plain fenced blocks
  • Relative images and inbound Markdown links (README → CHANGELOG, for example) resolve against the document's directory, so screenshots and sibling pages render and navigate in place; they are served read-only from that directory only, with symlink and .. escape checks (test-enforced)
  • External links open in your system browser
  • In-document anchor links scroll to the section you request
  • Live reload: the view refreshes when the file changes on disk (800 ms polling) and keeps your scroll position, so you never lose your place mid-edit
  • Find in page (Ctrl+F or /), step through hits with Enter or Shift+Enter, close with Esc
  • Copy the rendered HTML to the clipboard (Copy HTML button)
  • Print or save as PDF (Ctrl+P or the Print button)
  • Table of contents panel (T or F6) built from the document's headings
  • Recent files menu holds the last eight documents, cycle with Ctrl+Tab or Ctrl+Shift+Tab
  • Type zoom (reading size): Ctrl+= / Ctrl+-, Ctrl+0 returns to your system font size
  • Single instance: opening a second file raises the existing window and switches to the new file
  • Drag-and-drop a file onto the window to open it
  • Dark and light themes follow your system preference

Read-only by design #

Being read-only is the point, not a gap. There's no way to accidentally modify an untrusted document, and nothing to compete with your attention.

Sheaf is deliberate about being safe to open anything. Raw HTML in a document is stripped at render time. Unsafe link schemes such as javascript: are neutralized. Every external link opens with rel="nofollow noreferrer noopener". These rules are enforced by automated tests on every build.

If the file you're watching is deleted, the last rendered content stays on screen and the app silently stops watching.

Getting Sheaf #

Prerequisites: Go, Node, pnpm, and the Wails CLI.

wails build                          # produces build/bin/sheaf
wails build -tags webkit2_41         # if your distro ships webkit2gtk-4.1

Linux desktop install #

To install the binary plus the freedesktop integration (a .desktop entry, the app icon, AppStream metadata, and the Markdown MIME association so Sheaf appears in "Open With") — note the .desktop entry is also what sets the titlebar/taskbar icon on Wayland, which can't read the runtime window icon:

just desktop-install                 # install to ~/.local
just dest=/usr/local desktop-install # system-wide, with sudo
just desktop-uninstall               # remove

The canonical app icon is packaging/linux/sheaf.svg. To change the icon, edit this SVG. Then run bash scripts/render-icons.sh. The script regenerates the PNG set, build/appicon.png, and the Windows .ico from the SVG.

AppImage #

Every release ships an x86_64 AppImage, and the newest one always lives at a stable URL:

curl -LO https://katsuricata.tngl.io/sheaf/latest/sheaf-latest-x86_64.AppImage
chmod +x sheaf-latest-x86_64.AppImage

Release AppImages carry zsync update information, so AppImageUpdate (or any compatible updater) can delta-update the file in place — only the changed blocks are downloaded. Like the raw binary, the AppImage dynamically links the host's gtk3/webkit2gtk-4.1 rather than bundling them, so it needs a distro that ships webkit2gtk-4.1.

To build one locally (needs appimagetool and zsync):

just appimage    # -> dist/sheaf-0.0.0-dev-x86_64.AppImage, no update info

Arch Linux #

AUR package files for sheaf-git live under aur/ in this repository. To build and publish them, run:

just aur-init       # clone AUR repo, seed PKGBUILD, build
just aur-build      # rebuild after changes

Releases #

Tagged releases are built by Spindle (.tangled/workflows/release.yml) and published to https://katsuricata.tngl.io/sheaf/: raw binaries (amd64/arm64), tar/zip archives + SHA256SUMS.txt, a source tarball, a prebuilt sheaf-bin pacman package, and an x86_64 AppImage with zsync delta-updates. Releases are cut with annotated tags (git tag -a v1.0.0 -m ... && git push origin v1.0.0). See RELEASE.md for the full pipeline and one-time setup.

Development #

wails dev                            # live-reload dev
wails dev -tags webkit2_41           # required on distros with webkit2gtk-4.1

If Go tooling fails because frontend/dist is missing, run the frontend build once:

cd frontend && pnpm install && pnpm run build

Testing #

go test ./...                        # backend rendering and asset tests
cd frontend && pnpm run check        # Svelte typecheck
just coverage                        # backend tests + the coverage ratchet
just ci                              # everything CI runs: coverage + typecheck

just coverage runs scripts/check-coverage.sh, which enforces a per-file coverage ratchet: every Go file is pinned to a floor in coverage-floors.txt, and the build fails if a file drops below its pin (a covered line regressed) or its statement count changes (the code changed — re-pin after covering the new lines). The floors sit just under 100% on purpose: a handful of lines are not unit-testable (the Wails-bound methods and main() only run inside a live app, and a few defensive error branches are unreachable from real input). Those are documented in .coverignore. So the suite can't quietly rot — add a feature, and you either cover it or the ratchet fails the build.

Stack #

Wails v2 (Go backend, Svelte 5 frontend in your OS webview). Markdown rendering by blackfriday/v2. Syntax highlighting by Chroma.

License #

MutuaL-1.2. See LICENSE.md.

Project home: https://tangled.org/katsuricata.com/Sheaf · Downloads: https://katsuricata.tngl.io/sheaf/