` are now left out.
+- Screenshots with the script tag from the API: the lazy screenshot chunk was served without `Access-Control-Allow-Origin`, so other websites could not load it.
- Screenshots of pages using Alpine, Vue or other framework attributes (`:class`, `x-on:click`) were blank.
## [1.0.0] - 2026-10-16
diff --git a/README.md b/README.md
index 1940d21..a856a12 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,6 @@
-
@@ -42,14 +41,14 @@
**For your stack**
-- **Any website or app**: npm package or a single script tag, React, Vue, Alpine, Livewire, plain HTML. Isolated in a Shadow DOM, 18 KB gzip, no runtime dependencies
+- **Any website or app**: one script tag, served by your own API. React, Vue, Alpine, Livewire, plain HTML, no npm and no build step on the website. Isolated in a Shadow DOM, 18 KB gzip, no runtime dependencies. Updates arrive with every API deploy
- **Many projects, many environments**: one API serves any number of projects, each with several origins (wildcards and `localhost:*` included), optional IP ranges, its own color and daily limit
- **Privacy first**: mask elements with `data-feedback-mask`, password fields are always hidden, console logs are redacted in the browser
- **Your data, your server**: a small Laravel API with origin and IP allowlists, rate limits and a honeypot. Screenshots are stored in your database (SQLite, MySQL or PostgreSQL)
## How it works
-1. The widget runs on your website, in a Shadow DOM that leaves your styles untouched.
+1. Your website loads the widget from your API with one script tag. It runs in a Shadow DOM that leaves your styles untouched.
2. A visitor writes feedback and can attach a screenshot of the whole page. In marking mode they draw rectangles or highlight text:
@@ -60,7 +59,7 @@
4. The API checks origin, IP and rate limits, stores everything in your database and answers with a signed link to the feedback, which the thank you screen offers to copy.
5. Your team works through the feedback with Artisan commands or shares a [feedback page link](#feedback-pages-and-share-links) with whoever fixes it, who can mark it as completed right there.
-The repository contains both parts: the **API**, a Laravel 13 app in the root folder, and the **widget** in [`widget/`](widget), published as [`@codebar-ag/feedback-widget`](https://www.npmjs.com/package/@codebar-ag/feedback-widget).
+The repository contains both parts: the **API**, a Laravel 13 app in the root folder, and the **widget** in [`widget/`](widget), which the deploy build compiles and the API serves at `/widget/v1/feedback.js`.
## Quick start
@@ -92,21 +91,23 @@ php artisan feedback:project:create "Example Website" --origin=https://www.examp
Daily limit ........................................................... (no limit)
```
-**3. Add the widget to your website**
+**3. Build the widget**
```bash
-npm install @codebar-ag/feedback-widget
+npm ci && npm run build # Node 26, compiles the widget into resources/widget/v1
```
-```ts
-import { initFeedback } from '@codebar-ag/feedback-widget';
+**4. Add it to your website**
-await initFeedback({
- projectKey: 'fbk_live_92T7QQB6fj6Zb37jBGzrDI',
- endpoint: 'https://feedback.example.com',
-});
+```html
+
+
```
+That is all: no npm package, no bundler, no third-party CDN. The script always matches your API version.
+
## Widget
### Options
@@ -114,7 +115,7 @@ await initFeedback({
Only `projectKey` and `endpoint` are required.
```ts
-const feedback = await initFeedback({
+const feedback = await CodebarFeedback.init({
projectKey: 'fbk_live_92T7QQB6fj6Zb37jBGzrDI',
endpoint: 'https://feedback.example.com',
@@ -144,7 +145,7 @@ const feedback = await initFeedback({
});
```
-Call `initFeedback()` as early as possible: console errors are captured from that moment on. Calling it twice returns the same instance.
+Call `CodebarFeedback.init()` as early as possible: console errors are captured from that moment on. Calling it twice returns the same instance.
### Methods and events
@@ -177,26 +178,39 @@ Events: `ready`, `open`, `close`, `before_send`, `after_send`, `error`.
Password fields and `autocomplete="cc-*"` fields are always masked. Images from other hosts without CORS headers and cross-origin iframes render empty. If rendering fails, the feedback is sent without a screenshot.
-### Script tag
+### Loading the script
+
+The API serves the widget at `/widget/v1/feedback.js` (cached 5 minutes) and loads the screenshot code lazily from `/widget/v1/chunks/…` (cached for a year, the file name changes with every build). Both are sent with `Access-Control-Allow-Origin: *`, so they work on any website. A new widget version reaches every website with the next API deploy.
-No build step needed:
+**Without blocking the page**, start it when the script has loaded:
```html
-
-
+
```
-Each [release](https://github.com/codebar-ag/feedback.codebar.ch/releases) lists the SRI hash for `integrity="…"`.
+**From your own JavaScript** (Vite, webpack, a strict CSP without inline handlers), load it on demand, for example after the first interaction:
+
+```js
+function loadFeedback(endpoint) {
+ return new Promise((resolve, reject) => {
+ const script = document.createElement('script');
+ script.src = `${endpoint}/widget/v1/feedback.js`;
+ script.onload = () => resolve(window.CodebarFeedback);
+ script.onerror = reject;
+ document.head.append(script);
+ });
+}
+
+const CodebarFeedback = await loadFeedback('https://feedback.example.com');
+const feedback = await CodebarFeedback.init({ projectKey: 'fbk_live_…', endpoint: 'https://feedback.example.com' });
+```
### Laravel example
```blade
-
+
+