@antigenic-oss/paint 0.2.9 → 0.3.1

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.
@@ -1,11 +1,8 @@
1
1
  import type { Metadata } from 'next'
2
2
  import Link from 'next/link'
3
3
  import {
4
- CodeBlock,
5
4
  FaqAccordion,
6
5
  FaqSection,
7
- FrameworkAccordion,
8
- FrameworkSection,
9
6
  Sidebar,
10
7
  } from './DocsClient'
11
8
 
@@ -35,9 +32,6 @@ export const metadata: Metadata = {
35
32
  },
36
33
  }
37
34
 
38
- const SCRIPT_TAG =
39
- '<script src="https://dev-editor-flow.vercel.app/dev-editor-inspector.js"></script>'
40
-
41
35
  export default function DocsPage() {
42
36
  return (
43
37
  <div
@@ -92,11 +86,17 @@ export default function DocsPage() {
92
86
  </strong>
93
87
  , the editor loads your page through a{' '}
94
88
  <strong style={{ color: 'var(--text-primary)' }}>
95
- built-in reverse proxy
89
+ Service Worker proxy
96
90
  </strong>
97
- . The proxy fetches your HTML, strips client-side scripts (to
98
- prevent routing conflicts), and injects a lightweight inspector
99
- script. This inspector communicates with the editor via{' '}
91
+ . The proxy intercepts requests, fetches your page from
92
+ localhost, injects a lightweight inspector script, and strips
93
+ security headers that would block the iframe all happening
94
+ in the browser with no server-side proxying needed. Your
95
+ page&apos;s scripts and client-side rendering work normally,
96
+ so you see the fully interactive version of your site.
97
+ </p>
98
+ <p style={{ ...bodyText, marginTop: '0.75rem' }}>
99
+ The inspector communicates with the editor via{' '}
100
100
  <code style={inlineCodeStyle}>postMessage</code> — reporting
101
101
  element metadata when you hover or click, and applying style
102
102
  previews in real time.
@@ -109,10 +109,11 @@ export default function DocsPage() {
109
109
  actual source files.
110
110
  </p>
111
111
  <p style={{ ...bodyText, marginTop: '0.75rem' }}>
112
- If automatic injection fails (e.g., non-standard HTML
113
- responses), a banner will prompt you to add the script tag
114
- manually. The framework guides below show exactly where to place
115
- it.
112
+ <strong style={{ color: 'var(--success)' }}>
113
+ No script tags needed
114
+ </strong>{' '}
115
+ — the proxy handles everything automatically. Just click
116
+ Connect and start editing.
116
117
  </p>
117
118
  </Section>
118
119
 
@@ -207,402 +208,78 @@ export default function DocsPage() {
207
208
  </Section>
208
209
 
209
210
  {/* Framework Guides */}
210
- <Section id="framework-guides" title="Framework Guides">
211
- <p style={{ ...bodyText, marginBottom: '1rem' }}>
212
- If the automatic connection doesn&apos;t detect the inspector
213
- within 5 seconds, add the script tag manually to your project.
214
- Select your framework below for the exact file and placement.
211
+ <Section id="framework-guides" title="Framework Compatibility">
212
+ <p style={bodyText}>
213
+ pAInt works with any framework out of the box — no script
214
+ tags or configuration needed. The Service Worker proxy
215
+ automatically handles inspector injection for all projects.
215
216
  </p>
216
- <div
217
- className="flex items-center gap-2 px-4 py-3 rounded-md mb-4 text-sm"
218
- style={{
219
- background: 'var(--accent-bg)',
220
- border: '1px solid var(--accent)',
221
- color: 'var(--text-secondary)',
222
- }}
223
- >
224
- <span style={{ color: 'var(--accent)' }}>Note:</span>
225
- <span>
226
- The snippets below use the hosted URL. If running pAInt
227
- locally, replace{' '}
228
- <code style={{ ...inlineCodeStyle, fontSize: '0.8em' }}>
229
- https://dev-editor-flow.vercel.app
230
- </code>{' '}
231
- with{' '}
232
- <code style={{ ...inlineCodeStyle, fontSize: '0.8em' }}>
233
- http://localhost:4000
234
- </code>{' '}
235
- (or your custom port).
236
- </span>
217
+ <p style={{ ...bodyText, marginTop: '0.75rem' }}>
218
+ Tested and confirmed working with:
219
+ </p>
220
+ <div className="flex flex-col gap-2 mt-4">
221
+ <CompatItem icon="&#9650;" name="Next.js" detail="App Router & Pages Router" />
222
+ <CompatItem icon="&#9889;" name="Vite + React" detail="Including React Router, TanStack, etc." />
223
+ <CompatItem icon="&#9883;" name="Create React App" detail="Standard and ejected setups" />
224
+ <CompatItem icon="&#128241;" name="React Native / Expo Web" detail="Expo Router and custom web entry" />
225
+ <CompatItem icon="&#9899;" name="Vue / Nuxt" detail="Vue 3 (Vite) and Nuxt 3" />
226
+ <CompatItem icon="&#127793;" name="Svelte / SvelteKit" detail="SvelteKit and plain Svelte + Vite" />
227
+ <CompatItem icon="&#128196;" name="Plain HTML" detail="Static sites and vanilla JS" />
237
228
  </div>
238
- <FrameworkAccordion>
239
- {/* Next.js */}
240
- <FrameworkSection id="nextjs" title="Next.js" icon="&#9650;">
241
- <div>
242
- <h4
243
- className="text-base font-medium mb-1"
244
- style={{ color: 'var(--text-primary)' }}
245
- >
246
- App Router
247
- </h4>
248
- <p style={{ ...mutedText, marginBottom: '0.5rem' }}>
249
- Add to <code style={inlineCodeStyle}>app/layout.tsx</code>
250
- </p>
251
- <CodeBlock
252
- language="tsx"
253
- copyText={SCRIPT_TAG}
254
- code={`// app/layout.tsx
255
- export default function RootLayout({ children }: { children: React.ReactNode }) {
256
- return (
257
- <html lang="en">
258
- <body>
259
- {children}
260
- <script src="https://dev-editor-flow.vercel.app/dev-editor-inspector.js"></script>
261
- </body>
262
- </html>
263
- );
264
- }`}
265
- />
266
- </div>
267
- <div>
268
- <h4
269
- className="text-base font-medium mb-1"
270
- style={{ color: 'var(--text-primary)' }}
271
- >
272
- Pages Router
273
- </h4>
274
- <p style={{ ...mutedText, marginBottom: '0.5rem' }}>
275
- Add to{' '}
276
- <code style={inlineCodeStyle}>pages/_document.tsx</code>
277
- </p>
278
- <CodeBlock
279
- language="tsx"
280
- copyText={SCRIPT_TAG}
281
- code={`// pages/_document.tsx
282
- import { Html, Head, Main, NextScript } from 'next/document';
283
-
284
- export default function Document() {
285
- return (
286
- <Html>
287
- <Head />
288
- <body>
289
- <Main />
290
- <NextScript />
291
- <script src="https://dev-editor-flow.vercel.app/dev-editor-inspector.js"></script>
292
- </body>
293
- </Html>
294
- );
295
- }`}
296
- />
297
- </div>
298
- </FrameworkSection>
299
-
300
- {/* Vite + React */}
301
- <FrameworkSection
302
- id="vite-react"
303
- title="Vite + React"
304
- icon="&#9889;"
305
- >
306
- <p style={{ ...mutedText, marginBottom: '0.5rem' }}>
307
- Add to <code style={inlineCodeStyle}>index.html</code>{' '}
308
- (project root)
309
- </p>
310
- <CodeBlock
311
- language="html"
312
- copyText={SCRIPT_TAG}
313
- code={`<!-- index.html -->
314
- <!DOCTYPE html>
315
- <html lang="en">
316
- <head>
317
- <meta charset="UTF-8" />
318
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
319
- <title>My App</title>
320
- </head>
321
- <body>
322
- <div id="root"></div>
323
- <script type="module" src="/src/main.tsx"></script>
324
- <script src="https://dev-editor-flow.vercel.app/dev-editor-inspector.js"></script>
325
- </body>
326
- </html>`}
327
- />
328
- </FrameworkSection>
329
-
330
- {/* Create React App */}
331
- <FrameworkSection
332
- id="create-react-app"
333
- title="Create React App"
334
- icon="&#9883;"
335
- >
336
- <p style={{ ...mutedText, marginBottom: '0.5rem' }}>
337
- Add to{' '}
338
- <code style={inlineCodeStyle}>public/index.html</code>
339
- </p>
340
- <CodeBlock
341
- language="html"
342
- copyText={SCRIPT_TAG}
343
- code={`<!-- public/index.html -->
344
- <!DOCTYPE html>
345
- <html lang="en">
346
- <head>
347
- <meta charset="utf-8" />
348
- <meta name="viewport" content="width=device-width, initial-scale=1" />
349
- <title>My App</title>
350
- </head>
351
- <body>
352
- <div id="root"></div>
353
- <script src="https://dev-editor-flow.vercel.app/dev-editor-inspector.js"></script>
354
- </body>
355
- </html>`}
356
- />
357
- </FrameworkSection>
358
-
359
- {/* Plain HTML */}
360
- <FrameworkSection
361
- id="plain-html"
362
- title="Plain HTML"
363
- icon="&#128196;"
364
- >
365
- <p style={{ ...mutedText, marginBottom: '0.5rem' }}>
366
- Add before{' '}
367
- <code style={inlineCodeStyle}>&lt;/body&gt;</code> in any{' '}
368
- <code style={inlineCodeStyle}>.html</code> file
369
- </p>
370
- <CodeBlock
371
- language="html"
372
- copyText={SCRIPT_TAG}
373
- code={`<!DOCTYPE html>
374
- <html>
375
- <head>
376
- <title>My Page</title>
377
- </head>
378
- <body>
379
- <h1>Hello</h1>
380
- <script src="https://dev-editor-flow.vercel.app/dev-editor-inspector.js"></script>
381
- </body>
382
- </html>`}
383
- />
384
- </FrameworkSection>
385
-
386
- {/* React Native / Expo Web */}
387
- <FrameworkSection
388
- id="react-native-expo"
389
- title="React Native / Expo Web"
390
- icon="&#128241;"
391
- >
392
- <div>
393
- <h4
394
- className="text-base font-medium mb-1"
395
- style={{ color: 'var(--text-primary)' }}
396
- >
397
- Expo Router
398
- </h4>
399
- <p style={{ ...mutedText, marginBottom: '0.5rem' }}>
400
- Add to{' '}
401
- <code style={inlineCodeStyle}>app/_layout.tsx</code> using{' '}
402
- a <code style={inlineCodeStyle}>&lt;Script&gt;</code>{' '}
403
- component or the web{' '}
404
- <code style={inlineCodeStyle}>index.html</code>
405
- </p>
406
- <CodeBlock
407
- language="tsx"
408
- copyText={SCRIPT_TAG}
409
- code={`// app/_layout.tsx
410
- import { Slot } from 'expo-router';
411
- import { Platform } from 'react-native';
412
- import { useEffect } from 'react';
413
-
414
- export default function RootLayout() {
415
- useEffect(() => {
416
- if (Platform.OS === 'web') {
417
- const script = document.createElement('script');
418
- script.src = 'https://dev-editor-flow.vercel.app/dev-editor-inspector.js';
419
- document.body.appendChild(script);
420
- }
421
- }, []);
422
-
423
- return <Slot />;
424
- }`}
425
- />
426
- </div>
427
- <div>
428
- <h4
429
- className="text-base font-medium mb-1"
430
- style={{ color: 'var(--text-primary)' }}
431
- >
432
- Custom <code style={inlineCodeStyle}>web/index.html</code>
433
- </h4>
434
- <p style={{ ...mutedText, marginBottom: '0.5rem' }}>
435
- If your Expo project has a custom web entry point
436
- </p>
437
- <CodeBlock
438
- language="html"
439
- copyText={SCRIPT_TAG}
440
- code={`<!-- web/index.html -->
441
- <!DOCTYPE html>
442
- <html>
443
- <head>
444
- <meta charset="utf-8" />
445
- <meta name="viewport" content="width=device-width, initial-scale=1" />
446
- </head>
447
- <body>
448
- <div id="root"></div>
449
- <script src="https://dev-editor-flow.vercel.app/dev-editor-inspector.js"></script>
450
- </body>
451
- </html>`}
452
- />
453
- </div>
454
- </FrameworkSection>
455
-
456
- {/* Vue / Nuxt */}
457
- <FrameworkSection
458
- id="vue-nuxt"
459
- title="Vue / Nuxt"
460
- icon="&#9899;"
461
- >
462
- <div>
463
- <h4
464
- className="text-base font-medium mb-1"
465
- style={{ color: 'var(--text-primary)' }}
466
- >
467
- Vue (Vite)
468
- </h4>
469
- <p style={{ ...mutedText, marginBottom: '0.5rem' }}>
470
- Add to <code style={inlineCodeStyle}>index.html</code>{' '}
471
- (project root)
472
- </p>
473
- <CodeBlock
474
- language="html"
475
- copyText={SCRIPT_TAG}
476
- code={`<!-- index.html -->
477
- <!DOCTYPE html>
478
- <html lang="en">
479
- <head>
480
- <meta charset="UTF-8" />
481
- <title>My Vue App</title>
482
- </head>
483
- <body>
484
- <div id="app"></div>
485
- <script type="module" src="/src/main.ts"></script>
486
- <script src="https://dev-editor-flow.vercel.app/dev-editor-inspector.js"></script>
487
- </body>
488
- </html>`}
489
- />
490
- </div>
491
- <div>
492
- <h4
493
- className="text-base font-medium mb-1"
494
- style={{ color: 'var(--text-primary)' }}
495
- >
496
- Nuxt 3
497
- </h4>
498
- <p style={{ ...mutedText, marginBottom: '0.5rem' }}>
499
- Add via{' '}
500
- <code style={inlineCodeStyle}>nuxt.config.ts</code> or{' '}
501
- <code style={inlineCodeStyle}>app.html</code>
502
- </p>
503
- <CodeBlock
504
- language="ts"
505
- copyText={`app: {\n head: {\n script: [{ src: 'https://dev-editor-flow.vercel.app/dev-editor-inspector.js' }]\n }\n}`}
506
- code={`// nuxt.config.ts
507
- export default defineNuxtConfig({
508
- app: {
509
- head: {
510
- script: [
511
- { src: 'https://dev-editor-flow.vercel.app/dev-editor-inspector.js' }
512
- ]
513
- }
514
- }
515
- });`}
516
- />
517
- </div>
518
- </FrameworkSection>
519
-
520
- {/* Svelte / SvelteKit */}
521
- <FrameworkSection
522
- id="svelte-sveltekit"
523
- title="Svelte / SvelteKit"
524
- icon="&#127793;"
525
- >
526
- <p style={{ ...mutedText, marginBottom: '0.5rem' }}>
527
- Add to <code style={inlineCodeStyle}>src/app.html</code>
528
- </p>
529
- <CodeBlock
530
- language="html"
531
- copyText={SCRIPT_TAG}
532
- code={`<!-- src/app.html -->
533
- <!DOCTYPE html>
534
- <html lang="en">
535
- <head>
536
- <meta charset="utf-8" />
537
- <meta name="viewport" content="width=device-width, initial-scale=1" />
538
- %sveltekit.head%
539
- </head>
540
- <body data-sveltekit-preload-data="hover">
541
- <div style="display: contents">%sveltekit.body%</div>
542
- <script src="https://dev-editor-flow.vercel.app/dev-editor-inspector.js"></script>
543
- </body>
544
- </html>`}
545
- />
546
- </FrameworkSection>
547
- </FrameworkAccordion>
229
+ <p style={{ ...bodyText, marginTop: '1rem' }}>
230
+ Just start your dev server, open pAInt, select the port,
231
+ and click{' '}
232
+ <strong style={{ color: 'var(--text-primary)' }}>
233
+ Connect
234
+ </strong>
235
+ . The proxy preserves your page&apos;s scripts and
236
+ client-side rendering, so interactive features like 3D scenes
237
+ (Spline, Three.js), animations (GSAP, Framer Motion), and
238
+ client-side routing all work normally.
239
+ </p>
548
240
  </Section>
549
241
 
550
242
  {/* Troubleshooting */}
551
243
  <Section id="troubleshooting" title="Troubleshooting">
552
244
  <div className="flex flex-col gap-4">
553
- <TroubleshootItem title="Inspector script not detected">
245
+ <TroubleshootItem title="Stuck on &quot;Connecting&quot;">
554
246
  <p>
555
- If the banner appears after 5 seconds, the automatic proxy
556
- injection didn&apos;t work for your setup. Add the script
557
- tag manually using the framework guides above. Make sure
558
- pAInt is running on the port shown in the script URL.
247
+ If the editor stays in &quot;Connecting&quot; state, your
248
+ browser may be caching an old Service Worker. Open DevTools
249
+ &rarr; Application &rarr; Service Workers, unregister any
250
+ workers for <code style={inlineCodeStyle}>localhost:4000</code>,
251
+ clear Cache Storage, then hard refresh (Cmd+Shift+R).
559
252
  </p>
560
253
  </TroubleshootItem>
561
254
 
562
255
  <TroubleshootItem title="CORS or Cross-Origin errors">
563
256
  <p>
564
- pAInt proxy runs on a different port than your project. If
565
- your project sets strict CORS headers, the proxy may be
566
- blocked. The automatic method handles this by serving
567
- everything from the same origin. For the manual method,
568
- ensure your dev server allows requests from{' '}
569
- <code style={inlineCodeStyle}>localhost:4000</code>.
570
- </p>
571
- </TroubleshootItem>
572
-
573
- <TroubleshootItem title="COEP / COOP headers blocking the iframe">
574
- <p>
575
- Some frameworks set{' '}
576
- <code style={inlineCodeStyle}>
577
- Cross-Origin-Embedder-Policy
578
- </code>{' '}
579
- or{' '}
580
- <code style={inlineCodeStyle}>
581
- Cross-Origin-Opener-Policy
582
- </code>{' '}
583
- headers that prevent loading in an iframe. Check your server
584
- config or middleware and relax these headers in development.
257
+ The Service Worker proxy serves everything from the same
258
+ origin, which handles most CORS issues automatically. If
259
+ your project makes API calls to external services during
260
+ render, those requests are not proxied. This typically
261
+ doesn&apos;t affect visual editing.
585
262
  </p>
586
263
  </TroubleshootItem>
587
264
 
588
- <TroubleshootItem title="Infinite iframe reload">
265
+ <TroubleshootItem title="Page looks different or broken">
589
266
  <p>
590
- This happens when the target page&apos;s client-side router
591
- detects the proxy URL and redirects. pAInt&apos;s proxy
592
- strips <code style={inlineCodeStyle}>&lt;script&gt;</code>{' '}
593
- tags to prevent this. If you still see reloads, check that
594
- no inline scripts or meta-refresh tags are causing
595
- navigation.
267
+ The proxy preserves all scripts and client-side rendering.
268
+ If something looks off, try a hard refresh to ensure the
269
+ latest Service Worker is active. If the issue persists,
270
+ check the browser console for errors some pages with
271
+ very strict CSP headers may need those headers relaxed in
272
+ development.
596
273
  </p>
597
274
  </TroubleshootItem>
598
275
 
599
- <TroubleshootItem title="Styles look different in the editor">
276
+ <TroubleshootItem title="Target dev server not reachable">
600
277
  <p>
601
- The proxy serves SSR HTML with CSS intact but strips
602
- JavaScript. If your styles depend on client-side JS (e.g.,
603
- CSS-in-JS runtime injection), some styles may be missing.
604
- Use the manual script method with your full dev server if
605
- CSS-in-JS is critical.
278
+ Make sure your project&apos;s dev server is running on the
279
+ port you selected. The proxy connects to{' '}
280
+ <code style={inlineCodeStyle}>localhost:&lt;port&gt;</code>{' '}
281
+ from the browser, so the server must be accessible from
282
+ your machine.
606
283
  </p>
607
284
  </TroubleshootItem>
608
285
  </div>
@@ -612,17 +289,13 @@ export default defineNuxtConfig({
612
289
  <Section id="faq" title="FAQ">
613
290
  <FaqAccordion>
614
291
  <FaqSection
615
- id="faq-safe"
616
- question="Is it safe to add the inspector script to my project?"
292
+ id="faq-no-setup"
293
+ question="Do I need to add any script tags to my project?"
617
294
  >
618
295
  <p>
619
- Yes. The inspector script is a lightweight, read-only
620
- observer that listens for hover/click events and reports
621
- element metadata (tag name, styles, bounding box) back to
622
- pAInt via <code style={inlineCodeStyle}>postMessage</code>.
623
- It does not modify your source code, send data to external
624
- servers, or execute arbitrary code. It only communicates
625
- with pAInt origin.
296
+ No. pAInt uses a Service Worker proxy that automatically
297
+ injects the inspector script into your page. Just click
298
+ Connect no modifications to your project needed.
626
299
  </p>
627
300
  </FaqSection>
628
301
 
@@ -662,15 +335,13 @@ export default defineNuxtConfig({
662
335
 
663
336
  <FaqSection
664
337
  id="faq-production"
665
- question="Should I remove the script tag before deploying to production?"
338
+ question="Does pAInt affect my production build?"
666
339
  >
667
340
  <p>
668
- Yes. The inspector script is intended for local development
669
- only. Remove it (or wrap it in an environment check) before
670
- deploying. If you forget, the script will try to connect to
671
- pAInt origin and silently fail — it won&apos;t affect your
672
- users — but it&apos;s best practice to keep it out of
673
- production builds.
341
+ No. pAInt runs entirely in the browser through a Service
342
+ Worker nothing is added to your project&apos;s source
343
+ code or build output. There&apos;s nothing to remove before
344
+ deploying.
674
345
  </p>
675
346
  </FaqSection>
676
347
 
@@ -818,6 +489,42 @@ function TroubleshootItem({
818
489
  )
819
490
  }
820
491
 
492
+ function CompatItem({
493
+ icon,
494
+ name,
495
+ detail,
496
+ }: {
497
+ icon: string
498
+ name: string
499
+ detail: string
500
+ }) {
501
+ return (
502
+ <div
503
+ className="flex items-center gap-3 rounded-md px-4 py-3"
504
+ style={{
505
+ background: 'var(--bg-secondary)',
506
+ border: '1px solid var(--border)',
507
+ }}
508
+ >
509
+ <span className="text-base">{icon}</span>
510
+ <div>
511
+ <span
512
+ className="text-sm font-medium"
513
+ style={{ color: 'var(--text-primary)' }}
514
+ >
515
+ {name}
516
+ </span>
517
+ <span
518
+ className="text-sm ml-2"
519
+ style={{ color: 'var(--text-muted)' }}
520
+ >
521
+ — {detail}
522
+ </span>
523
+ </div>
524
+ </div>
525
+ )
526
+ }
527
+
821
528
  function UseCaseItem({
822
529
  title,
823
530
  description,
package/src/app/page.tsx CHANGED
@@ -3,6 +3,7 @@
3
3
  import { useEffect, useState } from 'react'
4
4
  import { Editor } from '@/components/Editor'
5
5
  import { useEditorStore } from '@/store'
6
+ import { registerSwProxy } from '@/lib/serviceWorkerRegistration'
6
7
 
7
8
  export default function Home() {
8
9
  const loadPersistedUI = useEditorStore((s) => s.loadPersistedUI)
@@ -26,6 +27,7 @@ export default function Home() {
26
27
 
27
28
  loadPersistedUI()
28
29
  loadPersistedClaude()
30
+ registerSwProxy()
29
31
 
30
32
  // Suppress HMR errors caused by proxied routes leaking into the
31
33
  // editor's route tree (e.g. "unrecognized HMR message").