Paintless
Latest

Build with AI

Hand this page to your coding agent and it installs Paintless in your project.

Paintless is installed by the same kind of agent it feeds. This page is written for one: give your coding agent the URL and it has the detection rules, the exact packages for your stack, the snippets, and a way to check its own work.

Give this to your agent

Set up Paintless in this project by following
https://kyechan99.github.io/paintless/build-with-ai.md

Follow it exactly. Stop and ask me only where the page says to ask.

Anything that can read a URL and edit files works — Claude Code, Cursor, Copilot, aider, or your own. The .md suffix serves this page as plain Markdown; drop it for the HTML version. Every docs page answers to both, and llms.txt indexes all of them.

Working in a chat instead? Easy to use at the top of this page copies the raw Markdown link, or opens the page straight in ChatGPT or Claude.


Everything below is addressed to the agent.

What you are installing

Three parts, in the order they matter:

PartWhat it doesWhere it goes
MapperAnnotates rendered elements with data-paintless-source="file:line:column" during devA build plugin in the app's bundler config
WidgetThe floating button, element picker and inspect panelMounted once in the app, dev only
Dev hostnpx paintless dev — a local WebSocket server that runs an agent against the working tree and commits on approvalA terminal, not a file

Definition of done: the human runs their dev server and npx paintless dev, opens the app, clicks the Paintless button, and the picker labels a hovered element with its own file:line. Anything short of that is a partial install — say so rather than reporting success.

1. Read the project before you touch it

Establish these from the repository, not from assumption:

  • Package manager — from the lockfile (pnpm-lock.yaml, package-lock.json, yarn.lock, bun.lock).
  • Frameworkreact, vue, svelte, or none, from package.json dependencies.
  • Bundlervite, webpack, or next, from dependencies and the config files present.
  • TypeScript — whether a tsconfig.json governs the app.
  • Monorepo — if there are workspaces, find the one package that renders in a browser. The mapper and widget go there; the dev host still runs at the repository root (see the rules below).
  • The app's root component and dev entry — where a <Paintless /> would mount, and which command starts the dev server.

If nothing in this repository runs in a browser dev server, stop and tell the human: Paintless has nothing to attach to.

If Paintless is already installed, repair or upgrade what is there. Do not add a second mapper, a second widget mount, or a second config.

2. Fast path: the setup command

npm create paintless@latest -- --yes

It detects the stack you just confirmed, installs the matching packages, writes paintless.config.ts, and prints the snippets for the bundler config and the app. --yes accepts the detected defaults (dev mode, Claude Code agent) with no prompts, which is what you want — you cannot answer interactive prompts.

  • pnpm create paintless@latest --yes / yarn create paintless --yes for the other managers.
  • --dir <path> to target one workspace in a monorepo.
  • --no-install to write the config and print the steps without installing.

It does not edit the bundler config or the app source. Take the snippets it printed and apply them yourself — steps 3.2 and 3.3 are the same edits, written out. If the command cannot run at all (no network, blocked registry, a stack it does not detect), do step 3 by hand.

3. Manual path

3.1 Install the packages

StackInstall
React + Vite-D @paintless/mapper-vite and @paintless/react
Vue + Vite-D @paintless/mapper-vue and @paintless/vue
Svelte + Vite-D @paintless/mapper-vite and @paintless/svelte
Next.js-D @paintless/mapper-next and @paintless/react
React or Vue + webpack-D @paintless/mapper-webpack and the matching binding
No bundler / plain HTML@paintless/widget by script tag — no file:line, the fallback below still applies

Add -D paintless too if the human wants paintless as a project script rather than npx paintless dev each time.

3.2 Wire the mapper into the build

The mapper must run before the framework plugin — it annotates the source the framework plugin then compiles.

Vite (React or Svelte):

// vite.config.ts
import { paintlessMapper } from '@paintless/mapper-vite'
import react from '@vitejs/plugin-react'
import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [paintlessMapper(), react()],
})

Vite (Vue SFC):

import { paintlessVueMapper } from '@paintless/mapper-vue'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [paintlessVueMapper(), vue()],
})

Next.js:

// next.config.mjs
import { withPaintless } from '@paintless/mapper-next'

export default withPaintless({
  // the project's existing config, unchanged
})

webpack:

module: {
  rules: [
    {
      test: /\.[jt]sx$/,
      exclude: /node_modules/,
      enforce: 'pre',
      use: ['@paintless/mapper-webpack'],
    },
  ],
}

Mappers annotate the dev server only by default. Leave it that way: { build: true } publishes your source layout to anyone who opens devtools on the deployed site, and it is only worth it in prod mode, which the human has to choose deliberately.

3.3 Mount the widget, dev only

React:

import { Paintless } from '@paintless/react'

export function App() {
  return (
    <>
      {/* the app */}
      {import.meta.env.DEV && <Paintless />}
    </>
  )
}

Use process.env.NODE_ENV !== 'production' where there is no import.meta.env (Next.js, webpack). Vue is <Paintless /> from @paintless/vue, Svelte is use:paintless from @paintless/svelte, and a plain page can load @paintless/widget's IIFE build with a script tag. The gate is the point: mount nothing in the production bundle unless the human is setting up prod mode.

3.4 The config file, only if you need it

paintless dev runs with no config: the default agent is Claude Code against the project root. Write paintless.config.ts when the human wants a different agent:

import { command, defineConfig } from 'paintless'

export default defineConfig({
  agents: { default: command('aider --message {prompt} --yes') },
})

Destinations and routes belong to prod mode. Do not add them here — point the human at Self-Hosting instead.

4. Verify

  1. Start the app's dev server the way the project already does.
  2. Start the dev host in a separate long-running process: npx paintless dev. It does not exit; do not wait on it and do not run it in a blocking step.
  3. Confirm it prints [paintless] dev host listening on ws://127.0.0.1:7327.
  4. Load the app and confirm the floating Paintless button is mounted.
  5. Open the picker and hover an element. The label must read a real path such as <button> · src/App.tsx:24. "no source mapper on this page" means step 3.2 did not take effect — fix it before reporting done.

If you can drive a browser, do steps 4 and 5 yourself. If you cannot, say so explicitly and give the human the two commands and what they should see.

5. Report back, then stop

Tell the human:

  • every file you changed, and the one-line reason for each;
  • the two commands that start the loop;
  • that the first agent run needs an authenticated Claude Code install or ANTHROPIC_API_KEY in the environment;
  • what you deliberately did not do — production mode, a self-hosted server, delivery destinations, and anything the rules below stopped you from touching.

Rules

  • Never write a secret into a tracked file. API keys and tokens go in .env, which must be gitignored. If a step seems to need one, ask instead.
  • Do not commit unless the human asked you to. Leave the working tree reviewable.
  • Do not point the dev host above the repository. --root is the boundary of everything the agent can edit; the default (the current directory) is correct for a single app, and the repository root is correct for a monorepo.
  • Do not bind the dev host to a public interface. The socket is unauthenticated. --host exists, and changing it exposes the repository to that network.
  • Do not change what the app already does. Every edit here is additive: a plugin, a mounted component, a config file.
  • Do not turn on build: true, prod mode, or a destination on your own judgment. Those publish source paths or send data outward and are the human's call.

Troubleshooting

SymptomCauseFix
Picker says "no source mapper on this page"Mapper missing, after the framework plugin, or you are looking at a production buildRe-check 3.2; the mapper goes first, and dev-serve only annotates the dev server
Widget cannot reach a dev hostpaintless dev not running, or a port change on one side onlyStart it; if --port changed, pass the same URL to the widget's host option
Connection refused from a phone or another machineThe dev host only accepts localhost originspaintless dev --allow-origin http://192.168.1.20:5173
Next.js with Turbopack shows no file:lineTurbopack builds are not annotated yetNothing to fix — requests still resolve through the fallback below
Elements resolve to the wrong file in a monorepoDev host rooted inside one packageRun it at the repository root so the agent can edit shared packages

Source mapping is an accelerator, never a requirement: without annotations a request still carries the selector path, component trail, element HTML and computed styles, and the agent finds the code by searching the repository.

Where to go next

Edit this page

Last updated: