DEV Community

Rizwan Saleem
Rizwan Saleem

Posted on

Building a Developer-Centric Notepad: A Practical Guide to Local-first Documentation with Timestamps

Building a Developer-Centric Notepad: A Practical Guide to Local-first Documentation with Timestamps

Building a Developer-Centric Notepad: A Practical Guide to Local-first Documentation with Timestamps and Diffable Edits

Documentation often lives in walled gardens like wiki pages or scattered Markdown files. What if you could ship a local-first, diffable documentation system that developers can use offline, sync selectively, and review changes with precise timestamps and simple diffs? In this tutorial, you’ll build a lightweight, local-first notepad for code projects that embraces offline work, per-edit provenance, and fast collaboration with minimal cognitive overhead.

Overview

  • What you’ll build: A local-first documentation tool that stores notes as JSON files, tracks edits with timestamps, and generates human- and machine-readable diffs.
  • Key concepts: local-first storage, delta diffs, per-note provenance, offline-first workflow, simple sync strategy.
  • Tech stack (example): Node.js for tooling, JSON or Markdown for notes, a tiny CLI, and optional Git for synchronization.

Why this approach

  • Offline capability: Developers can write and review docs without network access.
  • Provenance: Each edit is timestamped, so you can see how a doc evolved.
  • Diffability: Lightweight diffs make reviews fast and non-disruptive.
  • Minimal surface area: Small, transparent stack reduces complexity and debugging friction.

Prerequisites

  • Node.js 18+ installed
  • Basic familiarity with the command line
  • A code project you want to document (optional, you can practice with a sample project)
  • Git (optional, for synchronization)

Part 1: Design the data model

  • Note: Each note is a JSON object with fields: id, title, content, createdAt, updatedAt, author, and a history array of edits.
  • History entry: { at: timestamp, editor: string, diff: string, noteVersion: number }
  • Diff format: Use a simple line-based patch (unified diff style) or a minimal JSON patch representation. For simplicity, we’ll implement a small JSON patch-like structure in code.

Data model example
{
"id": "note-101",
"title": "API Endpoint Documentation",
"content": "GET /api/users returns a list of users...",
"createdAt": "2026-05-31T08:15:00Z",
"updatedAt": "2026-05-31T08:15:00Z",
"author": "alice",
"history": [
{
"at": "2026-05-31T08:15:00Z",
"editor": "alice",
"diff": "",
"noteVersion": 1
}
]
}

  • Storage layout: A folder named docs/ contains note-*.json files. A meta.json keeps a registry of all notes and a simple index.

Part 2: Create a small CLI tool
What it does:

  • create: add a new note with title and initial content
  • read: display a note nicely in the terminal
  • update: apply edits to content, generate a diff, update history
  • list: show all notes
  • diff: display the last diff for a note
  • export: export all notes as MD or JSON
  • import: import notes from a JSON export (for syncing)
  • status: show what changed since last sync (if you use Git, this can be a hint)

Project scaffold

  • package.json with scripts
  • src/cli.js (entry point)
  • src/storage.js (load/save notes)
  • src/diff.js (compute textual diffs)
  • src/exporter.js (export/import)

Code sketches (Node.js)

  • Simple utilities to read/write JSON safely, generate timestamps, and compute diffs.

Note: The following snippets are concise illustrations. Expand them as you implement.

1) Storage helpers
const fs = require('fs');
const path = require('path');

const DOCS_DIR = path.resolve(__dirname, '../docs');
const META_PATH = path.resolve(DOCS_DIR, 'meta.json');

function ensureDocsDir() {
if (!fs.existsSync(DOCS_DIR)) fs.mkdirSync(DOCS_DIR, { recursive: true });
}
function readJSON(p) {
if (!fs.existsSync(p)) return null;
const data = fs.readFileSync(p, 'utf8');
return JSON.parse(data);
}
function writeJSON(p, obj) {
fs.writeFileSync(p, JSON.stringify(obj, null, 2), 'utf8');
}

function loadMeta() {
ensureDocsDir();
if (!fs.existsSync(META_PATH)) {
writeJSON(META_PATH, { notes: [] });
}
return readJSON(META_PATH);
}
function saveMeta(meta) {
writeJSON(META_PATH, meta);
}

function notePath(id) {
return path.resolve(DOCS_DIR, ${id}.json);
}
function loadNote(id) {
return readJSON(notePath(id));
}
function saveNote(note) {
writeJSON(notePath(note.id), note);
}

module.exports = { ensureDocsDir, loadMeta, saveMeta, loadNote, saveNote, notePath };

2) Diff generation (very simple)
function simpleDiff(oldText, newText) {
const oldLines = oldText.split('\n');
const newLines = newText.split('\n');
const diffs = [];
const max = Math.max(oldLines.length, newLines.length);
for (let i = 0; i < max; i++) {
const o = oldLines[i] ?? '';
const n = newLines[i] ?? '';
if (o !== n) {
diffs.push({ line: i + 1, from: o, to: n });
}
}
return diffs;
}

3) CLI flow (high level)

  • create: prompt for title and author, content from stdin or an editor
  • update: load note, apply patch function, generate diff via simpleDiff
  • diff: show last diff from history

Note: For editing content, you can spawn an editor (like via $EDITOR) or read from stdin for simple use.

Part 3: Implement a minimal workflow

  • Create a new note
  • Update content and record a diff
  • Read and diff a note
  • Export/import

Example commands (illustrative):

  • node bin/cli.js create title "Dev Server Docs" author "alice" content "Initial doc content"
  • node bin/cli.js update id note-101 content "Updated content here"
  • node bin/cli.js diff id note-101
  • node bin/cli.js list
  • node bin/cli.js export format md
  • node bin/cli.js import file docs-export.json

Part 4: Offline-first workflow patterns

  • Commit-centric reviews: Treat each update as a small commit in the note’s history; compare with previous version.
  • Per-note diffs: Always store a diff snapshot per change, not just the latest content. This makes audits simpler.
  • Timestamp discipline: Use ISO 8601 UTC timestamps. This avoids confusion across time zones.
  • Lightweight syncing: Use Git or a simple rsync-based sync to share changes. If using Git, treat docs/ as a submodule or a separate repo.

Part 5: Optional enhancements

  • Rich content: Support Markdown in content and render to terminal-friendly view with colorized headings.
  • Rich diffs: Integrate a proper diff library to produce unified diffs for terminal display or HTML rendering.
  • Sync strategy: Implement a simple pull/push mechanism over a REST API or a peer-to-peer sync (e.g., using rsync over SSH or a Git-based flow).
  • Provenance UI: Build a tiny static HTML viewer that shows the note’s evolution with timestamps and authors.

Example: diff rendering in terminal

  • Display last edit diffs with line numbers and color:
    • Added lines in green
    • Removed lines in red
  • Show a compact history timeline: versions with timestamps and editors.

Part 6: Step-by-step implementation plan
1) Set up project and folder structure

  • Create a new Node.js package
  • Create docs/ directory and a meta.json as initial scaffolding

2) Implement storage utilities

  • Read/write JSON notes
  • Load and update meta/index

3) Implement note lifecycle

  • Create a new note with initial content
  • Update: compute diffs against previous content, push a history entry

4) Implement diff rendering

  • Simple line-by-line diff function
  • CLI display for last change and a per-line view

5) Build a minimal CLI

  • Use a tiny argument parser (yargs or commander)
  • Support subcommands: create, read, update, list, diff, export, import

6) Add export/import formats

  • Support JSON export for all notes
  • Optional Markdown export per note

7) Polish and test

  • Create a sample note, perform a few edits, view diffs
  • Test offline edits and re-loads

Illustrative example: end-to-end usage

  • Create a note
    • Title: API Guidelines
    • Content: “All endpoints must be versioned. Use semantic versioning in paths. Document error codes.”
    • createdAt: 2026-05-31T08:20:00Z
  • Update
    • Change: add a section on rate limiting
    • diff shows added lines under a new Section "Rate limits"
  • Read
    • Print content with a simple pretty view
  • Diff
    • Show the last change with added/removed lines

Tips for a practical workflow

  • Start with small notes: document a single concept per note to keep diffs readable.
  • Use stable IDs: derive IDs from titles or a GUID generator to avoid clashes.
  • Use a consistent authoring process: identify who edits each note; keep author field in each history entry.
  • If you already use Git in your project, place docs/ under version control and commit updates frequently to leverage Git’s diffing and history.

Caveats

  • This tutorial outlines a lightweight, local-first approach. For large organizations or complex docs workflows, consider established documentation platforms with built-in diff/merge, access control, and search capabilities.
  • The diff format here is intentionally simple. For advanced needs, integrate or implement a standard like unified diffs or JSON Patch.

What next

  • If you’d like, I can tailor the CLI scaffolding to your preferred tech stack (e.g., TypeScript, Python, or Rust) and provide a ready-to-run starter repository with tests and example notes.
  • I can also add a small web UI that renders notes and diffs in a browser for easier collaboration.

Would you like me to provide a concrete starter repository (with code you can clone and run) in your preferred language, or keep it minimal with a ready-to-run Node.js CLI?

-

Rizwan Saleem | https://rizwansaleem.co

Top comments (0)