@antigenic-oss/paint 0.2.8 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/NOTICE +2 -2
- package/README.md +42 -15
- package/bin/paint.js +32 -0
- package/next.config.mjs +8 -0
- package/package.json +10 -8
- package/public/dev-editor-inspector.js +14 -0
- package/public/sw-proxy/sw.js +886 -0
- package/src/app/api/proxy/[[...path]]/route.ts +12 -1
- package/src/app/api/sw-fetch/[[...path]]/route.ts +149 -0
- package/src/app/docs/DocsClient.tsx +1 -1
- package/src/app/docs/page.tsx +134 -407
- package/src/app/layout.tsx +48 -2
- package/src/app/page.tsx +2 -0
- package/src/components/ConnectModal.tsx +98 -181
- package/src/components/PreviewFrame.tsx +91 -0
- package/src/components/TargetSelector.tsx +49 -15
- package/src/components/left-panel/LayerNode.tsx +5 -2
- package/src/components/left-panel/LayersPanel.tsx +10 -1
- package/src/components/right-panel/changes/ChangesPanel.tsx +7 -1
- package/src/hooks/useChangeTracker.ts +34 -26
- package/src/hooks/usePostMessage.ts +27 -1
- package/src/lib/serviceWorkerRegistration.ts +163 -0
- package/src/store/treeSlice.ts +29 -17
- package/src/store/uiSlice.ts +6 -0
- package/src/types/messages.ts +6 -0
package/LICENSE
CHANGED
package/NOTICE
CHANGED
package/README.md
CHANGED
|
@@ -1,28 +1,35 @@
|
|
|
1
1
|
# pAInt
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
<p>
|
|
4
|
+
<a href="https://www.npmjs.com/package/@antigenic-oss/paint"><img alt="npm version" src="https://img.shields.io/npm/v/@antigenic-oss/paint" /></a>
|
|
5
|
+
<a href="https://www.npmjs.com/package/@antigenic-oss/paint"><img alt="npm downloads" src="https://img.shields.io/npm/dm/@antigenic-oss/paint" /></a>
|
|
6
|
+
<a href="https://github.com/Antigenic-OSS/pAInt/blob/main/LICENSE"><img alt="License" src="https://img.shields.io/github/license/Antigenic-OSS/pAInt" /></a>
|
|
7
|
+
<a href="https://github.com/Antigenic-OSS/pAInt/issues"><img alt="Issues" src="https://img.shields.io/github/issues/Antigenic-OSS/pAInt" /></a>
|
|
8
|
+
<br />
|
|
9
|
+
<a href="https://github.com/Antigenic-OSS/pAInt/stargazers"><img alt="Stars" src="https://img.shields.io/github/stars/Antigenic-OSS/pAInt?style=social" /></a>
|
|
10
|
+
<a href="https://bun.sh"><img alt="Bun" src="https://img.shields.io/badge/local%20dev-Bun-000000" /></a>
|
|
11
|
+
<a href="https://nodejs.org"><img alt="Node.js" src="https://img.shields.io/badge/runtime-Node.js-5FA04E" /></a>
|
|
12
|
+
<a href="https://nextjs.org"><img alt="Next.js" src="https://img.shields.io/badge/framework-Next.js-000000" /></a>
|
|
13
|
+
<a href="https://www.typescriptlang.org"><img alt="TypeScript" src="https://img.shields.io/badge/language-TypeScript-3178C6" /></a>
|
|
14
|
+
</p>
|
|
12
15
|
|
|
13
16
|
pAInt is a visual editor for localhost web projects. It helps you inspect elements, edit styles, manage CSS variables, and export changelogs for [Claude Code](https://claude.ai/claude-code).
|
|
14
17
|
|
|
18
|
+
Built by [Antigenic](https://antigenic.org).
|
|
19
|
+
|
|
15
20
|
## Table of Contents
|
|
16
21
|
|
|
17
22
|
- [Project Status](#project-status)
|
|
18
23
|
- [Global CLI](#global-cli)
|
|
19
24
|
- [What You Can Do](#what-you-can-do)
|
|
25
|
+
- [Why pAInt Saves AI Tokens](#why-paint-saves-ai-tokens)
|
|
20
26
|
- [Prerequisites](#prerequisites)
|
|
21
27
|
- [Quick Start](#quick-start)
|
|
22
28
|
- [Connection Modes](#connection-modes)
|
|
23
29
|
- [Core Workflow](#core-workflow)
|
|
24
30
|
- [Interface Layout](#interface-layout)
|
|
25
31
|
- [Commands](#commands)
|
|
32
|
+
- [Bridge vs Server vs Terminal](#bridge-vs-server-vs-terminal)
|
|
26
33
|
- [Architecture Summary](#architecture-summary)
|
|
27
34
|
- [Security Notes](#security-notes)
|
|
28
35
|
- [Documentation](#documentation)
|
|
@@ -101,6 +108,15 @@ Terminal WS (when started): `ws://localhost:4001/ws`
|
|
|
101
108
|
- Track every change and export a structured changelog
|
|
102
109
|
- Send changelogs to Claude Code for source-file application
|
|
103
110
|
|
|
111
|
+
## Why pAInt Saves AI Tokens
|
|
112
|
+
|
|
113
|
+
pAInt helps you use fewer AI tokens by turning broad prompts into precise, structured change data:
|
|
114
|
+
|
|
115
|
+
- You edit visually first, so you do not need long back-and-forth prompt iterations
|
|
116
|
+
- Exported changelogs include exact selectors, properties, and before/after values
|
|
117
|
+
- Claude Code receives focused instructions, reducing token-heavy ambiguity
|
|
118
|
+
- You review only real diffs instead of asking the model to rediscover page context each time
|
|
119
|
+
|
|
104
120
|
## Prerequisites
|
|
105
121
|
|
|
106
122
|
- Local repository development: Bun `>=1.3`
|
|
@@ -172,6 +188,20 @@ bun run start # Production server (port 4000)
|
|
|
172
188
|
bun run lint # Biome check
|
|
173
189
|
```
|
|
174
190
|
|
|
191
|
+
## Bridge vs Server vs Terminal
|
|
192
|
+
|
|
193
|
+
These three pieces have different jobs:
|
|
194
|
+
|
|
195
|
+
- `pAInt server` (`paint start`): runs the main pAInt web app UI (default `http://127.0.0.1:4000`)
|
|
196
|
+
- `bridge server` (`paint bridge start`): local HTTP bridge used when pAInt UI is hosted remotely (for example Vercel) but still needs safe local machine access (`http://127.0.0.1:4002` by default)
|
|
197
|
+
- `terminal server` (`paint terminal start`): local WebSocket PTY service for the in-app Terminal tab (`ws://localhost:4001/ws` by default)
|
|
198
|
+
|
|
199
|
+
When to run each:
|
|
200
|
+
|
|
201
|
+
- Local-only workflow: run `paint start` (and optionally `paint terminal start` if you want in-app terminal streaming)
|
|
202
|
+
- Hosted UI + local project workflow: run `paint bridge start` so the hosted UI can reach local-only capabilities
|
|
203
|
+
- Claude/CLI visibility workflow: run `paint terminal start` to see command output and progress inside pAInt
|
|
204
|
+
|
|
175
205
|
## Architecture Summary
|
|
176
206
|
|
|
177
207
|
- Next.js App Router frontend (develop locally with Bun, run globally via Node.js CLI)
|
|
@@ -201,15 +231,12 @@ See `CONTRIBUTING.md` for setup, workflow, and pull request expectations.
|
|
|
201
231
|
|
|
202
232
|
## Release Automation
|
|
203
233
|
|
|
204
|
-
- Versioning
|
|
234
|
+
- Versioning is manual (update `package.json` before merging to `main`).
|
|
205
235
|
- CI workflow: `.github/workflows/ci.yml`
|
|
206
236
|
- Release workflow: `.github/workflows/release.yml`
|
|
207
237
|
- Publishing uses npm Trusted Publishing (OIDC) from the `release` GitHub Environment.
|
|
208
|
-
-
|
|
209
|
-
|
|
210
|
-
```bash
|
|
211
|
-
bun run changeset
|
|
212
|
-
```
|
|
238
|
+
- On every push to `main`, the release workflow runs build/lint/smoke checks.
|
|
239
|
+
- Publish happens automatically only when `package.json` version differs from the currently published npm version.
|
|
213
240
|
|
|
214
241
|
## License
|
|
215
242
|
|
package/bin/paint.js
CHANGED
|
@@ -491,6 +491,8 @@ async function startApp(options) {
|
|
|
491
491
|
console.log(`pAInt started at http://${options.host}:${options.port}`)
|
|
492
492
|
console.log(`Web pid: ${webChild.pid}`)
|
|
493
493
|
console.log(`Web logs: ${WEB_LOG_FILE}`)
|
|
494
|
+
|
|
495
|
+
checkForUpdate()
|
|
494
496
|
}
|
|
495
497
|
|
|
496
498
|
function stopApp() {
|
|
@@ -529,6 +531,8 @@ function appStatus() {
|
|
|
529
531
|
console.log(`URL: http://${existing.host}:${existing.port}`)
|
|
530
532
|
console.log(`Started: ${existing.startedAt}`)
|
|
531
533
|
if (existing.logs?.web) console.log(`Web logs: ${existing.logs.web}`)
|
|
534
|
+
|
|
535
|
+
checkForUpdate()
|
|
532
536
|
}
|
|
533
537
|
|
|
534
538
|
function appLogs() {
|
|
@@ -747,6 +751,34 @@ function bridgeLogs() {
|
|
|
747
751
|
process.stdout.write(fs.readFileSync(BRIDGE_LOG_FILE, 'utf8'))
|
|
748
752
|
}
|
|
749
753
|
|
|
754
|
+
function checkForUpdate() {
|
|
755
|
+
const pkg = JSON.parse(fs.readFileSync(APP_PACKAGE_JSON, 'utf8'))
|
|
756
|
+
const name = pkg.name
|
|
757
|
+
const registryUrl = `https://registry.npmjs.org/${name}/latest`
|
|
758
|
+
|
|
759
|
+
const req = https.get(registryUrl, { timeout: 3000 }, (res) => {
|
|
760
|
+
if (res.statusCode !== 200) return
|
|
761
|
+
const chunks = []
|
|
762
|
+
res.on('data', (chunk) => chunks.push(chunk))
|
|
763
|
+
res.on('end', () => {
|
|
764
|
+
try {
|
|
765
|
+
const data = JSON.parse(Buffer.concat(chunks).toString('utf8'))
|
|
766
|
+
const latest = data.version
|
|
767
|
+
if (latest && latest !== APP_VERSION) {
|
|
768
|
+
console.log(
|
|
769
|
+
`\nUpdate available: ${APP_VERSION} → ${latest}\nRun: npm install -g ${name}\n`,
|
|
770
|
+
)
|
|
771
|
+
}
|
|
772
|
+
} catch {
|
|
773
|
+
// ignore parse errors
|
|
774
|
+
}
|
|
775
|
+
})
|
|
776
|
+
})
|
|
777
|
+
|
|
778
|
+
req.on('error', () => {})
|
|
779
|
+
req.on('timeout', () => req.destroy())
|
|
780
|
+
}
|
|
781
|
+
|
|
750
782
|
function showHelp() {
|
|
751
783
|
console.log(`paint - pAInt server manager
|
|
752
784
|
|
package/next.config.mjs
CHANGED
|
@@ -20,6 +20,14 @@ const nextConfig = {
|
|
|
20
20
|
{ key: 'Cross-Origin-Resource-Policy', value: 'cross-origin' },
|
|
21
21
|
],
|
|
22
22
|
},
|
|
23
|
+
{
|
|
24
|
+
// Allow the SW to control /sw-proxy/ scope even though the script
|
|
25
|
+
// lives at /sw-proxy/sw.js (scope matches script directory)
|
|
26
|
+
source: '/sw-proxy/sw.js',
|
|
27
|
+
headers: [
|
|
28
|
+
{ key: 'Service-Worker-Allowed', value: '/sw-proxy/' },
|
|
29
|
+
],
|
|
30
|
+
},
|
|
23
31
|
]
|
|
24
32
|
},
|
|
25
33
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antigenic-oss/paint",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Visual editor for localhost web projects with a global paint CLI",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://github.com/Antigenic-OSS/pAInt",
|
|
@@ -13,10 +13,16 @@
|
|
|
13
13
|
},
|
|
14
14
|
"keywords": [
|
|
15
15
|
"paint",
|
|
16
|
+
"pAInt",
|
|
16
17
|
"visual-editor",
|
|
17
18
|
"nextjs",
|
|
18
19
|
"css",
|
|
19
|
-
"localhost"
|
|
20
|
+
"localhost",
|
|
21
|
+
"bridge-server",
|
|
22
|
+
"terminal",
|
|
23
|
+
"claude-code",
|
|
24
|
+
"ai-coding",
|
|
25
|
+
"developer-tools"
|
|
20
26
|
],
|
|
21
27
|
"bin": {
|
|
22
28
|
"paint": "bin/paint.js"
|
|
@@ -53,10 +59,7 @@
|
|
|
53
59
|
"lint:fix": "biome lint --write .",
|
|
54
60
|
"format": "biome format --write .",
|
|
55
61
|
"smoke:packed-global": "node ./bin/smoke-packed-global.js",
|
|
56
|
-
"prepublishOnly": "npm run smoke:packed-global"
|
|
57
|
-
"changeset": "changeset",
|
|
58
|
-
"version-packages": "changeset version",
|
|
59
|
-
"release": "changeset publish"
|
|
62
|
+
"prepublishOnly": "npm run smoke:packed-global"
|
|
60
63
|
},
|
|
61
64
|
"dependencies": {
|
|
62
65
|
"@tailwindcss/postcss": "4.2.1",
|
|
@@ -77,7 +80,6 @@
|
|
|
77
80
|
"zustand": "^5.0.11"
|
|
78
81
|
},
|
|
79
82
|
"devDependencies": {
|
|
80
|
-
"@biomejs/biome": "2.4.5"
|
|
81
|
-
"@changesets/cli": "^2.29.7"
|
|
83
|
+
"@biomejs/biome": "2.4.5"
|
|
82
84
|
}
|
|
83
85
|
}
|
|
@@ -429,6 +429,13 @@
|
|
|
429
429
|
'position:absolute;top:-18px;left:-1px;padding:1px 6px;font-size:10px;font-family:-apple-system,BlinkMacSystemFont,sans-serif;line-height:14px;color:#fff;background:#1D3F23;border-radius:3px 3px 0 0;white-space:nowrap;pointer-events:none;'
|
|
430
430
|
hoverOverlay.appendChild(hoverLabel)
|
|
431
431
|
|
|
432
|
+
// Watch for framework hydration removing our overlays from the DOM.
|
|
433
|
+
// When detected, re-append them to the current document.body.
|
|
434
|
+
new MutationObserver(function () {
|
|
435
|
+
if (!selectionOverlay.isConnected) document.body.appendChild(selectionOverlay)
|
|
436
|
+
if (!hoverOverlay.isConnected) document.body.appendChild(hoverOverlay)
|
|
437
|
+
}).observe(document.documentElement, { childList: true, subtree: true })
|
|
438
|
+
|
|
432
439
|
var hoveredElement = null
|
|
433
440
|
|
|
434
441
|
function getElementLabel(el) {
|
|
@@ -447,6 +454,7 @@
|
|
|
447
454
|
}
|
|
448
455
|
|
|
449
456
|
document.addEventListener('mousemove', (e) => {
|
|
457
|
+
|
|
450
458
|
if (!selectionModeEnabled) {
|
|
451
459
|
hoverOverlay.style.display = 'none'
|
|
452
460
|
return
|
|
@@ -524,6 +532,7 @@
|
|
|
524
532
|
function selectElement(el) {
|
|
525
533
|
// Don't select elements when selection mode is disabled (preview mode)
|
|
526
534
|
if (!selectionModeEnabled) return
|
|
535
|
+
|
|
527
536
|
selectedElement = el
|
|
528
537
|
var rect = el.getBoundingClientRect()
|
|
529
538
|
selectionOverlay.style.display = 'block'
|
|
@@ -575,6 +584,7 @@
|
|
|
575
584
|
}
|
|
576
585
|
|
|
577
586
|
function updateSelectionOverlay() {
|
|
587
|
+
|
|
578
588
|
if (!selectedElement || selectionOverlay.style.display === 'none') return
|
|
579
589
|
var rect = selectedElement.getBoundingClientRect()
|
|
580
590
|
selectionOverlay.style.top = `${rect.top}px`
|
|
@@ -1439,6 +1449,10 @@
|
|
|
1439
1449
|
}
|
|
1440
1450
|
if (resolved.origin !== window.location.origin) continue
|
|
1441
1451
|
var linkPath = resolved.pathname
|
|
1452
|
+
// Strip /sw-proxy/ prefix added by SW proxy URL rewriting
|
|
1453
|
+
if (linkPath.indexOf('/sw-proxy/') === 0) {
|
|
1454
|
+
linkPath = linkPath.substring('/sw-proxy'.length) || '/'
|
|
1455
|
+
}
|
|
1442
1456
|
if (linkPath.indexOf('/api/') === 0 || linkPath === '') continue
|
|
1443
1457
|
if (!linkPath.startsWith('/')) continue
|
|
1444
1458
|
if (seen[linkPath]) continue
|