A tiny Obsidian-style note app built on the new
deno desktop subcommand (denoland/deno#33441).
Notes are plain Markdown files, you cross-link them with [[wikilinks]], and a
force-directed graph shows how they connect.
It's deliberately small — a single Deno HTTP server plus a vanilla HTML/CSS/JS frontend, no framework and no build step for the UI.
- Notes — create, rename, edit, and delete Markdown notes from the sidebar,
with live search. Everything is stored as
.mdfiles in~/Denidian, so the vault is just a folder of plain text you fully own. - Crosslinking — type
[[Note title]]to link notes. In read mode links render as clickable chips; clicking a link to a note that doesn't exist yet creates it. - Live preview — toggle between a Markdown editor and a rendered view (headings, lists, code, blockquotes, bold/italic, links, and wikilinks).
- Graph view — a force-directed diagram of every note and its
[[links]]. The layout pre-settles instantly, and you can drag nodes, drag the background to pan, scroll (or use the +/−/Reset buttons) to zoom, and click a node to open that note.
deno desktop runs a normal Deno program and points a native window's embedded
webview at a local HTTP server. denidian leans into that:
┌─────────────────────────── deno desktop ───────────────────────────┐
│ │
│ main.ts ── Deno.serve() ──► http://127.0.0.1:<random port> │
│ │ ▲ │
│ │ reads/writes │ webview navigates here │
│ ▼ │ │
│ ~/Denidian/*.md native window (Deno.BrowserWindow)│
│ │ │
│ web/ (index.html, app.js, style) │
└─────────────────────────────────────────────────────────────────────┘
-
main.tsis the whole backend. On startup it ensures the vault directory~/Denidianexists, seeds it with a starter set of notes if it's empty, and (in a desktop build) adopts the startup window viaDeno.BrowserWindowto set the title and a 2000×1000 size. ThenDeno.serve()handles everything:GET /and other paths → static files fromweb/.GET /api/notes→ list of{ name, links }for every note (links are parsed from[[wikilinks]]server-side).GET /api/notes/:name→{ name, content }for one note.PUT /api/notes/:name→ save a note's Markdown.DELETE /api/notes/:name→ delete a note.
In a
deno desktopbuild the port is chosen by the runtime (via theDENO_SERVE_ADDRESSenv var) and the webview navigates to it automatically — there's no port to manage. -
web/is the frontend, served as plain static files:index.html— the shell (sidebar, editor/preview panes, graph view).style.css— a dark Obsidian-like theme.app.js— all the client logic: fetches the notes API, renders Markdown + wikilinks, autosaves edits (debounced), and draws the SVG graph. The graph runs a small force simulation (repulsion + springs + centering) for ~400 steps up front so it appears settled immediately, then only re-simulates while you drag.
Because it's just Deno.serve(), the exact same code runs in a normal browser
for development — handy for iterating on the UI without rebuilding.
Notes live in ~/Denidian as one .md file per note (the note title is the
filename). Edit them with denidian, any text editor, or sync them however you
like — there's no database or lock-in. Delete the folder to reset; denidian
re-seeds a starter vault on next launch.
The starter vault is JS/Web-dev themed: one dense "JavaScript core" cluster of 45 notes surrounded by five satellite clusters (Frameworks, Build Tools, TypeScript, CSS & Styling, Testing) so the graph view has interesting structure out of the box.
deno task dev # desktop window with hot reload
deno task start # desktop window
deno task build # produce ./dist/Denidian.app
deno run -A main.ts # plain browser dev at http://localhost:8000The app needs read/write/env permissions (for the vault and $HOME), which the
tasks grant with -A. The UI assets in web/ are embedded into the compiled
binary via --include ./web.
Prebuilt downloads are on the releases page:
| Platform | Download |
|---|---|
| macOS (Apple) | denidian-aarch64-apple-darwin.dmg |
| macOS (Intel) | denidian-x86_64-apple-darwin.dmg |
| Linux (x86_64) | denidian-x86_64-unknown-linux-gnu.AppImage |
| Windows (x86_64) | denidian-x86_64-pc-windows-msvc.msi |
deno desktop cross-compiles for every platform from a single machine — it
downloads the matching prebuilt runtime and UI backend, so no per-platform
toolchain is needed. The output format is chosen by the file extension.
# Each target downloads its backend on first build.
deno desktop -A --include ./web --backend webview \
--target aarch64-apple-darwin --output dist/denidian-aarch64-apple-darwin.dmg main.ts
deno desktop -A --include ./web --backend webview \
--target x86_64-apple-darwin --output dist/denidian-x86_64-apple-darwin.dmg main.ts
deno desktop -A --include ./web --backend webview \
--target x86_64-unknown-linux-gnu --output dist/denidian-x86_64-unknown-linux-gnu.AppImage main.tsSupported targets: aarch64-apple-darwin, x86_64-apple-darwin,
x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu,
x86_64-pc-windows-msvc. Build them all at once with --all-targets.
deno desktop emits a Windows app directory, not an installer. denidian wraps
that directory into a proper .msi with msitools
(wixl), which works on macOS/Linux too — so the installer can be cross-built
from the same machine. The MSI installs to Program Files\denidian and adds
Start Menu and Desktop shortcuts.
# 1. Build the Windows app directory.
deno desktop -A --include ./web --backend webview --icon ./icons/app.ico \
--target x86_64-pc-windows-msvc --output dist/win/denidian main.ts
# 2. Wrap it into an MSI (brew install msitools / apt-get install wixl).
(cd dist/win/denidian && \
wixl -a x64 -o ../../release/denidian-x86_64-pc-windows-msvc.msi \
-I . ../../../installer/windows.wxs)The WiX source lives in installer/windows.wxs.
--backend webview uses the operating system's built-in webview (WKWebView on
macOS, WebView2 on Windows, WebKitGTK on Linux), which keeps the download small
— denidian is ~30 MB per platform. The alternative, --backend cef, bundles
Chromium for pixel-identical rendering everywhere, at the cost of size (~150 MB+
on macOS, ~375 MB as a Linux AppImage). For an app this simple, webview is the
better trade, and it's the default in deno.json and the tasks above.
Output formats are picked from the extension: macOS .app/.dmg, Linux
.AppImage (or a plain directory), Windows directory (wrapped into an .msi
above). The macOS .dmg is produced with hdiutil, so it must be built on a
macOS host; everything else (including the .msi) cross-compiles from anywhere.
denidian/
├── main.ts # Deno server: static UI + notes API + vault + window
├── deno.json # desktop config (name, icon, backend) and tasks
├── web/
│ ├── index.html # app shell
│ ├── style.css # dark theme
│ └── app.js # frontend: Markdown, wikilinks, graph
└── icons/
├── icon.svg # source icon (a little node-graph glyph)
├── app.icns # macOS app icon
├── app.ico # Windows app icon
└── app-512.png # Linux app icon

