redlining

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.