Roadmap Product site

Surfaces

Express middleware v0.3.1

A zero-dependency, dev-only review-notes overlay for Node. Mount one middleware and a floating Red Pen button appears on your pages - leave typed notes, pin them to elements, and they persist to an append-only log in your repo that you are meant to commit.

User manual (PDF)

Red Pen for Express is the web sibling of the WordPress plugin. It works in any Express app, and in any Connect-style app that uses the same middleware signature. It has no database, no build step, and no runtime dependencies - it ships as plain Node and serves its overlay client as a single script. Free, MIT. Dev credit: Lincoln Tracy.

Dev-only by default Two gates, not one. The middleware disables itself when NODE_ENV === 'production', and separately it answers only connections from this machine - localOnly is a real check on the connection's remote address, not a guess from an environment variable. It is a tool for the people building the app, never for its visitors, so it is safe to leave mounted.

How it works

The middleware does two things. First, it watches outgoing HTML responses and injects a small overlay script tag before the closing body tag, so every page you serve gets the Red Pen button without you editing any templates. Second, it mounts a tiny notes API under a URL prefix (default /__redpen) that the overlay talks to. Notes are appended to a log in your project - .redpen/notes.jsonl, one record per line - and a derived .redpen/notes.json snapshot is written beside it for tools that read notes off disk. There is no other moving part.

Install

Red Pen for Express is not published to npm. The source lives on GitHub at github.com/LTracy86/red-pen-express - clone it somewhere near your projects, then install it into an app as a local file: dependency:

# clone once, next to your apps
git clone https://github.com/LTracy86/red-pen-express.git

# from the app you want to annotate - path is relative to the app
npm install ../red-pen-express

That writes a "red-pen-express": "file:../red-pen-express" entry into the consuming project's package.json, which resolves as a live symlink. Because it is a symlink rather than a copied package, pulling a new Red Pen version flows into every app that links it the next time that app restarts - there is no per-app reinstall step. You can also use npm link if you prefer a globally linked package.

Mount it first

Add the middleware right after you create the app, before static file serving, compression, or your routes. Mounting it early ensures the overlay is injected into responses and the notes API is reachable. Both module systems are supported.

CommonJS (require)

const express = require('express');
const redPen = require('red-pen-express');

const app = express();
app.use(redPen({ file: __dirname + '/.redpen/notes.json' }));

// ...your routes...

ES Module (import)

import redPen from 'red-pen-express';

const app = express();
app.use(redPen({ file: here + '/.redpen/notes.json' }));

In an ES Module there is no __dirname by default - derive a directory variable (for example with fileURLToPath(import.meta.url)), or omit file entirely to default to the current working directory.

Commit your notes - this advice reversed in v0.3.0 Older versions of this page said to add .redpen/ to your project's .gitignore. Do the opposite: commit .redpen/notes.jsonl. It is an append-only log, and Red Pen ships a .redpen/.gitattributes marking it merge=union so two people on two branches cannot corrupt each other's notes. A nested .redpen/.gitignore keeps the derived snapshot and the migration backup out of your commits for you. If the old rule is still in your project's .gitignore, the store asks git check-ignore on open and prints the offending file, line and pattern - it does not edit that file, because that file is yours.

Options

OptionDefaultDescription
mount/__redpenURL prefix for the notes API and the overlay script.
file./.redpen/notes.jsonThe snapshot path. The log lives beside it as notes.jsonl and is the real store.
enabledNODE_ENV !== 'production'Master on/off switch for the whole middleware.
injecttrueAuto-inject the overlay into HTML responses. Set to false to add the script tag yourself.
localOnlytrueServe only connections from this machine. Set to false to let someone else on the network use the same overlay - three people around one staging box.
authorfrom gitThe name notes and replies are signed with. Seeded from git config user.name, falling back to the OS username; each browser can override it from the name chip in the panel header.

If you turn off auto-injection, include the overlay manually where you want it:

<script src="/__redpen/widget.js" defer></script>

Using the overlay

Start the app as usual. A red Red Pen button appears in the bottom-right corner. From there you can:

What is new in v0.3.x

v0.3.0 is the release that makes notes shareable, and v0.3.1 is the pass that made three of its headline claims actually true. The short version:

ChangeWhat it means for you
Append-only JSONL store.redpen/notes.jsonl, one record per line, shipped with merge=union. A pretty-printed JSON array corrupts under a real two-branch merge; a log does not. Reading replays the log, newest wins per field.
author on notes and repliesSeeded from git config user.name. No accounts, no user table - just a name on the note, which is what a second person needs.
Three-state lifecycleopen, in_progress, resolved, with Start and Stop in the panel and on the board, amber cards and pins for in-progress, and a tab per state.
A real localhost gatelocalOnly checks the connection's remote address instead of trusting NODE_ENV alone.
statusAt, resolvedAt, typeLabelThe cross-surface contract fields. The Hub force-reverts a surface that does not send statusAt; Express used to be one.
Capability declarationGET {mount}/capabilities tells the Hub what this surface does not implement, so the board can hide a control instead of writing a value that would be ignored.
Git-ignore warning (v0.3.1)If your .gitignore still hides .redpen/, the store says so on open, with the file, line and pattern to change.

Carried over from v0.2.0

Version 0.2.0 was the "notes UX parity" milestone - it brought the overlay's day-to-day note handling in line with the WordPress plugin. Every item below is still in the current release.

FeatureWhat it does
Reply thread + inline formEach note renders its full reply thread plus a toggleable inline reply form, so a note can become a short conversation.
Edit a note after addingLoad a note back into the form to change its body, type, or priority. You can re-pin it to a different element or clear its anchor. The store stamps an editedAt timestamp on field edits.
Status tabsThe old single flat list is replaced by tabs with live counts. Open is the default view. v0.3.0 added the In progress tab between the two.
Error toasts on failed savesA save that fails (network error or a non-2xx response) now surfaces a red toast instead of failing silently. Reads stay quiet.
Persistent add-form prefsThe add form remembers your last-used type and priority across sessions (stored in localStorage), so repeated notes of the same kind are faster to file.
Undo toast on resolveResolving a note shows an Undo toast; clicking it reopens the note. The toast only appears after the server confirms the resolve.

Storage

All notes for an app live under .redpen/ at the project root, in two files that do different jobs:

FileWhat it isCommit it?
notes.jsonlThe store. An append-only log, one JSON record per line - id, body, type, priority, status, url/page, anchor, replies, author, and timestamps. Nothing is edited in place; reading replays the log, newest wins per field.Yes. That is what merge=union is for.
notes.jsonA derived snapshot of the log, written atomically for tools that read notes off disk - the Hub reads exactly this file. A status the Hub writes into it is folded back into the log rather than silently reverted.No - it is gitignored for you.

Both are plain text you can inspect, back up, or wipe by hand. An older array-format notes.json is migrated to the log on first open, with the original kept as a .pre-jsonl-*.json backup. See Core concepts for the full note model.

The notes API

The overlay talks to a small REST API under the mount prefix. You rarely call it directly, but it is documented so you can script against it if needed.

RoutePurpose
GET {mount}/notes?page=<path>List notes, optionally scoped to one page. Also returns the project's default author and the capability block.
GET {mount}/capabilitiesWhat this surface implements, so the Hub can hide a control rather than write a value that would be ignored.
POST {mount}/notesCreate a note: { body, type, priority, url, page, anchor }.
PATCH {mount}/notes/:idUpdate a note: { status | body | priority | reply }.
DELETE {mount}/notes/:idDelete a note.
GET {mount}/widget.jsThe overlay client script.

Seeding it across many apps

Because the package installs as a live symlink, Red Pen is meant to ride along in every local app you build. The pattern is always the same three steps: install the local dependency, mount the middleware first, and make sure that app's .gitignore does not hide .redpen/ - the notes are meant to be committed. Once linked, every future Red Pen release upgrades all of those apps on their next restart, with no reinstall.

Try the demo

npm install
npm run example   # http://localhost:4000

The demo serves a small page with the overlay injected, so you can exercise the full loop - add, pin, reply, edit, start, resolve, reopen, delete - against a real .redpen/notes.jsonl.