projects

Noted

2025·live·github

A local-first note-taking app for developers. Everything stores as plain .md files in a folder you control. No accounts, no sync lock-in, no subscription.


The problem

Every note app I tried had the same issue — it optimises for features at the cost of clarity. By the time you've organised your notes, you've forgotten what you came to write.

The best tool is the one that gets out of your way.

Noted is deliberately minimal. One folder of markdown files. Instant search. That's the product.


What it does

Core features:

  • Local markdown files as the source of truth
  • Instant fuzzy full-text search via Fuse.js
  • Live preview with syntax highlighting
  • File watcher — changes sync instantly from any external editor
  • Keyboard-first: every action has a shortcut

Non-features (intentional):

  • No built-in sync (use iCloud, Dropbox, or Git)
  • No formatting toolbar
  • No tags, folders-in-folders, or wiki links
  • No mobile app

Technical decisions

Why Electron

Electron gets criticism for bundle size, but for a local-first app that needs filesystem access, it is the right call. The alternatives (Tauri, native) add complexity without meaningful benefit for this use case.

src/main/watcher.ts
import chokidar from 'chokidar';
import { BrowserWindow } from 'electron';
 
export function watchDirectory(dir: string, win: BrowserWindow) {
  const watcher = chokidar.watch(dir, {
    ignored: /(^|[\/\\])\../,
    persistent: true,
  });
 
  watcher.on('change', (filePath) => {
    win.webContents.send('file-changed', filePath);
  });
 
  return watcher;
}

Search

Fuzzy search runs entirely in-process with Fuse.js. For a typical notes folder (< 5,000 files), results are instant:

import Fuse from 'fuse.js';
 
const fuse = new Fuse(notes, {
  keys: ['title', 'content'],
  threshold: 0.3,
  includeMatches: true,
});
 
const results = fuse.search(query);

Stack

LayerTechnologyWhy
ShellElectron 28Filesystem access
UIReact 18 + TypeScriptComponent model
EditorCodeMirror 6Extensible, performant
SearchFuse.jsZero-dependency fuzzy search
WatcherchokidarReliable cross-platform FS events
BuildVite + electron-builderFast dev loop

Performance

Some benchmarks on a MacBook Air M2:

  1. Cold start → 420ms to interactive
  2. Search → < 5ms for 1,000 notes
  3. File sync → < 50ms from disk write to UI update
  4. Memory → ~90MB idle

The installed size is 4.8MB. For comparison, Notion's desktop app is ~280MB.


What I learned

Building Noted taught me that constraints are generative. Every time I removed a feature, the app got clearer. The hardest decisions were not technical — they were about what not to build.

The chokidar watcher has a subtle bug on macOS when folders are moved via the Finder. The fix is dirty:

// Restart the watcher when the directory is moved
watcher.on('error', () => {
  setTimeout(() => watchDirectory(dir, win), 500);
});

Not elegant, but it works.