Help
The overlay does not open
No pen button bottom-right
<Redlining /> renders nothing unless NODE_ENV is
development. Check that it is mounted in the root layout, that you
are running next dev, and that nothing sets
NODE_ENV=production in your dev environment. To force it on in a staging
build, pass enabled.
The button is there but Alt+R does nothing
Another extension or your window manager may own the combination. Choose a different one
with the hotkey prop, for example "Ctrl+Shift+K". The hotkey
is matched on the physical key, so macOS Option combinations work even though they type
symbols.
Clicking selects nothing
The overlay ignores <html> and <body>, its own
toolbar and panel, and anything under the mouse that belongs to another overlay in a
shadow root with a higher stacking order. Move the cursor onto page content; the outline
follows the smallest host element.
The badge says "unresolved" or the export says "ancestor"
The element has no data-rl
Anchors come from a loader that only runs on files matched by
include (default app, components,
src) with .tsx or .jsx extensions. Files
elsewhere, .js files, and everything in node_modules are not
stamped. Check the served HTML: decorated elements carry
data-rl="path:line:col".
withRedlining(config, { include: ['app', 'features', 'packages/ui/src'] })
Third-party components and portals
A design-system button that renders its own <button> inside
node_modules cannot be stamped. The export then names the nearest decorated
ancestor and adds locate the child by selector, which is enough for the agent
in most cases. Prefer selecting the wrapping element you own with [.
The owner chain is empty
Server Components have no client-side fiber, so owners is [];
the file and line are still exact. That is expected and documented in the export.
Snapshot tests fail because of data-rl
The attribute exists only in development. Tests that render dev HTML can strip it:
import { stripRedlining } from 'redlining'
expect(stripRedlining(html)).toMatchSnapshot()
Monorepos
Nothing is stamped in a pnpm / yarn workspace
Turbopack takes the directory of the lockfile as its root, which is the workspace, not
the app. Redlining matches files with a path pattern for that reason, and stamps paths
relative to the app directory (process.cwd() when
next.config is evaluated). If you run Next from the workspace root with a
filter, paths are still relative to the app. Sibling packages that match
include, such as packages/ui/src, are stamped with a
../ path, which is correct: the file really is there.
Save fails
Toast says Save failed: 404
The route is missing. Run npx redlining init, or create
app/api/redlining/route.ts with
export { GET, POST } from 'redlining/next/route'. If you changed the path,
pass the same value as endpoint to <Redlining />.
403 Redlining is disabled outside development
The handler refuses unless NODE_ENV is development. Use
createHandler({ enabled: true }) only if you really want saving in another
environment.
413 Body exceeds 8388608 bytes
The screenshot is too large for the default cap. Turn the camera off for this save, or
raise maxBytes in createHandler. The overlay itself refuses
captures over 7 MB and tells you.
Vite: there is no Save
The save endpoint is a Next.js route handler. In Vite projects use
Copy prompt and paste the Markdown into Claude Code, or write your own
endpoint that accepts { session } and calls the serializers.
Screenshots
The screenshot is blank or missing fonts
The page is rendered in the browser with html-to-image, which cannot read
cross-origin stylesheets or images without CORS headers. Self-hosted assets render fine;
a CDN font without Access-Control-Allow-Origin falls back. The pins and the
Markdown are unaffected, so the spec is still complete.
Pins are in the wrong place
Pins are drawn at each element's position at save time, in page coordinates. If the layout changes between annotating and saving (a collapsed menu, a resized window), the Markdown still points at the right file and line; re-save to refresh the picture.
Device frame
The narrow view looks different from my phone
The frame is an iframe at exactly the chosen CSS width, so media queries and fixed bars behave as in a browser window of that size. What it cannot reproduce is device pixel ratio, touch input and the platform's own UI, so treat it as the layout check it is, not a device emulator.
My page refuses to load inside the frame
A frame-ancestors Content-Security-Policy or an
X-Frame-Options header that forbids same-origin framing blocks it. In
development, allow 'self' for those headers, or annotate at full width and
say the breakpoint in the note.
Annotations made in the frame do not show in the panel outside
They sync through the browser's storage event, which fires only across documents of the same origin and route. Close the frame or press L twice to refresh the panel; if the frame page has a different path (a redirect, a locale prefix), it is a different session by design.
Production
Does any of this ship?
No. withRedlining() adds the loader only under the development-server
phase, so next build emits no data-rl. The package's
production export is a component that returns null; the
roughly thirty bytes of that stub are the only trace, and only because your layout
imports it. The route handler answers 403 outside development. CI in the Redlining
repository asserts all three on every commit.
FAQ
Does it work outside Next.js — Angular, Vue, plain HTML?
Yes, with selector anchors instead of file:line: load
redlining/dist/standalone.js from a script tag (React bundled, dev only) or
call mount() from redlining/standalone. Angular component
names come from ng.getComponent in dev mode, Vue names from the instance
chain. Without a save endpoint the files are downloaded. See
Other stacks.
Why not a visual editor that writes the code itself?
Because the agent already reads the codebase and can make the change consistent with it. Redlining's job is to make your visual intent precise enough that the first attempt lands. It never edits code.
Why annotate in the app instead of a screenshot?
A screenshot says what but not where in the code. The DOM already knows which file and component every pixel came from; Redlining captures that together with your note.
Can I use this without Claude Code?
Yes. Copy prompt gives you the Markdown for any agent, and
annotations.json is the same session for your own tooling. The
/redline command is just a Markdown file in .claude/commands/.
Where does the name come from?
Redlining is the proofreader's term for marking a proof with corrections and additions in red, which is exactly what the tool does to a running page. The American housing term shares the word, not the meaning.
Is anything sent anywhere?
No. Everything stays on localhost: the overlay talks only to your own dev server, there are no accounts and no telemetry.