Surfaces
Express middleware v0.2.0
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 a local JSON file you can keep out of git.
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.
NODE_ENV === 'production'. It is a tool for the people building the app, never for its visitors - so it is safe to leave mounted, and it simply goes quiet in production.
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 read from and written to a single JSON file in your project - by default .redpen/notes.json. There is no other moving part.
Install
Red Pen for Express is not published to npm yet. During development you install it as a local file: dependency, which resolves as a live symlink. Because it is a symlink rather than a copied package, any upgrade to Red Pen flows into every app that links it the next time that app restarts - there is no per-app reinstall step.
From the app you want to annotate:
# top-level app - path is relative to the app
npm install ../red-pen-express
# nested launcher (for example, an app at <name>/launcher)
npm install ../../red-pen-express
That writes a "red-pen-express": "file:../red-pen-express" entry into the consuming project's package.json. 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.
.redpen/ to your project's .gitignore. Notes are dev-local working data, not something you commit and share.
Options
| Option | Default | Description |
|---|---|---|
mount | /__redpen | URL prefix for the notes API and the overlay script. |
file | ./.redpen/notes.json | Where notes are stored on disk. |
enabled | NODE_ENV !== 'production' | Master on/off switch for the whole middleware. |
inject | true | Auto-inject the overlay into HTML responses. Set to false to add the script tag yourself. |
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:
- Leave a typed note - choose a type (note, idea, problem, or question) and a priority, then write the note.
- Pin to an element - click Pin, then click any element on the page. The note is anchored to that element by a CSS selector plus a relative position. Numbered red markers render over the pinned elements and follow the page on scroll and resize.
- Locate - jump from a note back to its pinned element; the page scrolls to it and the element flashes.
- The repository - the panel header links to "All notes" at
{mount}/repo, a standalone page listing every note across every page with filters and bulk actions. - Dark mode and a resizable panel - a moon/sun toggle in the panel header, and a drag handle on the panel's left edge. Both preferences are saved to
localStorageand restored next time.
What is new in v0.2.0
Version 0.2.0 is the "notes UX parity" milestone - it brings the overlay's day-to-day note handling in line with the WordPress plugin. Every item below is in the current release.
| Feature | What it does |
|---|---|
| Reply thread + inline form | Each note renders its full reply thread plus a toggleable inline reply form, so a note can become a short conversation. |
| Edit a note after adding | Load 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. |
| Open / Resolved tabs | The old single flat list is replaced by Open and Resolved tabs, each showing a live count. Open is the default view. |
| Error toasts on failed saves | A 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 prefs | The 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 resolve | Resolving 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 in one file - by default .redpen/notes.json at the project root. It is a simple JSON document holding the array of notes (each with its id, body, type, priority, status, url/page, anchor, replies, and timestamps). Because it is just a file, you can inspect it, back it up, or wipe it by hand. 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.
| Route | Purpose |
|---|---|
GET {mount}/notes?page=<path> | List notes, optionally scoped to one page. |
POST {mount}/notes | Create a note: { body, type, priority, url, page, anchor }. |
PATCH {mount}/notes/:id | Update a note: { status | body | priority | reply }. |
DELETE {mount}/notes/:id | Delete a note. |
GET {mount}/widget.js | The 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. It is currently wired into nine local apps this way. The pattern is always the same three steps: install the local dependency, mount the middleware first, and add .redpen/ to that app's .gitignore. 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, resolve, reopen, delete - against a real .redpen/notes.json.