@mindbowser_inc/ui-feedback-plugin
v0.4.0
Published
A floating widget for collecting annotated UI feedback on web prototypes — Figma-style comments, screenshots, reply threads, Firebase/Supabase/AWS support
Readme
ui-feedback-plugin
A drop-in floating widget for collecting annotated UI feedback on web prototypes. Think Figma comments — but on any web page.
Features
- Draggable floating trigger button
- Freehand drawing + shapes (rectangle, circle, arrow)
- Full-page screenshots with annotations
- Comment threads with replies
- Open / Resolved filter
- Three fully independent backend options — no lock-in
Installation
npm install @mindbowser_inc/ui-feedback-pluginBackends
Choose one backend. They are fully independent — switch any time by changing the backend config.
| Backend | Database | Screenshots | Real-time | |---|---|---|---| | Firebase | Firestore | Firebase Storage | Yes (onSnapshot) | | Supabase | Postgres | Supabase Storage | Yes (Realtime) | | AWS | JSON files in S3 | S3 | Polling (5 s default) |
Firebase
Firestore stores comments and replies. Firebase Storage holds screenshots. No backend required — Firebase keys are public identifiers, secured by Security Rules.
Quick start
import { initFeedbackPlugin } from '@mindbowser_inc/ui-feedback-plugin'
initFeedbackPlugin({
backend: {
provider: 'firebase',
apiKey: 'AIzaSy...',
authDomain: 'your-project.firebaseapp.com',
projectId: 'your-project',
storageBucket: 'your-project.appspot.com',
messagingSenderId: '123456789',
appId: '1:123456789:web:abc123',
},
projectKey: 'my-project',
})The Firebase
apiKeyis a public identifier, not a secret. Secure your data with Firebase Security Rules.
Firebase console setup
- Go to console.firebase.google.com and create a project.
- Firestore → Create database → Start in test mode (then lock it down with rules below).
- Storage → Get started → set rules below.
- Project Settings → Your apps → Add a web app → copy the config values above.
Recommended Security Rules
Firestore (firestore.rules):
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /ufp_comments/{doc} {
allow read, write: if true; // Replace with auth checks for production
}
match /ufp_replies/{doc} {
allow read, write: if true;
}
}
}Storage (storage.rules):
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /ufp/{allPaths=**} {
allow read, write: if true; // Replace with auth checks for production
}
}
}Supabase
Postgres stores comments and replies. Supabase Storage holds screenshots. No backend required — the anonKey is a public key secured by Row Level Security.
Quick start
import { initFeedbackPlugin } from '@mindbowser_inc/ui-feedback-plugin'
initFeedbackPlugin({
backend: {
provider: 'supabase',
url: 'https://xxxx.supabase.co',
anonKey: 'eyJhbGci...',
},
projectKey: 'my-project',
})The Supabase
anonKeyis a public key by design. Secure your data with Row Level Security (RLS).
Supabase setup
- Create a project at supabase.com.
- Go to SQL Editor and run the SQL below.
- Go to Storage → New bucket → name it
ufp-screenshots→ set it to Public. - Run the storage policy SQL below.
- Find your URL and anon key in Project Settings → API.
Database SQL (run once in SQL Editor):
create table if not exists ufp_comments (
id uuid primary key default gen_random_uuid(),
page_url text not null,
project_key text not null default 'default',
author_name text not null,
text text not null,
screenshot_url text default '',
annotation jsonb not null default '{}',
resolved boolean default false,
created_at bigint not null
);
create table if not exists ufp_replies (
id uuid primary key default gen_random_uuid(),
comment_id uuid not null references ufp_comments(id) on delete cascade,
author_name text not null,
text text not null,
created_at bigint not null
);
-- Enable Row Level Security
alter table ufp_comments enable row level security;
alter table ufp_replies enable row level security;
-- Allow all operations (restrict per your auth strategy in production)
create policy "ufp_comments_all" on ufp_comments for all using (true) with check (true);
create policy "ufp_replies_all" on ufp_replies for all using (true) with check (true);
-- Grant table access to the anon role (RLS policies alone are not enough)
grant select, insert, update, delete on ufp_comments to anon;
grant select, insert, update, delete on ufp_replies to anon;
-- Enable real-time for comments
alter publication supabase_realtime add table ufp_comments;Storage policy SQL (run after creating the ufp-screenshots bucket):
create policy "ufp_storage_insert" on storage.objects
for insert to anon with check (bucket_id = 'ufp-screenshots');
create policy "ufp_storage_select" on storage.objects
for select to anon using (bucket_id = 'ufp-screenshots');
create policy "ufp_storage_update" on storage.objects
for update to anon using (bucket_id = 'ufp-screenshots');AWS (Cognito Identity Pool)
Everything — comments, replies, and screenshots — is stored directly in S3 as JSON and PNG files. No backend server required. No Firebase or Supabase needed.
The browser obtains temporary, rotating credentials from a Cognito Identity Pool. No long-lived AWS keys ever touch the browser. The IAM role follows the least-privilege principle (file-type restrictions, size limits, no delete permission).
The AWS SDK is not bundled with the plugin — install it alongside:
npm install @aws-sdk/client-s3 @aws-sdk/credential-provider-cognito-identityQuick start
import { initFeedbackPlugin } from '@mindbowser_inc/ui-feedback-plugin'
initFeedbackPlugin({
backend: {
provider: 'aws',
region: 'us-east-1',
bucket: 'your-bucket-name',
identityPoolId: 'us-east-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
pollInterval: 5000, // optional, default 5000 ms
},
projectKey: 'my-project',
})AWS setup (step by step)
Step 1 — Create an S3 bucket
- Open the S3 console.
- Click Create bucket.
- Choose a name and region (remember these — you'll need them in the plugin config).
- Leave Block all public access ON (you'll open just the screenshots prefix below).
- Enable Versioning (optional, but useful for recovery).
- Create the bucket.
Step 2 — Configure CORS
Go to your bucket → Permissions → Cross-origin resource sharing (CORS) → Edit, and paste:
[
{
"AllowedOrigins": ["https://your-domain.com"],
"AllowedMethods": ["GET", "PUT", "HEAD"],
"AllowedHeaders": ["*"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3000
}
]Use
"*"forAllowedOriginsduring local development. Restrict to real domains in production.
Step 3 — Bucket policy (public read for screenshots only)
Go to bucket → Permissions → Bucket policy → Edit, and paste (replace YOUR_BUCKET):
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadScreenshots",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::YOUR_BUCKET/ufp/*/screenshots/*"
}
]
}This makes only screenshots publicly readable so they can display in <img> tags. Comments and reply JSON files remain private.
Step 4 — Create an IAM policy
Go to IAM → Policies → Create policy → JSON tab, and paste (replace YOUR_BUCKET):
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "WriteJsonFiles",
"Effect": "Allow",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::YOUR_BUCKET/ufp/*",
"Condition": {
"StringLike": {
"s3:RequestObjectKey": [
"ufp/*/comments/*.json",
"ufp/replies/*/*.json",
"ufp/_index/*.json"
]
},
"NumericLessThanEquals": {
"s3:content-length": "51200"
}
}
},
{
"Sid": "WriteScreenshots",
"Effect": "Allow",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::YOUR_BUCKET/ufp/*",
"Condition": {
"StringLike": {
"s3:RequestObjectKey": "ufp/*/screenshots/*.png"
},
"NumericLessThanEquals": {
"s3:content-length": "3145728"
}
}
},
{
"Sid": "ReadFiles",
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::YOUR_BUCKET/ufp/*"
},
{
"Sid": "ListFiles",
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::YOUR_BUCKET",
"Condition": {
"StringLike": {
"s3:prefix": "ufp/*"
}
}
}
]
}What this enforces:
- Only
.jsonfiles under the correct paths can be written (no arbitrary uploads) - JSON files are capped at 50 KB per file
- Screenshot
.pngfiles are capped at 3 MB per file - All access is scoped to the
ufp/prefix s3:DeleteObjectis absent — the browser can never delete files (soft delete is used instead)
Name the policy (e.g. ufp-plugin-policy) and save it.
Step 5 — Create an IAM role
- Go to IAM → Roles → Create role.
- Trusted entity type: Web identity.
- Identity provider: Amazon Cognito.
- Leave the audience field blank for now (Cognito will configure it).
- Click Next, attach the policy you created in Step 4.
- Name the role (e.g.
ufp-plugin-cognito-unauth) and create it.
Step 6 — Create a Cognito Identity Pool
- Go to Cognito → Identity pools → Create identity pool.
- User access: check Guest access (unauthenticated identities).
- Guest role: select Use an existing IAM role → choose the role from Step 5.
- Give the pool a name (e.g.
ufp-plugin-pool) and create it. - On the pool detail page, copy the Identity pool ID — it looks like
us-east-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.
Step 7 — Configure the plugin
initFeedbackPlugin({
backend: {
provider: 'aws',
region: 'us-east-1', // same region as your bucket and Identity Pool
bucket: 'your-bucket-name',
identityPoolId: 'us-east-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
},
projectKey: 'my-project',
})How data is stored
your-bucket/
ufp/
{projectKey}/
{sha256(pageUrl)[0..15]}/
comments/
{commentId}.json ← comment data
screenshots/
{commentId}.png ← publicly readable
replies/
{commentId}/
{replyId}.json ← reply data
_index/
{commentId}.json ← { "p": projectKey, "h": pageHash }Limitations
| Limitation | Detail | |---|---| | No real-time | New comments from other users appear after the next poll (default 5 s) | | Soft delete only | Deleted comments are hidden but remain in S3 (good for audit trail) | | N+1 reads | Loading comments = 1 LIST + N GET requests (fine for typical 10–30 comments) | | CORS required | Your bucket must whitelist the domains the plugin runs on |
Configuration reference
initFeedbackPlugin({
backend: FirebaseProviderConfig | SupabaseProviderConfig | AWSProviderConfig // required
projectKey?: string // namespace comments per project (default: 'default')
position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'
theme?: { primaryColor?: string } // default: '#6366f1'
})Plain HTML / CDN (UMD)
No build step needed — load directly from a CDN:
<script src="https://unpkg.com/@mindbowser_inc/ui-feedback-plugin/dist/ui-feedback-plugin.umd.js"></script>
<script>
UIFeedbackPlugin.initFeedbackPlugin({
backend: {
provider: 'supabase',
url: 'https://xxxx.supabase.co',
anonKey: 'eyJhbGci...',
},
projectKey: 'my-project',
})
</script>Replace the backend block with any of the three provider configs shown above.
The AWS backend is not usable this way — the AWS SDK is externalized from the bundle and needs a bundler (Vite, webpack, etc.) to resolve it. Firebase and Supabase remain fully usable via CDN as shown above.
Local development
git clone https://github.com/vishmindbowser/ui-feedback-plugin.git
cd ui-feedback-plugin
npm install
cp .env.local.example .env.local # fill in your credentials
npm run devSet VITE_PROVIDER in .env.local to supabase, firebase, or aws to switch backends.
License
MIT
