Browse and edit an Obsidian vault in your browser. No install, no backend —
a static site on GitHub Pages, plus a new-tab Chrome extension.
note opens a real Obsidian vault folder straight from the browser (File System Access
API) and edits it in place — or holds quick Scratchpad notes in the browser when your
vault isn't around.
| Mode | What it does | Where |
|---|---|---|
| Browser | Pick a vault folder; the browser reads & writes it directly. The real vault editor. | Web — Chrome / Edge / Brave |
| Scratchpad | Quick notes saved in this browser. Export as .md, copy into your vault, optionally sync across devices. |
Web + extension |
The deployed site is Browser + Scratchpad only. Server and Dropbox modes from the
original zero-build app still run locally with server.js — see
Local server below.
npm install
npm run dev # → http://localhost:5174| Script | Output |
|---|---|
npm run dev |
dev server |
npm run build |
production web build → dist/ |
npm run build:ext |
Chrome extension build → extension/ |
npm run build:all |
both |
npm run preview |
serve the built dist/ locally |
npm test |
security tests — Markdown renderer + sync logic |
Requires Node.js 20+.
Push to main and .github/workflows/deploy-pages.yml
runs npm ci && npm run build, then deploys dist/. One-time setup: Settings → Pages →
Source = "GitHub Actions".
The build uses base: "./" so asset paths are relative — the site works at a subpath
(<account>.github.io/<repo>/) and at an apex domain. The footer version is baked in from
package.json via Vite define (__APP_VERSION__).
The same React app, built as a Manifest V3 extension that overrides the new tab — every new tab opens straight onto the Scratchpad.
npm run build:ext # → extension/Load it: chrome://extensions → enable Developer mode → Load unpacked → select
the repo's extension/ folder.
- New tab opens the Scratchpad; notes live in the extension's own storage.
- Editing a local vault hands off to the web app — the File
System Access picker is unreliable from
chrome-extension://pages. - Chromium only (Chrome / Edge / Brave). Same XSS-safe bundle as the web app; the only
permission is
storage.
Scratchpad notes stay in this browser by default. Optional sync keeps them on every device — no backend, just a store you control. Open Sync (the ⟳ icon in Scratchpad mode).
- GitHub Gist (web + extension) — notes sync to a private Gist on your GitHub account.
Both token types work, as long as the token has Gists access:
- Fine-grained (
github_pat_…) → Account permissions → Gists → Read and write - Classic (
ghp_…) → thegistscope
- Fine-grained (
- Chrome account sync (extension only) —
chrome.storage.sync, zero config, ~100 KB cap.
Reconciliation: last-write-wins per note by edit time; deletions propagate via tombstones
(pruned after 30 days). It pulls on open/reconnect and pushes ~3 s after an edit, skipping the
push when nothing changed. The token is yours, stored only in this browser's localStorage,
and used solely to call api.github.com — revoke it any time (the app forgets it on
Disconnect or Clear data).
Known LWW limitation: a delete on one device can be transiently undone by a peer that hasn't synced it yet; it self-heals once every device pulls the tombstone.
- Split view by default — Markdown + preview side by side (toggle via topbar or
⌘K). - Keyboard-navigable tree — ↑/↓ move focus, →/← expand or collapse, Enter opens.
- Drag to move files or folders; F2 / double-click to rename; a folder's ✕ removes it recursively. Moving a folder re-keys every note inside it.
- Auto-save (~1 s idle) and Ctrl+S.
- Image paste (Browser mode) writes to
attachments/and inserts the link. - Obsidian wikilinks (
[[Note]],[[Note|Alias]]) and tables render in preview.
Turn any note into a presentation with reveal.js — no export, no extra files.
- Slides view (the 4th editor mode, or
⌘K→ "View: slides"): edit on the left, a live slide preview on the right. - Fullscreen present (the ⤢ button, or
Alt+P): a kiosk overlay;Escexits. - Slide breaks: a line that is exactly
---starts a new slide;--starts a vertical sub-slide. Breaks inside a fenced code block are literal. A leading Obsidian YAML front-matter block (---…---) is stripped.
The slides are rendered by the same XSS-safe Markdown engine as the preview, so nothing in a note can execute inside a deck. reveal.js loads on demand (a separate ~31 KB chunk) only when you enter Slides / Present, so it never slows the editor's first paint.
- XSS-safe preview. The renderer escapes all HTML and sanitises links/images through a
scheme allowlist (
http,https,mailto,tel,#) that strips control chars first — so[x](java<TAB>script:alert(1))anddata:images are neutralised. Covered bynpm test. - Strict CSP via a
<meta>tag (script-src 'self',connect-src 'self'; the extension addshttps://api.github.com). Even a future renderer regression can't execute injected scripts on a static host. - Sync tokens never leave the device except the one authenticated call to GitHub; the
strict
script-srcmeans an injected script can't exfiltrate them.chrome.storage.synccarries no token. - Clear data (footer) wipes the Scratchpad, saved vault link, theme, tokens, and session data — use it on shared machines.
- Hidden from the tree:
.obsidian,.git,node_modules,.trash,.DS_Store.
React 19 + Vite + Tailwind v4 + @alansynn/design
("Motus"), light theme by default (electric-violet #7c5cf6 primary). One src/ builds to
both dist/ (web) and extension/.
src/lib/markdown.mjs— pure, DOM-free Markdown renderer +safeHrefallowlist (the security boundary).src/lib/stores.mjs— storage backends: Scratchpad (localStorage), Browser (File System Access), the IndexedDB handle store.src/lib/sync-merge.mjs— pure merge logic (LWW + tombstones), unit-tested in isolation.src/lib/sync.mjs— the two sync transports + thesyncNoworchestrator.src/lib/{paths,env}.mjs— path helpers; runtime flags (IS_EXTENSION,HOSTED_APP_URL).src/vault.jsx— vault provider: tree, open file, autosave, hotkeys, sync wiring.src/components/*— Sidebar, FileTree, EditorPane, Topbar, Statusbar, VaultDialog, CommandPalette.index.html— Vite entry + the CSP<meta>.vite.config.ext.mjs+app-static-ext/— extension build (separate config + MV3 manifest).
The original zero-build app still lives in
public/as a legacy fallback; the Vite build never touches it (app-static/is the React app'spublicDir).
Server & Dropbox modes — local only, not deployed
The bundled server.js reads/writes a vault folder on disk:
node server.js --vault /path/to/your/vault # or: OBSIDIAN_VAULT=/path/to/vault npm startOpen http://127.0.0.1:5173. Without --vault/OBSIDIAN_VAULT it serves the app in Browser
mode (you pick the folder in the UI).
Dropbox mode needs server-side OAuth env vars — DROPBOX_APP_KEY, DROPBOX_APP_SECRET,
DROPBOX_REDIRECT_URI — and proxies file ops through /api/dropbox/files/*. Neither mode runs
on the static deploy (no backend; Dropbox is hidden and CSP-blocked).
Branch from main (features/…), open a focused PR. Update this README when behavior
changes, bump the version on every commit, and never commit personal vault content or
secrets. Force-push only to your own branch (--force-with-lease, never main).
Not specified.
