review-lens-react 0.1.1 → 0.2.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.
@@ -1 +1 @@
1
- (function(w,n){typeof exports=="object"&&typeof module<"u"?n(exports,require("react/jsx-runtime"),require("react")):typeof define=="function"&&define.amd?define(["exports","react/jsx-runtime","react"],n):(w=typeof globalThis<"u"?globalThis:w||self,n(w.ReviewLensReact={},w.jsxRuntime,w.React))})(this,(function(w,n,p){"use strict";const G=["https://www.googleapis.com/auth/spreadsheets","https://www.googleapis.com/auth/userinfo.email"].join(" "),J="https://www.googleapis.com/oauth2/v3/userinfo";function $(e){const o=e.feedbackSheetName??"Feedback",t=e.usersSheetName??"Users";let r,s;async function i(){return r??(r=X(e.googleClientId)),r}async function m(d,c){const a=await i(),l=await fetch(`https://sheets.googleapis.com/v4/spreadsheets/${e.spreadsheetId}${d}`,{...c,headers:{Authorization:`Bearer ${a}`,"Content-Type":"application/json",...c==null?void 0:c.headers}});if(!l.ok)throw new Error(`Google Sheets request failed with ${l.status}`);return l.json()}async function k(d){return(await m(`/values/${encodeURIComponent(d)}`)).values??[]}return{async getCurrentUser(){if(!s){const d=await i(),c=await fetch(J,{headers:{Authorization:`Bearer ${d}`}});if(!c.ok)throw new Error(`Google userinfo request failed with ${c.status}`);s=(await c.json()).email}if(!s)throw new Error("Google account did not return an email address");return{email:s}},async getPermissions(d){const[{email:c},a]=await Promise.all([this.getCurrentUser(),k(t)]),l=j(a),C=c.toLowerCase(),h=l.find(b=>{var g;return((g=b.email)==null?void 0:g.toLowerCase())===C&&b.active!=="false"&&(!b.projectKey||b.projectKey===d)});return Q((h==null?void 0:h.role)??"designer")},async listFeedback(d){return j(await k(o)).map(M).filter(a=>a!==null).filter(a=>a.projectKey===d.projectKey&&a.contentId===d.contentId&&a.normalizedPath===d.normalizedPath).sort((a,l)=>l.createdAt.localeCompare(a.createdAt))},async createFeedback(d){const c=new Date().toISOString(),a={...d,id:crypto.randomUUID(),status:"open",createdAt:c,updatedAt:c};return await m(`/values/${encodeURIComponent(o)}:append?valueInputOption=RAW`,{method:"POST",body:JSON.stringify({values:[H(a)]})}),a},async resolveFeedback(d,c){const a=await k(o),l=a[0]??R,C=l.indexOf("id"),h=l.indexOf("status"),b=l.indexOf("updatedAt"),g=l.indexOf("resolvedAt"),v=l.indexOf("resolvedBy"),f=a.findIndex((T,B)=>B>0&&T[C]===d);if(f<1)throw new Error(`Feedback ${d} was not found`);const y=[...a[f]],E=new Date().toISOString();y[h]="resolved",y[b]=E,y[g]=E,y[v]=c,await m(`/values/${encodeURIComponent(o)}!A${f+1}:Q${f+1}?valueInputOption=RAW`,{method:"PUT",body:JSON.stringify({values:[y]})});const F=M(W(l,y));if(!F)throw new Error(`Feedback ${d} could not be parsed after resolving`);return F}}}const R=["id","projectKey","contentId","normalizedPath","originalUrl","selector","selectorStrategy","elementFingerprintJson","cssSnapshotJson","comment","status","authorEmail","createdAt","updatedAt","resolvedAt","resolvedBy"];function H(e){return[e.id,e.projectKey,e.contentId,e.normalizedPath,e.originalUrl,e.selector,e.selectorStrategy,JSON.stringify(e.elementFingerprint),JSON.stringify(e.cssSnapshot),e.comment,e.status,e.authorEmail,e.createdAt,e.updatedAt,e.resolvedAt??"",e.resolvedBy??""]}function j(e){const[o,...t]=e;return o?t.map(r=>W(o,r)):[]}function W(e,o){return Object.fromEntries(e.map((t,r)=>[t,o[r]??""]))}function M(e){return e.id?{id:e.id,projectKey:e.projectKey,contentId:e.contentId,normalizedPath:e.normalizedPath,originalUrl:e.originalUrl,selector:e.selector,selectorStrategy:e.selectorStrategy==="stable-attribute"?"stable-attribute":"css-path",elementFingerprint:U(e.elementFingerprintJson,{tagName:"",width:0,height:0}),cssSnapshot:D(e.cssSnapshotJson),comment:e.comment,status:e.status==="resolved"?"resolved":"open",authorEmail:e.authorEmail,createdAt:e.createdAt,updatedAt:e.updatedAt,resolvedAt:e.resolvedAt||void 0,resolvedBy:e.resolvedBy||void 0}:null}function U(e,o){try{return e?JSON.parse(e):o}catch{return o}}function D(e){const o=U(e,{});return{margin:o.margin??"",marginTop:o.marginTop??"",marginRight:o.marginRight??"",marginBottom:o.marginBottom??"",marginLeft:o.marginLeft??"",padding:o.padding??"",paddingTop:o.paddingTop??"",paddingRight:o.paddingRight??"",paddingBottom:o.paddingBottom??"",paddingLeft:o.paddingLeft??"",border:o.border??"",borderTopWidth:o.borderTopWidth??"",borderRightWidth:o.borderRightWidth??"",borderBottomWidth:o.borderBottomWidth??"",borderLeftWidth:o.borderLeftWidth??"",fontFamily:o.fontFamily??"",fontSize:o.fontSize??"",lineHeight:o.lineHeight??"",color:o.color??"",backgroundColor:o.backgroundColor??"",width:o.width??0,height:o.height??0}}function Q(e){return e==="admin"?["create","read","resolve"]:e==="developer"?["read","resolve"]:["create","read"]}async function X(e){return await Y(),new Promise((o,t)=>{var s;const r=(s=window.google)==null?void 0:s.accounts.oauth2.initTokenClient({client_id:e,scope:G,callback:i=>{if(i.error||!i.access_token){t(new Error(i.error??"Google OAuth did not return an access token"));return}o(i.access_token)}});r==null||r.requestAccessToken({prompt:""})})}function Y(){var e;return(e=window.google)!=null&&e.accounts.oauth2?Promise.resolve():new Promise((o,t)=>{const r=document.querySelector('script[src="https://accounts.google.com/gsi/client"]');if(r){r.addEventListener("load",()=>o(),{once:!0}),r.addEventListener("error",()=>t(new Error("Google Identity failed to load")),{once:!0});return}const s=document.createElement("script");s.src="https://accounts.google.com/gsi/client",s.async=!0,s.defer=!0,s.onload=()=>o(),s.onerror=()=>t(new Error("Google Identity failed to load")),document.head.append(s)})}function O(e){return new URL(e,window.location.href).pathname.replace(/\/+$/,"")||"/"}const z=p.createContext(null);function V({config:e,children:o}){const t=p.useMemo(()=>e.adapter?e.adapter:$({googleClientId:_(e.googleClientId,"googleClientId"),spreadsheetId:_(e.spreadsheetId,"spreadsheetId"),feedbackSheetName:e.sheetName??"Feedback"}),[e.adapter,e.googleClientId,e.sheetName,e.spreadsheetId]),r=e.currentUrl??window.location.href,s=(e.normalizeUrl??O)(r),[i,m]=p.useState(),[k,d]=p.useState([]),[c,a]=p.useState([]),l=p.useCallback(async()=>{const g=await t.listFeedback({projectKey:e.projectKey,contentId:e.contentId,normalizedPath:s});a(g)},[t,e.contentId,e.projectKey,s]);p.useEffect(()=>{let g=!0;async function v(){const[f,y]=await Promise.all([t.getCurrentUser(),t.getPermissions(e.projectKey)]);g&&(m(f),d(y),await l())}return v(),()=>{g=!1}},[t,e.projectKey,l]);const C=p.useCallback(async g=>{const v=await t.createFeedback(g);return a(f=>[v,...f]),v},[t]),h=p.useCallback(async g=>{const v=await t.resolveFeedback(g,(i==null?void 0:i.email)??"");return a(f=>f.map(y=>y.id===g?v:y)),v},[t,i==null?void 0:i.email]),b=p.useMemo(()=>({config:e,adapter:t,currentUser:i,permissions:k,feedback:c,normalizedPath:s,refreshFeedback:l,createFeedback:C,resolveFeedback:h}),[t,e,C,i,c,s,k,l,h]);return n.jsx(z.Provider,{value:b,children:o})}function K(){const e=p.useContext(z);if(!e)throw new Error("useReviewLens must be used inside ReviewLensProvider");return e}function _(e,o){if(!e)throw new Error(`review-lens-react requires config.${o} when no adapter is provided`);return e}const Z=["data-review-id","data-testid","data-test-id","aria-label","name"];function A(e){const o=e.getBoundingClientRect(),t=ee(e);return{selector:t.selector,selectorStrategy:t.strategy,fingerprint:oe(e,o),cssSnapshot:ne(e,o),rect:o}}function ee(e){for(const o of Z){const t=e.getAttribute(o);if(t)return{selector:`[${o}="${q(t)}"]`,strategy:"stable-attribute"}}return e.id?{selector:`#${q(e.id)}`,strategy:"stable-attribute"}:{selector:te(e),strategy:"css-path"}}function te(e){const o=[];let t=e;for(;t&&t.nodeType===Node.ELEMENT_NODE&&t!==document.body;){const r=t.parentElement,s=t.tagName.toLowerCase();if(!r){o.unshift(s);break}const i=t.tagName,m=Array.from(r.children).filter(d=>d.tagName===i),k=m.indexOf(t)+1;o.unshift(m.length>1?`${s}:nth-of-type(${k})`:s),t=r}return o.join(" > ")}function oe(e,o){var t;return{tagName:e.tagName.toLowerCase(),id:e.id||void 0,className:e.getAttribute("class")||void 0,textSnippet:((t=e.textContent)==null?void 0:t.trim().slice(0,80))||void 0,ariaLabel:e.getAttribute("aria-label")||void 0,width:Math.round(o.width),height:Math.round(o.height)}}function ne(e,o){const t=window.getComputedStyle(e);return{margin:P(t.marginTop,t.marginRight,t.marginBottom,t.marginLeft),marginTop:t.marginTop,marginRight:t.marginRight,marginBottom:t.marginBottom,marginLeft:t.marginLeft,padding:P(t.paddingTop,t.paddingRight,t.paddingBottom,t.paddingLeft),paddingTop:t.paddingTop,paddingRight:t.paddingRight,paddingBottom:t.paddingBottom,paddingLeft:t.paddingLeft,border:P(t.borderTopWidth,t.borderRightWidth,t.borderBottomWidth,t.borderLeftWidth),borderTopWidth:t.borderTopWidth,borderRightWidth:t.borderRightWidth,borderBottomWidth:t.borderBottomWidth,borderLeftWidth:t.borderLeftWidth,fontFamily:t.fontFamily,fontSize:t.fontSize,lineHeight:t.lineHeight,color:t.color,backgroundColor:t.backgroundColor,width:Math.round(o.width),height:Math.round(o.height)}}function P(e,o,t,r){return e===o&&o===t&&t===r?e:`${e} ${o} ${t} ${r}`}function q(e){return typeof CSS<"u"&&typeof CSS.escape=="function"?CSS.escape(e):e.replace(/["\\]/g,"\\$&")}function re({open:e,onOpenChange:o,placement:t="top-right",showResolved:r=!1}){const{config:s,currentUser:i,feedback:m,normalizedPath:k,permissions:d,createFeedback:c,resolveFeedback:a}=K(),[l,C]=p.useState(),[h,b]=p.useState(),[g,v]=p.useState(""),[f,y]=p.useState(),E=d.includes("create"),F=d.includes("resolve"),T=p.useMemo(()=>m.filter(u=>r||u.status!=="resolved"),[m,r]);p.useEffect(()=>{e||(C(void 0),b(void 0),v(""))},[e]);const B=p.useCallback(u=>{const N=u.target instanceof Element?u.target:null;if(N)return N.closest("[data-review-lens-ui]")?null:N;const L=document.elementFromPoint(u.clientX,u.clientY);return!L||L.closest("[data-review-lens-ui]")?null:L},[]);if(p.useEffect(()=>{if(!e||h)return;function u(L){const I=B(L);C(I?A(I):void 0)}function N(L){const I=B(L);I&&(L.preventDefault(),L.stopPropagation(),b(A(I)))}return window.addEventListener("mousemove",u,!0),window.addEventListener("click",N,!0),()=>{window.removeEventListener("mousemove",u,!0),window.removeEventListener("click",N,!0)}},[B,h,e]),!e)return null;const x=h??l;async function le(){!h||!g.trim()||!i||!E||(await c({projectKey:s.projectKey,contentId:s.contentId,normalizedPath:k,originalUrl:s.currentUrl??window.location.href,selector:h.selector,selectorStrategy:h.selectorStrategy,elementFingerprint:h.fingerprint,cssSnapshot:h.cssSnapshot,comment:g.trim(),authorEmail:i.email}),v(""),b(void 0))}return n.jsxs("div",{className:"review-lens-root","data-review-lens-ui":!0,children:[x?n.jsx(se,{target:x,locked:!!h}):null,n.jsx(ie,{feedback:T,selectedFeedback:f,onSelect:y}),n.jsxs("aside",{className:`review-lens-panel review-lens-panel--${t}`,"data-review-lens-ui":!0,children:[n.jsxs("header",{className:"review-lens-panel__header",children:[n.jsxs("div",{children:[n.jsx("p",{className:"review-lens-kicker",children:"Review Lens"}),n.jsx("h2",{children:h?"Element locked":"Inspecting"})]}),n.jsx("button",{type:"button",onClick:()=>o==null?void 0:o(!1),children:"Close"})]}),x?n.jsx(ae,{target:x}):n.jsx("p",{children:"Move over the app to inspect."}),h?n.jsxs("form",{className:"review-lens-feedback-form",onSubmit:u=>{u.preventDefault(),le()},children:[n.jsx("label",{htmlFor:"review-lens-comment",children:"Feedback"}),n.jsx("textarea",{id:"review-lens-comment",value:g,disabled:!E,onChange:u=>v(u.target.value),placeholder:E?"Describe the UX issue...":"You do not have permission to comment."}),n.jsxs("div",{className:"review-lens-actions",children:[n.jsx("button",{type:"button",onClick:()=>b(void 0),children:"Unlock"}),n.jsx("button",{type:"submit",disabled:!g.trim()||!E,children:"Save feedback"})]})]}):null,n.jsxs("section",{className:"review-lens-comments",children:[n.jsx("h3",{children:"Page feedback"}),T.length===0?n.jsx("p",{children:"No feedback for this view."}):null,T.map(u=>n.jsxs("article",{className:(f==null?void 0:f.id)===u.id?"review-lens-comment review-lens-comment--selected":"review-lens-comment",children:[n.jsx("p",{children:u.comment}),n.jsx("span",{children:u.authorEmail}),u.status==="open"&&F?n.jsx("button",{type:"button",onClick:()=>void a(u.id),children:"Resolve"}):null]},u.id))]})]})]})}function se({target:e,locked:o}){const t=ce(e);return n.jsxs("div",{className:o?"review-lens-highlight review-lens-highlight--locked":"review-lens-highlight",style:{top:t.margin.top,left:t.margin.left,width:t.margin.width,height:t.margin.height},children:[n.jsx("div",{className:"review-lens-highlight__border",style:{top:t.border.top-t.margin.top,left:t.border.left-t.margin.left,width:t.border.width,height:t.border.height}}),n.jsx("div",{className:"review-lens-highlight__padding",style:{top:t.padding.top-t.margin.top,left:t.padding.left-t.margin.left,width:t.padding.width,height:t.padding.height}}),n.jsx("div",{className:"review-lens-highlight__content",style:{top:t.content.top-t.margin.top,left:t.content.left-t.margin.left,width:t.content.width,height:t.content.height}}),n.jsxs("div",{className:"review-lens-highlight__label",children:[Math.round(e.rect.width)," x ",Math.round(e.rect.height)]})]})}function ie({feedback:e,selectedFeedback:o,onSelect:t}){return n.jsx(n.Fragment,{children:e.map(r=>{const s=de(r.selector),i=s==null?void 0:s.getBoundingClientRect();return i?n.jsx("button",{type:"button",className:(o==null?void 0:o.id)===r.id?"review-lens-marker review-lens-marker--selected":"review-lens-marker",style:{top:i.top,left:i.left+i.width},onClick:()=>t(r),"aria-label":`Open feedback from ${r.authorEmail}`},r.id):null})})}function ae({target:e}){const o=[["Selector",e.selector],["Size",`${e.cssSnapshot.width} x ${e.cssSnapshot.height}`],["Margin",e.cssSnapshot.margin],["Padding",e.cssSnapshot.padding],["Border",e.cssSnapshot.border],["Font",`${e.cssSnapshot.fontSize} / ${e.cssSnapshot.lineHeight}`],["Family",e.cssSnapshot.fontFamily],["Color",e.cssSnapshot.color],["Background",e.cssSnapshot.backgroundColor]];return n.jsx("dl",{className:"review-lens-metrics",children:o.map(([t,r])=>n.jsxs("div",{children:[n.jsx("dt",{children:t}),n.jsx("dd",{children:r})]},t))})}function de(e){try{return document.querySelector(e)}catch{return null}}function ce(e){const o={top:S(e.cssSnapshot.marginTop),right:S(e.cssSnapshot.marginRight),bottom:S(e.cssSnapshot.marginBottom),left:S(e.cssSnapshot.marginLeft)},t={top:S(e.cssSnapshot.borderTopWidth),right:S(e.cssSnapshot.borderRightWidth),bottom:S(e.cssSnapshot.borderBottomWidth),left:S(e.cssSnapshot.borderLeftWidth)},r={top:S(e.cssSnapshot.paddingTop),right:S(e.cssSnapshot.paddingRight),bottom:S(e.cssSnapshot.paddingBottom),left:S(e.cssSnapshot.paddingLeft)},s={top:e.rect.top,left:e.rect.left,width:Math.max(e.rect.width,0),height:Math.max(e.rect.height,0)},i={top:s.top-o.top,left:s.left-o.left,width:s.width+o.left+o.right,height:s.height+o.top+o.bottom},m={top:s.top+t.top,left:s.left+t.left,width:Math.max(s.width-t.left-t.right,0),height:Math.max(s.height-t.top-t.bottom,0)},k={top:m.top+r.top,left:m.left+r.left,width:Math.max(m.width-r.left-r.right,0),height:Math.max(m.height-r.top-r.bottom,0)};return{margin:i,border:s,padding:m,content:k}}function S(e){const o=Number.parseFloat(e||"0");return Number.isFinite(o)?o:0}w.ReviewLensOverlay=re,w.ReviewLensProvider=V,w.buildElementTarget=A,w.createGoogleSheetsAdapter=$,w.normalizeReviewUrl=O,w.useReviewLens=K,Object.defineProperty(w,Symbol.toStringTag,{value:"Module"})}));
1
+ (function(x,r){typeof exports=="object"&&typeof module<"u"?r(exports,require("react/jsx-runtime"),require("react")):typeof define=="function"&&define.amd?define(["exports","react/jsx-runtime","react"],r):(x=typeof globalThis<"u"?globalThis:x||self,r(x.ReviewLensReact={},x.jsxRuntime,x.React))})(this,(function(x,r,l){"use strict";const Ke=["https://www.googleapis.com/auth/spreadsheets","https://www.googleapis.com/auth/userinfo.email"].join(" "),Je="https://www.googleapis.com/oauth2/v3/userinfo";function le(e){const t=e.feedbackSheetName??"Feedback",n=e.messagesSheetName??"Messages",a=e.usersSheetName??"Users";let i,d;async function f(){return i??(i=tt(e.googleClientId)),i}async function b(c,w){const u=await f(),v=await fetch(`https://sheets.googleapis.com/v4/spreadsheets/${e.spreadsheetId}${c}`,{...w,headers:{Authorization:`Bearer ${u}`,"Content-Type":"application/json",...w==null?void 0:w.headers}});if(!v.ok)throw new Error(`Google Sheets request failed with ${v.status}`);return v.json()}async function p(c){return(await b(`/values/${encodeURIComponent(c)}`)).values??[]}return{async getCurrentUser(){if(!d){const c=await f(),w=await fetch(Je,{headers:{Authorization:`Bearer ${c}`}});if(!w.ok)throw new Error(`Google userinfo request failed with ${w.status}`);d=(await w.json()).email}if(!d)throw new Error("Google account did not return an email address");return{email:d}},async getPermissions(c){const[{email:w},u]=await Promise.all([this.getCurrentUser(),p(a)]),v=ne(u),E=w.toLowerCase(),N=v.find(A=>{var h;return((h=A.email)==null?void 0:h.toLowerCase())===E&&A.active!=="false"&&(!A.projectKey||A.projectKey===c)});return Ye((N==null?void 0:N.role)??"designer")},async listFeedback(c){return ne(await p(t)).map(pe).filter(u=>u!==null).filter(u=>u.projectKey===c.projectKey&&u.contentId===c.contentId&&u.normalizedPath===c.normalizedPath).sort((u,v)=>v.createdAt.localeCompare(u.createdAt))},async createFeedback(c){const w=new Date().toISOString(),u={...c,id:crypto.randomUUID(),attachments:[],createdAt:w,updatedAt:w};return await b(`/values/${encodeURIComponent(t)}:append?valueInputOption=RAW`,{method:"POST",body:JSON.stringify({values:[ce(u)]})}),u},async updateFeedback(c,w){const u=await p(t),v=u[0]??de,E=v.indexOf("id");if(E===-1)throw new Error(`Sheet ${t} is missing an id column`);const N=u.findIndex((m,k)=>k>0&&m[E]===c);if(N<1)throw new Error(`Feedback ${c} was not found`);const A=new Date().toISOString(),h=pe(he(v,u[N]));if(!h)throw new Error(`Feedback ${c} could not be parsed before updating`);const B={...h,...w,updatedAt:A},y=ce(B);return await b(`/values/${encodeURIComponent(t)}!A${N+1}:${et(de.length)}${N+1}?valueInputOption=RAW`,{method:"PUT",body:JSON.stringify({values:[y]})}),B},async listMessages(c){return ne(await p(n)).map(Ge).filter(u=>u!==null).filter(u=>u.feedbackId===c).sort((u,v)=>u.createdAt.localeCompare(v.createdAt))},async createMessage(c){const w={...c,id:crypto.randomUUID(),createdAt:new Date().toISOString()};return await b(`/values/${encodeURIComponent(n)}:append?valueInputOption=RAW`,{method:"POST",body:JSON.stringify({values:[Ve(w)]})}),w}}}const de=["id","projectKey","contentId","normalizedPath","originalUrl","selector","selectorStrategy","elementFingerprintJson","createdCssSnapshotJson","comment","status","severity","category","assigneeEmail","viewportWidth","viewportHeight","viewportPreset","screenshotUrl","screenshotThumbnailUrl","attachmentJson","authorEmail","createdAt","updatedAt","fixedCssSnapshotJson","fixedAt","fixedBy","resolvedAt","resolvedBy"],qe=["id","feedbackId","body","authorEmail","createdAt"];function ce(e){return[e.id,e.projectKey,e.contentId,e.normalizedPath,e.originalUrl,e.selector,e.selectorStrategy,JSON.stringify(e.elementFingerprint),JSON.stringify(e.createdCssSnapshot),e.comment,e.status,e.severity,e.category,e.assigneeEmail??"",String(e.viewportWidth),String(e.viewportHeight),e.viewportPreset,e.screenshotUrl??"",e.screenshotThumbnailUrl??"",JSON.stringify(e.attachments),e.authorEmail,e.createdAt,e.updatedAt,e.fixedCssSnapshot?JSON.stringify(e.fixedCssSnapshot):"",e.fixedAt??"",e.fixedBy??"",e.resolvedAt??"",e.resolvedBy??""]}function Ve(e){return qe.map(t=>e[t])}function ne(e){const[t,...n]=e;return t?n.map(a=>he(t,a)):[]}function he(e,t){return Object.fromEntries(e.map((n,a)=>[n,t[a]??""]))}function pe(e){return e.id?{id:e.id,projectKey:e.projectKey,contentId:e.contentId,normalizedPath:e.normalizedPath,originalUrl:e.originalUrl,selector:e.selector,selectorStrategy:e.selectorStrategy==="stable-attribute"?"stable-attribute":"css-path",elementFingerprint:re(e.elementFingerprintJson,{tagName:"",width:0,height:0}),createdCssSnapshot:ue(e.createdCssSnapshotJson),fixedCssSnapshot:e.fixedCssSnapshotJson?ue(e.fixedCssSnapshotJson):void 0,comment:e.comment,status:Xe(e.status),severity:Qe(e.severity),category:Ze(e.category),assigneeEmail:e.assigneeEmail||void 0,viewportWidth:Number(e.viewportWidth)||0,viewportHeight:Number(e.viewportHeight)||0,viewportPreset:Re(e.viewportPreset),screenshotUrl:e.screenshotUrl||void 0,screenshotThumbnailUrl:e.screenshotThumbnailUrl||void 0,attachments:re(e.attachmentJson,[]),authorEmail:e.authorEmail,createdAt:e.createdAt,updatedAt:e.updatedAt,fixedAt:e.fixedAt||void 0,fixedBy:e.fixedBy||void 0,resolvedAt:e.resolvedAt||void 0,resolvedBy:e.resolvedBy||void 0}:null}function Ge(e){return!e.id||!e.feedbackId?null:{id:e.id,feedbackId:e.feedbackId,body:e.body,authorEmail:e.authorEmail,createdAt:e.createdAt}}function re(e,t){try{return e?JSON.parse(e):t}catch{return t}}function ue(e){const t=re(e,{});return{margin:t.margin??"",marginTop:t.marginTop??"",marginRight:t.marginRight??"",marginBottom:t.marginBottom??"",marginLeft:t.marginLeft??"",padding:t.padding??"",paddingTop:t.paddingTop??"",paddingRight:t.paddingRight??"",paddingBottom:t.paddingBottom??"",paddingLeft:t.paddingLeft??"",border:t.border??"",borderTopWidth:t.borderTopWidth??"",borderRightWidth:t.borderRightWidth??"",borderBottomWidth:t.borderBottomWidth??"",borderLeftWidth:t.borderLeftWidth??"",fontFamily:t.fontFamily??"",fontSize:t.fontSize??"",lineHeight:t.lineHeight??"",color:t.color??"",backgroundColor:t.backgroundColor??"",borderRadius:t.borderRadius??"",width:t.width??0,height:t.height??0}}function Ye(e){return e==="admin"?["create","read","reply","update","assign"]:e==="developer"?["read","reply","update","assign"]:["create","read","reply"]}function Xe(e){return e==="in_progress"||e==="needs_clarification"||e==="fixed"||e==="wontfix"||e==="resolved"?e:"open"}function Qe(e){return e==="low"||e==="high"?e:"medium"}function Ze(e){return e==="visual"||e==="copy"||e==="accessibility"||e==="responsive"?e:"bug"}function Re(e){return e==="mobile"||e==="tablet"||e==="desktop"?e:"custom"}function et(e){let t=e,n="";for(;t>0;){const a=(t-1)%26;n=String.fromCharCode(65+a)+n,t=Math.floor((t-a)/26)}return n}async function tt(e){return await nt(),new Promise((t,n)=>{var i;const a=(i=window.google)==null?void 0:i.accounts.oauth2.initTokenClient({client_id:e,scope:Ke,callback:d=>{if(d.error||!d.access_token){n(new Error(d.error??"Google OAuth did not return an access token"));return}t(d.access_token)}});a==null||a.requestAccessToken({prompt:""})})}function nt(){var e;return(e=window.google)!=null&&e.accounts.oauth2?Promise.resolve():new Promise((t,n)=>{const a=document.querySelector('script[src="https://accounts.google.com/gsi/client"]');if(a){a.addEventListener("load",()=>t(),{once:!0}),a.addEventListener("error",()=>n(new Error("Google Identity failed to load")),{once:!0});return}const i=document.createElement("script");i.src="https://accounts.google.com/gsi/client",i.async=!0,i.defer=!0,i.onload=()=>t(),i.onerror=()=>n(new Error("Google Identity failed to load")),document.head.append(i)})}function ge(e){return new URL(e,window.location.href).pathname.replace(/\/+$/,"")||"/"}const fe=l.createContext(null);function rt({config:e,children:t}){const n=l.useMemo(()=>e.adapter?e.adapter:le({googleClientId:ve(e.googleClientId,"googleClientId"),spreadsheetId:ve(e.spreadsheetId,"spreadsheetId"),feedbackSheetName:e.sheetName??"Feedback"}),[e.adapter,e.googleClientId,e.sheetName,e.spreadsheetId]),a=e.currentUrl??window.location.href,i=(e.normalizeUrl??ge)(a),[d,f]=l.useState(),[b,p]=l.useState([]),[c,w]=l.useState([]),u=l.useCallback(async()=>{const y=await n.listFeedback({projectKey:e.projectKey,contentId:e.contentId,normalizedPath:i});w(y)},[n,e.contentId,e.projectKey,i]);l.useEffect(()=>{let y=!0;async function m(){const[k,$]=await Promise.all([n.getCurrentUser(),n.getPermissions(e.projectKey)]);y&&(f(k),p($),await u())}return m(),()=>{y=!1}},[n,e.projectKey,u]);const v=l.useCallback(async y=>{const m=await n.createFeedback(y);return w(k=>[m,...k]),m},[n]),E=l.useCallback(async(y,m)=>{const k=await n.updateFeedback(y,m);return w($=>$.map(z=>z.id===y?k:z)),k},[n]),N=l.useCallback(y=>n.listMessages(y),[n]),A=l.useCallback(y=>n.createMessage(y),[n]),h=l.useCallback(async(y,m)=>{const k=e.uploadAttachment??n.uploadAttachment;if(!k)throw new Error("Review Lens attachment upload is not configured");return k(y,m)},[n,e]),B=l.useMemo(()=>({config:e,adapter:n,currentUser:d,permissions:b,feedback:c,normalizedPath:i,refreshFeedback:u,createFeedback:v,updateFeedback:E,listMessages:N,createMessage:A,uploadAttachment:h}),[n,e,v,d,c,i,b,u,E,N,A,h]);return r.jsx(fe.Provider,{value:B,children:t})}function be(){const e=l.useContext(fe);if(!e)throw new Error("useReviewLens must be used inside ReviewLensProvider");return e}function ve(e,t){if(!e)throw new Error(`review-lens-react requires config.${t} when no adapter is provided`);return e}const st=["data-review-id","data-testid","data-test-id","aria-label","name"];function W(e){const t=e.getBoundingClientRect(),n=at(e);return{selector:n.selector,selectorStrategy:n.strategy,fingerprint:ot(e,t),cssSnapshot:lt(e,t),rect:t}}function at(e){for(const t of st){const n=e.getAttribute(t);if(n)return{selector:`[${t}="${we(n)}"]`,strategy:"stable-attribute"}}return e.id?{selector:`#${we(e.id)}`,strategy:"stable-attribute"}:{selector:it(e),strategy:"css-path"}}function it(e){const t=[];let n=e;for(;n&&n.nodeType===Node.ELEMENT_NODE&&n!==document.body;){const a=n.parentElement,i=n.tagName.toLowerCase();if(!a){t.unshift(i);break}const d=n.tagName,f=Array.from(a.children).filter(p=>p.tagName===d),b=f.indexOf(n)+1;t.unshift(f.length>1?`${i}:nth-of-type(${b})`:i),n=a}return t.join(" > ")}function ot(e,t){var n;return{tagName:e.tagName.toLowerCase(),id:e.id||void 0,className:e.getAttribute("class")||void 0,textSnippet:((n=e.textContent)==null?void 0:n.trim().slice(0,80))||void 0,ariaLabel:e.getAttribute("aria-label")||void 0,width:Math.round(t.width),height:Math.round(t.height)}}function lt(e,t){const n=window.getComputedStyle(e);return{margin:se(n.marginTop,n.marginRight,n.marginBottom,n.marginLeft),marginTop:n.marginTop,marginRight:n.marginRight,marginBottom:n.marginBottom,marginLeft:n.marginLeft,padding:se(n.paddingTop,n.paddingRight,n.paddingBottom,n.paddingLeft),paddingTop:n.paddingTop,paddingRight:n.paddingRight,paddingBottom:n.paddingBottom,paddingLeft:n.paddingLeft,border:se(n.borderTopWidth,n.borderRightWidth,n.borderBottomWidth,n.borderLeftWidth),borderTopWidth:n.borderTopWidth,borderRightWidth:n.borderRightWidth,borderBottomWidth:n.borderBottomWidth,borderLeftWidth:n.borderLeftWidth,fontFamily:n.fontFamily,fontSize:n.fontSize,lineHeight:n.lineHeight,color:n.color,backgroundColor:n.backgroundColor,borderRadius:n.borderRadius,width:Math.round(t.width),height:Math.round(t.height)}}function se(e,t,n,a){return e===t&&t===n&&n===a?e:`${e} ${t} ${n} ${a}`}function we(e){return typeof CSS<"u"&&typeof CSS.escape=="function"?CSS.escape(e):e.replace(/["\\]/g,"\\$&")}const dt=[{label:"Desktop",value:"desktop"},{label:"Tablet",value:"tablet"},{label:"Mobile",value:"mobile"}],me=["open","in_progress","needs_clarification","fixed","wontfix","resolved"],ye=["low","medium","high"],Se=["bug","visual","copy","accessibility","responsive"],Z={open:"Open",in_progress:"In progress",needs_clarification:"Needs clarification",fixed:"Fixed",wontfix:"Won't fix",resolved:"Resolved"},H={low:"Low",medium:"Medium",high:"High"},j={bug:"Bug",visual:"Visual",copy:"Copy",accessibility:"Accessibility",responsive:"Responsive"};function ct({open:e,onOpenChange:t,placement:n="top-right",showResolved:a=!1,syncSelectionToUrl:i=!1,responsivePresets:d=dt}){var Oe;const{adapter:f,config:b,currentUser:p,feedback:c,normalizedPath:w,permissions:u,createFeedback:v,updateFeedback:E,listMessages:N,createMessage:A,uploadAttachment:h}=be(),[B,y]=l.useState(),[m,k]=l.useState(),[$,z]=l.useState(""),[xe,Ft]=l.useState("medium"),[Ae,Mt]=l.useState("visual"),[Le,Fe]=l.useState(""),[Me,Tt]=l.useState(((Oe=d[0])==null?void 0:Oe.value)??"desktop"),[S,J]=l.useState(),[M,P]=l.useState("review"),[It,Te]=l.useState(!1),[q,Ie]=l.useState("all"),[V,Be]=l.useState("all"),[G,$e]=l.useState("all"),[Y,Pe]=l.useState("all"),[X,_e]=l.useState("all"),[Bt,$t]=l.useState(!1),[Pt,Ue]=l.useState({}),[ae,ie]=l.useState(""),ee=l.useRef(null),T=!!p,I=u.includes("create"),We=u.includes("reply"),De=u.includes("update"),_t=u.includes("assign"),U=B??m,Ut=!!m,Wt=!!(b.captureScreenshot&&(b.uploadAttachment||f.uploadAttachment)),Dt=l.useMemo(()=>{const s=c.map(g=>g.assigneeEmail).filter(g=>!!g);return p!=null&&p.email&&s.push(p.email),Array.from(new Set(s)).sort((g,o)=>g.localeCompare(o))},[p==null?void 0:p.email,c]),F=l.useMemo(()=>c.filter(s=>a||s.status!=="resolved").filter(s=>q==="all"||s.status===q).filter(s=>V==="all"||s.severity===V).filter(s=>G==="all"||s.category===G).filter(s=>Y==="all"||s.assigneeEmail===Y).filter(s=>X==="all"||s.viewportPreset===X),[Y,G,c,V,a,q,X]),zt=[q,V,G,Y,X].filter(s=>s!=="all").length;l.useEffect(()=>{e||(y(void 0),k(void 0),z(""),ie(""),P("review"))},[e]),l.useEffect(()=>{T||(y(void 0),k(void 0))},[T]),l.useEffect(()=>{!m||M!=="review"||window.requestAnimationFrame(()=>{var s,g,o;(g=(s=ee.current)==null?void 0:s.scrollIntoView)==null||g.call(s,{block:"nearest"}),(o=ee.current)==null||o.focus()})},[m,M]),l.useEffect(()=>{if(!S)return;let s=!0;return N(S.id).then(g=>{s&&Ue(o=>({...o,[S.id]:g}))}),()=>{s=!1}},[N,S]),l.useEffect(()=>{if(!e||!i||S||c.length===0)return;const s=new URL(window.location.href).searchParams.get("reviewLensFeedback"),g=c.find(o=>o.id===s);g&&Q(g,{syncUrl:!1})},[c,e,S,i]),l.useEffect(()=>{if(!e)return;function s(o){var C;if(o.key==="Shift"&&Te(!0),o.key==="Escape"){o.preventDefault(),t==null||t(!1);return}Et(o.target)||((o.key==="n"||o.key==="ArrowDown")&&(o.preventDefault(),ze(1)),(o.key==="p"||o.key==="ArrowUp")&&(o.preventDefault(),ze(-1)),o.key==="c"&&(o.preventDefault(),P("review"),(C=ee.current)==null||C.focus()),o.key==="f"&&S&&De&&(o.preventDefault(),je(S)))}function g(o){o.key==="Shift"&&Te(!1)}return window.addEventListener("keydown",s),window.addEventListener("keyup",g),()=>{window.removeEventListener("keydown",s),window.removeEventListener("keyup",g)}});const oe=l.useCallback(s=>{const g=s.target instanceof Element?s.target:null;if(g)return g.closest("[data-review-lens-ui]")?null:g;const o=document.elementFromPoint(s.clientX,s.clientY);return!o||o.closest("[data-review-lens-ui]")?null:o},[]);if(l.useEffect(()=>{if(!e||!T)return;function s(o){const C=oe(o);y(C?W(C):void 0)}function g(o){const C=oe(o);C&&(o.preventDefault(),o.stopPropagation(),k(W(C)),P("review"))}return window.addEventListener("mousemove",s,!0),window.addEventListener("click",g,!0),()=>{window.removeEventListener("mousemove",s,!0),window.removeEventListener("click",g,!0)}},[T,oe,e]),!e)return null;function Q(s,g={syncUrl:!0}){var C;if(J(s),k(void 0),P("feedback"),i&&g.syncUrl!==!1){const te=new URL(window.location.href);te.searchParams.set("reviewLensFeedback",s.id),window.history.replaceState({},"",te)}const o=D(s.selector);if(!o){y(void 0);return}(C=o.scrollIntoView)==null||C.call(o,{behavior:"smooth",block:"center",inline:"center"}),window.requestAnimationFrame(()=>{y(W(o))})}function ze(s){if(F.length===0)return;const g=S?F.findIndex(C=>C.id===S.id):-1,o=g<0?s>0?0:F.length-1:(g+s+F.length)%F.length;Q(F[o])}async function He(){if(!m||!$.trim()||!p||!I)return;let s=await v({projectKey:b.projectKey,contentId:b.contentId,normalizedPath:w,originalUrl:b.currentUrl??window.location.href,selector:m.selector,selectorStrategy:m.selectorStrategy,elementFingerprint:m.fingerprint,createdCssSnapshot:m.cssSnapshot,comment:$.trim(),status:"open",severity:xe,category:Ae,assigneeEmail:Le.trim()||void 0,viewportWidth:window.innerWidth,viewportHeight:window.innerHeight,viewportPreset:Me,screenshotUrl:void 0,screenshotThumbnailUrl:void 0,authorEmail:p.email});if(b.captureScreenshot)try{const g=await b.captureScreenshot(m),o=await h(s.id,{type:"screenshot",data:g,createdBy:p.email});s=await E(s.id,{attachments:[o],screenshotUrl:o.url,screenshotThumbnailUrl:o.thumbnailUrl})}catch{}z(""),Fe(""),k(void 0),y(void 0),P("feedback"),J(s)}async function Ht(s,g){const o=new Date().toISOString(),C=g==="resolved"?{status:g,resolvedAt:o,resolvedBy:p==null?void 0:p.email}:{status:g},te=await E(s.id,C);J(te)}async function je(s){const g=D(s.selector);if(!g||!p)return;const o=W(g),C=await E(s.id,{status:"fixed",fixedCssSnapshot:o.cssSnapshot,fixedAt:new Date().toISOString(),fixedBy:p.email});J(C)}async function jt(s){if(!ae.trim()||!p||!We)return;const g=await A({feedbackId:s.id,body:ae.trim(),authorEmail:p.email});Ue(o=>({...o,[s.id]:[...o[s.id]??[],g]})),ie("")}return r.jsxs("div",{className:"review-lens-root","data-review-lens-ui":!0,children:[T&&U?r.jsx(ft,{target:U,locked:!!m}):null,T&&m&&B&&It?r.jsx(bt,{from:m,to:B}):null,T?r.jsxs(r.Fragment,{children:[r.jsx(vt,{feedback:F,selectedFeedback:S,onSelect:Q}),r.jsx(mt,{feedback:F,selectedFeedback:S,onSelect:Q})]}):null,r.jsxs("aside",{className:`review-lens-panel review-lens-panel--${n}`,"data-review-lens-ui":!0,children:[r.jsxs("header",{className:"review-lens-panel__header",children:[r.jsxs("div",{children:[r.jsx("p",{className:"review-lens-kicker",children:"Review Lens"}),r.jsx("h2",{children:M==="summary"?"Summary":M==="feedback"?"Feedback":m?"Element locked":"Inspecting"})]}),r.jsx("button",{type:"button",onClick:()=>t==null?void 0:t(!1),children:"Close"})]}),r.jsxs("div",{className:"review-lens-panel__body",children:[r.jsxs("div",{className:"review-lens-mode-switch",role:"tablist","aria-label":"Review Lens mode",children:[r.jsx("button",{type:"button",role:"tab","aria-selected":M==="review",onClick:()=>P("review"),children:"Review"}),r.jsxs("button",{type:"button",role:"tab","aria-selected":M==="feedback",onClick:()=>P("feedback"),children:["Feedback ",r.jsx("span",{children:F.length})]}),r.jsx("button",{type:"button",role:"tab","aria-selected":M==="summary",onClick:()=>P("summary"),children:"Summary"})]}),M==="review"?r.jsxs("div",{className:"review-lens-review-pane",role:"tabpanel",children:[r.jsxs("div",{className:"review-lens-inspection",children:[T?null:r.jsx("p",{children:"Authenticate with Google to inspect this page."}),T&&U?r.jsxs(r.Fragment,{children:[r.jsx(yt,{target:U}),r.jsx(ke,{title:"Accessibility",items:St(U)}),r.jsx(ke,{title:"Design tokens",items:kt(U.cssSnapshot,b.designTokens)})]}):null,T&&!U?r.jsx("p",{children:"Move over the app to inspect."}):null]}),Ut?r.jsx("div",{className:"review-lens-composer-panel",children:r.jsxs("form",{className:"review-lens-feedback-form",onSubmit:s=>{s.preventDefault(),He()},children:[r.jsx("label",{htmlFor:"review-lens-comment",children:"New feedback"}),r.jsx("textarea",{ref:ee,id:"review-lens-comment",value:$,disabled:!I,onChange:s=>z(s.target.value),onKeyDown:s=>{s.key==="Enter"&&s.metaKey&&(s.preventDefault(),He())},placeholder:I?"Describe the UX issue...":"You do not have permission to comment."}),r.jsxs("div",{className:"review-lens-form-grid",children:[r.jsxs("label",{children:["Severity",r.jsx("select",{value:xe,onChange:s=>Ft(s.target.value),disabled:!I,children:ye.map(s=>r.jsx("option",{value:s,children:H[s]},s))})]}),r.jsxs("label",{children:["Type",r.jsx("select",{value:Ae,onChange:s=>Mt(s.target.value),disabled:!I,children:Se.map(s=>r.jsx("option",{value:s,children:j[s]},s))})]}),r.jsxs("label",{children:["Assignee",r.jsx("input",{value:Le,onChange:s=>Fe(s.target.value),disabled:!I,placeholder:"optional@email.com"})]}),r.jsxs("label",{children:["Viewport",r.jsx("select",{value:Me,onChange:s=>Tt(s.target.value),disabled:!I,children:d.map(s=>r.jsx("option",{value:s.value,children:s.label},s.value))})]})]}),I?r.jsxs("p",{className:"review-lens-feedback-form__hint",children:["Press ",r.jsx("kbd",{children:"Command"})," + ",r.jsx("kbd",{children:"Enter"})," to submit.",Wt?" Screenshot capture runs after save.":""]}):null,r.jsx("div",{className:"review-lens-actions",children:r.jsx("button",{type:"submit",disabled:!$.trim()||!I,children:"Save feedback"})})]})}):null]}):null,M==="feedback"?r.jsxs("div",{className:"review-lens-comments",children:[r.jsx(ht,{open:Bt,activeCount:zt,statusFilter:q,severityFilter:V,categoryFilter:G,assigneeFilter:Y,viewportFilter:X,assignees:Dt,responsivePresets:d,onStatusChange:Ie,onSeverityChange:Be,onCategoryChange:$e,onAssigneeChange:Pe,onViewportChange:_e,onToggle:()=>$t(s=>!s),onClear:()=>{Ie("all"),Be("all"),$e("all"),Pe("all"),_e("all")}}),r.jsxs("div",{className:"review-lens-list-panel",children:[r.jsxs("div",{className:"review-lens-comments__header",children:[r.jsx("h3",{children:"All feedback"}),r.jsx("span",{children:F.length})]}),r.jsxs("div",{className:"review-lens-comments__list",children:[F.length===0?r.jsx("p",{children:"No feedback for this view."}):null,F.map(s=>r.jsx(pt,{item:s,selected:(S==null?void 0:S.id)===s.id,onSelect:Q},s.id))]})]}),S?r.jsxs("div",{className:"review-lens-selected-panel",children:[r.jsx("div",{className:"review-lens-selected-panel__label",children:"Selected feedback"}),r.jsx(ut,{item:S,messages:Pt[S.id]??[],messageDraft:ae,canReply:We,canUpdate:De,canAssign:_t,onMessageDraftChange:ie,onSubmitMessage:()=>void jt(S),onStatusChange:s=>void Ht(S,s),onAssigneeChange:s=>void E(S.id,{assigneeEmail:s.trim()||void 0}).then(J),onMarkFixed:()=>void je(S)},S.id)]}):r.jsxs("div",{className:"review-lens-selected-panel review-lens-selected-panel--empty",children:[r.jsx("div",{className:"review-lens-selected-panel__label",children:"Selected feedback"}),r.jsx("p",{children:"Select a feedback item above to review status, assignment, drift, and replies."})]})]}):null,M==="summary"?r.jsx(gt,{feedback:c}):null]})]})]})}function ht({open:e,activeCount:t,statusFilter:n,severityFilter:a,categoryFilter:i,assigneeFilter:d,viewportFilter:f,assignees:b,responsivePresets:p,onStatusChange:c,onSeverityChange:w,onCategoryChange:u,onAssigneeChange:v,onViewportChange:E,onToggle:N,onClear:A}){return r.jsxs("div",{className:"review-lens-filter-shell",children:[r.jsxs("div",{className:"review-lens-filter-bar",children:[r.jsxs("button",{type:"button","aria-expanded":e,onClick:N,children:["Filters",t>0?r.jsx("span",{children:t}):null]}),t>0?r.jsx("button",{type:"button",onClick:A,children:"Clear"}):null]}),e?r.jsxs("div",{className:"review-lens-filters",children:[r.jsxs("label",{children:["Status",r.jsxs("select",{"aria-label":"Filter status",value:n,onChange:h=>c(h.target.value),children:[r.jsx("option",{value:"all",children:"All statuses"}),me.map(h=>r.jsx("option",{value:h,children:Z[h]},h))]})]}),r.jsxs("label",{children:["Priority",r.jsxs("select",{"aria-label":"Filter severity",value:a,onChange:h=>w(h.target.value),children:[r.jsx("option",{value:"all",children:"All priorities"}),ye.map(h=>r.jsx("option",{value:h,children:H[h]},h))]})]}),r.jsxs("label",{children:["Type",r.jsxs("select",{"aria-label":"Filter type",value:i,onChange:h=>u(h.target.value),children:[r.jsx("option",{value:"all",children:"All types"}),Se.map(h=>r.jsx("option",{value:h,children:j[h]},h))]})]}),r.jsxs("label",{children:["Assignee",r.jsxs("select",{"aria-label":"Filter assignee",value:d,onChange:h=>v(h.target.value),children:[r.jsx("option",{value:"all",children:"All assignees"}),b.map(h=>r.jsx("option",{value:h,children:h},h))]})]}),r.jsxs("label",{children:["Viewport",r.jsxs("select",{"aria-label":"Filter viewport",value:f,onChange:h=>E(h.target.value),children:[r.jsx("option",{value:"all",children:"All viewports"}),p.map(h=>r.jsx("option",{value:h.value,children:h.label},h.value))]})]})]}):null]})}function pt({item:e,selected:t,onSelect:n}){const a=Ce(e);return r.jsxs("article",{tabIndex:0,className:["review-lens-comment",`review-lens-comment--${e.severity}`,t?"review-lens-comment--selected":""].filter(Boolean).join(" "),onClick:()=>n(e),onKeyDown:i=>{(i.key==="Enter"||i.key===" ")&&(i.preventDefault(),n(e))},children:[r.jsxs("div",{className:"review-lens-comment__header",children:[r.jsx("span",{children:Z[e.status]}),r.jsx("strong",{children:H[e.severity]})]}),r.jsxs("div",{className:"review-lens-comment__content",children:[r.jsx("p",{children:e.comment}),r.jsxs("span",{children:[e.authorEmail,e.assigneeEmail?` -> ${e.assigneeEmail}`:""]})]}),r.jsxs("div",{className:"review-lens-tags",children:[r.jsx("span",{children:j[e.category]}),r.jsx("span",{children:e.viewportPreset}),r.jsx("span",{children:a.label})]})]})}function ut({item:e,messages:t,messageDraft:n,canReply:a,canUpdate:i,canAssign:d,onMessageDraftChange:f,onSubmitMessage:b,onStatusChange:p,onAssigneeChange:c,onMarkFixed:w}){const u=Ce(e);return r.jsxs("section",{className:"review-lens-detail","aria-label":"Selected feedback detail",children:[r.jsxs("div",{className:"review-lens-detail__header",children:[r.jsxs("h3",{children:[j[e.category]," feedback"]}),r.jsx("strong",{children:H[e.severity]})]}),r.jsx("blockquote",{children:e.comment}),r.jsxs("dl",{className:"review-lens-detail-meta",children:[r.jsxs("div",{children:[r.jsx("dt",{children:"Target"}),r.jsx("dd",{children:u.label})]}),r.jsxs("div",{children:[r.jsx("dt",{children:"Viewport"}),r.jsx("dd",{children:e.viewportPreset})]}),e.screenshotUrl?r.jsxs("div",{children:[r.jsx("dt",{children:"Evidence"}),r.jsx("dd",{children:r.jsx("a",{href:e.screenshotUrl,target:"_blank",rel:"noreferrer",children:"Screenshot"})})]}):null]}),r.jsxs("div",{className:"review-lens-form-grid",children:[r.jsxs("label",{children:["Status",r.jsx("select",{value:e.status,disabled:!i,onChange:v=>p(v.target.value),children:me.map(v=>r.jsx("option",{value:v,children:Z[v]},v))})]}),r.jsxs("label",{children:["Assignee",r.jsx("input",{defaultValue:e.assigneeEmail??"",disabled:!d,onBlur:v=>c(v.target.value),placeholder:"optional@email.com"})]})]}),r.jsxs("div",{className:"review-lens-status-actions",children:[r.jsx("button",{type:"button",className:"review-lens-button-secondary",disabled:!i,onClick:w,children:"Mark fixed"}),r.jsx("button",{type:"button",className:"review-lens-button-primary",disabled:!i,onClick:()=>p("resolved"),children:"Resolve"})]}),r.jsxs("div",{className:"review-lens-thread",children:[r.jsxs("div",{className:"review-lens-thread__header",children:[r.jsx("h3",{children:"Thread"}),r.jsx("span",{children:t.length})]}),t.length===0?r.jsx("p",{children:"No replies yet."}):null,t.map(v=>r.jsxs("div",{className:"review-lens-thread__message",children:[r.jsx("p",{children:v.body}),r.jsx("span",{children:v.authorEmail})]},v.id)),r.jsx("textarea",{"aria-label":"Reply",value:n,disabled:!a,onChange:v=>f(v.target.value),placeholder:a?"Reply...":"You do not have permission to reply."}),r.jsx("div",{className:"review-lens-actions",children:r.jsx("button",{type:"button",disabled:!n.trim()||!a,onClick:b,children:"Reply"})})]})]})}function gt({feedback:e}){return r.jsxs("div",{className:"review-lens-summary",role:"tabpanel",children:[r.jsx(O,{title:"Status",values:K(e,t=>Z[t.status])}),r.jsx(O,{title:"Severity",values:K(e,t=>H[t.severity])}),r.jsx(O,{title:"Type",values:K(e,t=>j[t.category])}),r.jsx(O,{title:"Assignee",values:K(e,t=>t.assigneeEmail??"Unassigned")}),r.jsx(O,{title:"Viewport",values:K(e,t=>t.viewportPreset)})]})}function O({title:e,values:t}){return r.jsxs("section",{children:[r.jsx("h3",{children:e}),r.jsx("dl",{children:t.map(([n,a])=>r.jsxs("div",{children:[r.jsx("dt",{children:n}),r.jsx("dd",{children:a})]},n))})]})}function ft({target:e,locked:t}){const n=Lt(e),a=Ct(e.fingerprint);return r.jsxs("div",{className:t?"review-lens-highlight review-lens-highlight--locked":"review-lens-highlight",style:{top:n.margin.top,left:n.margin.left,width:n.margin.width,height:n.margin.height},children:[r.jsx("div",{className:"review-lens-highlight__border",style:{top:n.border.top-n.margin.top,left:n.border.left-n.margin.left,width:n.border.width,height:n.border.height}}),r.jsx("div",{className:"review-lens-highlight__padding",style:{top:n.padding.top-n.margin.top,left:n.padding.left-n.margin.left,width:n.padding.width,height:n.padding.height}}),r.jsx("div",{className:"review-lens-highlight__content",style:{top:n.content.top-n.margin.top,left:n.content.left-n.margin.left,width:n.content.width,height:n.content.height}}),r.jsxs("div",{className:"review-lens-highlight__label",children:[r.jsx("strong",{children:a}),r.jsxs("span",{children:[Math.round(e.rect.width)," x ",Math.round(e.rect.height)]})]})]})}function bt({from:e,to:t}){const n=Nt(e.rect,t.rect);return n.length===0?null:r.jsx(r.Fragment,{children:n.map(a=>r.jsx("div",{className:`review-lens-distance review-lens-distance--${a.axis}`,style:{top:a.top,left:a.left,width:a.width,height:a.height},children:r.jsx("span",{children:a.label})},a.key))})}function vt({feedback:e,selectedFeedback:t,onSelect:n}){return r.jsx(r.Fragment,{children:e.map(a=>r.jsx(wt,{feedback:a,selected:(t==null?void 0:t.id)===a.id,onSelect:n},a.id))})}function wt({feedback:e,selected:t,onSelect:n}){const a=l.useRef(null);return l.useLayoutEffect(()=>{let i=0;const d=()=>{i=0;const b=a.current,p=D(e.selector),c=p==null?void 0:p.getBoundingClientRect();!b||!c||(b.style.top=`${c.top}px`,b.style.left=`${c.right}px`,b.hidden=c.bottom<0||c.top>window.innerHeight)},f=()=>{i||(i=window.requestAnimationFrame(d))};return d(),window.addEventListener("scroll",f,!0),window.addEventListener("resize",f),()=>{i&&window.cancelAnimationFrame(i),window.removeEventListener("scroll",f,!0),window.removeEventListener("resize",f)}},[e.selector]),r.jsx("button",{ref:a,type:"button",className:t?"review-lens-marker review-lens-marker--selected":"review-lens-marker",onClick:()=>n(e),"aria-label":`Open feedback from ${e.authorEmail}`})}function mt({feedback:e,selectedFeedback:t,onSelect:n}){const a=e.map(i=>{const d=D(i.selector),f=d==null?void 0:d.getBoundingClientRect(),b=Math.max(document.documentElement.scrollHeight,document.body.scrollHeight,window.innerHeight);return!f||b<=0?null:{item:i,top:Math.min(100,Math.max(0,(f.top+window.scrollY)/b*100))}}).filter(i=>i!==null);return a.length===0?null:r.jsx("div",{className:"review-lens-minimap","data-review-lens-ui":!0,"aria-label":"Feedback map",children:a.map(i=>r.jsx("button",{type:"button",className:(t==null?void 0:t.id)===i.item.id?"review-lens-minimap__point review-lens-minimap__point--selected":"review-lens-minimap__point",style:{top:`${i.top}%`},onClick:()=>n(i.item),"aria-label":`Jump to feedback from ${i.item.authorEmail}`},i.item.id))})}function yt({target:e}){const t=[["Selector",e.selector],["Size",`${e.cssSnapshot.width} x ${e.cssSnapshot.height}`],["Margin",e.cssSnapshot.margin],["Padding",e.cssSnapshot.padding],["Border",e.cssSnapshot.border],["Radius",e.cssSnapshot.borderRadius],["Font",`${e.cssSnapshot.fontSize} / ${e.cssSnapshot.lineHeight}`],["Family",e.cssSnapshot.fontFamily],["Color",e.cssSnapshot.color],["Background",e.cssSnapshot.backgroundColor]];return r.jsx("dl",{className:"review-lens-metrics",children:t.map(([n,a])=>r.jsxs("div",{children:[r.jsx("dt",{children:n}),r.jsx("dd",{children:a})]},n))})}function ke({title:e,items:t}){return r.jsxs("section",{className:"review-lens-insights",children:[r.jsx("h3",{children:e}),t.length===0?r.jsx("p",{children:"No issues detected."}):null,t.length>0?r.jsx("ul",{children:t.map(n=>r.jsx("li",{children:n},n))}):null]})}function D(e){try{return document.querySelector(e)}catch{return null}}function Ce(e){const t=D(e.selector);if(!t)return{label:"Target missing",level:"warning"};const n=W(t);return n.fingerprint.tagName!==e.elementFingerprint.tagName?{label:"Element changed",level:"warning"}:Math.abs(n.fingerprint.width-e.elementFingerprint.width)>2||Math.abs(n.fingerprint.height-e.elementFingerprint.height)>2?{label:"Size changed",level:"warning"}:n.cssSnapshot.fontSize!==e.createdCssSnapshot.fontSize||n.cssSnapshot.color!==e.createdCssSnapshot.color||n.cssSnapshot.padding!==e.createdCssSnapshot.padding?{label:"Style changed",level:"warning"}:{label:"Target unchanged",level:"ok"}}function St(e){var p;const t=D(e.selector);if(!t)return["Selected element is no longer available."];const n=[],a=t.tagName.toLowerCase(),i=t.getAttribute("role"),d=["button","a","input","select","textarea"].includes(a)||i==="button"||i==="link",f=t.getAttribute("aria-label")||t.getAttribute("title")||((p=t.textContent)==null?void 0:p.trim());d&&!f&&n.push("Interactive element has no accessible name."),d&&(e.rect.width<44||e.rect.height<44)&&n.push("Tap target is smaller than 44 x 44."),a==="img"&&!t.getAttribute("alt")&&n.push("Image is missing alt text.");const b=/^h[1-6]$/.test(a)?Number(a.slice(1)):0;return b>1&&!document.querySelector(`h${b-1}`)&&n.push("Heading may skip the previous level."),xt(e.cssSnapshot.color,e.cssSnapshot.backgroundColor)&&n.push("Text contrast may be low."),n}function kt(e,t={}){const n=[];return _("Padding",e.padding,t.spacing,n),_("Margin",e.margin,t.spacing,n),_("Font size",e.fontSize,t.fontSize,n),_("Line height",e.lineHeight,t.lineHeight,n),_("Text color",e.color,t.color,n),_("Background",e.backgroundColor,t.color,n),_("Radius",e.borderRadius,t.radius,n),n}function _(e,t,n,a){!n||n.length===0||!t||n.includes(t)||a.push(`${e} ${t} is outside configured tokens.`)}function Ct(e){const t=e.id?`#${e.id}`:"",n=e.className?`.${e.className.split(/\s+/).filter(Boolean).slice(0,2).join(".")}`:"",a=e.ariaLabel?`[aria-label="${e.ariaLabel}"]`:"";return`${e.tagName}${t}${n}${a}`||e.tagName}function Nt(e,t){const n=[],a=(Math.max(e.left,t.left)+Math.min(e.right,t.right))/2,i=(Math.max(e.top,t.top)+Math.min(e.bottom,t.bottom))/2;if(e.right<=t.left||t.right<=e.left){const d=e.right<=t.left?e.right:t.right,f=e.right<=t.left?t.left:e.left;n.push({key:"horizontal",axis:"horizontal",top:Ne(i,0,window.innerHeight),left:d,width:Math.max(f-d,1),height:1,label:`${Math.round(f-d)}px`})}if(e.bottom<=t.top||t.bottom<=e.top){const d=e.bottom<=t.top?e.bottom:t.bottom,f=e.bottom<=t.top?t.top:e.top;n.push({key:"vertical",axis:"vertical",top:d,left:Ne(a,0,window.innerWidth),width:1,height:Math.max(f-d,1),label:`${Math.round(f-d)}px`})}return n}function Ne(e,t,n){return Math.min(Math.max(e,t),n)}function K(e,t){const n=new Map;for(const a of e){const i=t(a);n.set(i,(n.get(i)??0)+1)}return Array.from(n.entries()).sort((a,i)=>i[1]-a[1]||a[0].localeCompare(i[0]))}function Et(e){return e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement||e instanceof HTMLSelectElement||e instanceof HTMLElement&&e.isContentEditable}function xt(e,t){const n=Ee(e),a=Ee(t);return!n||!a||a.alpha===0?!1:At(n,a)<4.5}function Ee(e){const t=e.match(/rgba?\(([^)]+)\)/);if(!t)return null;const[n,a,i,d="1"]=t[1].split(",").map(f=>f.trim());return{red:Number(n),green:Number(a),blue:Number(i),alpha:Number(d)}}function At(e,t){const n=Math.max(R(e),R(t)),a=Math.min(R(e),R(t));return(n+.05)/(a+.05)}function R(e){const t=[e.red,e.green,e.blue].map(n=>{const a=n/255;return a<=.03928?a/12.92:((a+.055)/1.055)**2.4});return t[0]*.2126+t[1]*.7152+t[2]*.0722}function Lt(e){const t={top:L(e.cssSnapshot.marginTop),right:L(e.cssSnapshot.marginRight),bottom:L(e.cssSnapshot.marginBottom),left:L(e.cssSnapshot.marginLeft)},n={top:L(e.cssSnapshot.borderTopWidth),right:L(e.cssSnapshot.borderRightWidth),bottom:L(e.cssSnapshot.borderBottomWidth),left:L(e.cssSnapshot.borderLeftWidth)},a={top:L(e.cssSnapshot.paddingTop),right:L(e.cssSnapshot.paddingRight),bottom:L(e.cssSnapshot.paddingBottom),left:L(e.cssSnapshot.paddingLeft)},i={top:e.rect.top,left:e.rect.left,width:Math.max(e.rect.width,0),height:Math.max(e.rect.height,0)},d={top:i.top-t.top,left:i.left-t.left,width:i.width+t.left+t.right,height:i.height+t.top+t.bottom},f={top:i.top+n.top,left:i.left+n.left,width:Math.max(i.width-n.left-n.right,0),height:Math.max(i.height-n.top-n.bottom,0)},b={top:f.top+a.top,left:f.left+a.left,width:Math.max(f.width-a.left-a.right,0),height:Math.max(f.height-a.top-a.bottom,0)};return{margin:d,border:i,padding:f,content:b}}function L(e){const t=Number.parseFloat(e);return Number.isFinite(t)?t:0}x.ReviewLensOverlay=ct,x.ReviewLensProvider=rt,x.buildElementTarget=W,x.createGoogleSheetsAdapter=le,x.normalizeReviewUrl=ge,x.useReviewLens=be,Object.defineProperty(x,Symbol.toStringTag,{value:"Module"})}));
@@ -3,6 +3,7 @@ type GoogleSheetsAdapterConfig = {
3
3
  googleClientId: string;
4
4
  spreadsheetId: string;
5
5
  feedbackSheetName?: string;
6
+ messagesSheetName?: string;
6
7
  usersSheetName?: string;
7
8
  projectsSheetName?: string;
8
9
  };
package/dist/styles.css CHANGED
@@ -1 +1 @@
1
- .review-lens-root{color:#171717;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;top:0;right:0;bottom:0;left:0;pointer-events:none;position:fixed;z-index:2147483647}.review-lens-highlight{background:#f9731633;box-shadow:0 0 0 9999px #0f172a14;box-sizing:border-box;pointer-events:none;position:fixed}.review-lens-highlight--locked{box-shadow:0 0 0 9999px #0f172a1f}.review-lens-highlight__border,.review-lens-highlight__padding,.review-lens-highlight__content{box-sizing:border-box;position:absolute}.review-lens-highlight__border{border:2px solid #facc15;background:#facc1538}.review-lens-highlight__padding{border:2px solid #22c55e;background:#22c55e38}.review-lens-highlight__content{border:2px solid #2563eb;background:#2563eb29}.review-lens-highlight--locked .review-lens-highlight__content{border-color:#f97316}.review-lens-highlight__label{background:#171717;border-radius:4px;color:#fff;font-size:11px;font-weight:700;left:0;line-height:1;padding:4px 6px;position:absolute;top:-24px;white-space:nowrap}.review-lens-panel{background:#fafafa;border:1px solid #d4d4d4;border-radius:8px;box-shadow:0 24px 70px #0f172a38;box-sizing:border-box;max-height:calc(100vh - 32px);overflow:auto;padding:16px;pointer-events:auto;position:fixed;width:min(380px,calc(100vw - 32px))}.review-lens-panel--top-left{left:16px;top:16px}.review-lens-panel--top-right{right:16px;top:16px}.review-lens-panel--bottom-left{bottom:16px;left:16px}.review-lens-panel--bottom-right{bottom:16px;right:16px}.review-lens-panel__header{align-items:flex-start;display:flex;gap:16px;justify-content:space-between}.review-lens-panel h2,.review-lens-panel h3,.review-lens-panel p{margin:0}.review-lens-panel h2{font-size:18px;line-height:1.25}.review-lens-panel h3{font-size:14px;margin-top:18px}.review-lens-kicker{color:#525252;font-size:11px;font-weight:700;letter-spacing:0;text-transform:uppercase}.review-lens-panel button{background:#171717;border:1px solid #171717;border-radius:6px;color:#fff;cursor:pointer;font:inherit;font-size:13px;min-height:32px;padding:6px 10px}.review-lens-panel button:disabled{cursor:not-allowed;opacity:.45}.review-lens-metrics{border:1px solid #e5e5e5;border-radius:8px;display:grid;gap:0;margin:16px 0 0;overflow:hidden}.review-lens-metrics div{display:grid;grid-template-columns:96px minmax(0,1fr)}.review-lens-metrics dt,.review-lens-metrics dd{border-bottom:1px solid #e5e5e5;font-size:12px;margin:0;min-width:0;padding:8px}.review-lens-metrics dt{background:#f5f5f5;color:#525252;font-weight:700}.review-lens-metrics dd{overflow-wrap:anywhere}.review-lens-feedback-form{display:grid;gap:8px;margin-top:16px}.review-lens-feedback-form label{font-size:13px;font-weight:700}.review-lens-feedback-form textarea{border:1px solid #d4d4d4;border-radius:8px;box-sizing:border-box;font:inherit;min-height:96px;padding:10px;resize:vertical;width:100%}.review-lens-actions{display:flex;gap:8px;justify-content:flex-end}.review-lens-comments{display:grid;gap:8px}.review-lens-comment{border:1px solid #e5e5e5;border-radius:8px;display:grid;gap:6px;padding:10px}.review-lens-comment--selected{border-color:#2563eb}.review-lens-comment span{color:#525252;font-size:12px}.review-lens-marker{background:#f97316;border:2px solid #ffffff;border-radius:999px;box-shadow:0 8px 20px #0f172a3d;cursor:pointer;height:18px;pointer-events:auto;position:fixed;transform:translate(-50%,-50%);width:18px}.review-lens-marker--selected{background:#2563eb}
1
+ .review-lens-root{color:#171717;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;top:0;right:0;bottom:0;left:0;pointer-events:none;position:fixed;z-index:2147483647}.review-lens-highlight{background:#f9731633;box-shadow:0 0 0 9999px #0f172a14;box-sizing:border-box;pointer-events:none;position:fixed}.review-lens-highlight--locked{box-shadow:0 0 0 9999px #0f172a1f}.review-lens-highlight__border,.review-lens-highlight__padding,.review-lens-highlight__content{box-sizing:border-box;position:absolute}.review-lens-highlight__border{border:2px solid #facc15;background:#facc1538}.review-lens-highlight__padding{border:2px solid #22c55e;background:#22c55e38}.review-lens-highlight__content{border:2px solid #2563eb;background:#2563eb29}.review-lens-highlight--locked .review-lens-highlight__content{border-color:#f97316}.review-lens-highlight__label{background:#171717;border-radius:4px;color:#fff;display:grid;gap:3px;font-size:11px;left:0;line-height:1.1;max-width:240px;padding:6px 7px;position:absolute;top:-38px;white-space:nowrap}.review-lens-highlight__label strong{font-size:11px;font-weight:800;overflow:hidden;text-overflow:ellipsis}.review-lens-highlight__label span{color:#d4d4d4;font-size:10px;font-weight:700}.review-lens-distance{background:#2563eb;pointer-events:none;position:fixed;z-index:1}.review-lens-distance:before,.review-lens-distance:after{background:#2563eb;content:"";position:absolute}.review-lens-distance--horizontal:before,.review-lens-distance--horizontal:after{height:9px;top:-4px;width:1px}.review-lens-distance--horizontal:before{left:0}.review-lens-distance--horizontal:after{right:0}.review-lens-distance--vertical:before,.review-lens-distance--vertical:after{height:1px;left:-4px;width:9px}.review-lens-distance--vertical:before{top:0}.review-lens-distance--vertical:after{bottom:0}.review-lens-distance span{background:#2563eb;border-radius:999px;color:#fff;font-size:10px;font-weight:800;left:50%;line-height:1;padding:4px 6px;position:absolute;top:50%;transform:translate(-50%,-50%);white-space:nowrap}.review-lens-panel{background:#fafafa;border:1px solid #d4d4d4;border-radius:8px;box-shadow:0 24px 70px #0f172a38;box-sizing:border-box;display:flex;flex-direction:column;height:min(680px,calc(100vh - 32px));min-height:min(520px,calc(100vh - 32px));overflow:hidden;padding:16px;pointer-events:auto;position:fixed;width:min(380px,calc(100vw - 32px))}.review-lens-panel--top-left{left:16px;top:16px}.review-lens-panel--top-right{right:16px;top:16px}.review-lens-panel--bottom-left{bottom:16px;left:16px}.review-lens-panel--bottom-right{bottom:16px;right:16px}.review-lens-panel__header{flex:0 0 auto;align-items:flex-start;display:flex;gap:16px;justify-content:space-between;margin-bottom:12px}.review-lens-panel__body{display:flex;flex:1 1 auto;flex-direction:column;gap:16px;min-height:0;overflow:hidden}.review-lens-panel h2,.review-lens-panel h3,.review-lens-panel p{margin:0}.review-lens-panel h2{font-size:18px;line-height:1.25}.review-lens-panel h3{font-size:14px;margin-top:18px}.review-lens-kicker{color:#525252;font-size:11px;font-weight:700;letter-spacing:0;text-transform:uppercase}.review-lens-panel button{background:#171717;border:1px solid #171717;border-radius:6px;color:#fff;cursor:pointer;font:inherit;font-size:13px;min-height:32px;padding:6px 10px;transition:transform .14s cubic-bezier(.23,1,.32,1)}.review-lens-panel button:active{transform:scale(.97)}.review-lens-panel button:disabled{cursor:not-allowed;opacity:.45}.review-lens-mode-switch{background:#eee;border:1px solid #d4d4d4;border-radius:7px;display:grid;flex:0 0 auto;gap:2px;grid-template-columns:repeat(3,minmax(0,1fr));padding:2px}.review-lens-mode-switch button{background:transparent;border:0;color:#525252;min-height:30px}.review-lens-mode-switch button[aria-selected=true]{background:#fff;box-shadow:0 1px 4px #0f172a1f;color:#171717}.review-lens-mode-switch span{color:#737373;font-size:11px}.review-lens-metrics{border:1px solid #e5e5e5;border-radius:8px;display:grid;gap:0;margin:0;overflow:hidden}.review-lens-metrics div{display:grid;grid-template-columns:96px minmax(0,1fr)}.review-lens-metrics dt,.review-lens-metrics dd{border-bottom:1px solid #e5e5e5;font-size:11px;margin:0;min-width:0;padding:6px 8px}.review-lens-metrics dt{background:#f5f5f5;color:#525252;font-weight:700}.review-lens-metrics dd{overflow-wrap:anywhere}.review-lens-inspection{display:grid;flex:0 0 auto;gap:16px}.review-lens-review-pane{display:flex;flex:1 1 auto;flex-direction:column;gap:16px;min-height:0;overflow:auto;padding-right:4px}.review-lens-feedback-form{display:grid;flex:1 1 auto;gap:8px;grid-template-rows:auto minmax(96px,auto) auto auto auto;margin-top:0;min-height:0}.review-lens-composer-panel{background:linear-gradient(#fafafa,#fafafa00) top / 100% 12px no-repeat,#fff;border:1px solid #d4d4d4;border-radius:8px;box-shadow:inset 0 8px 12px #0f172a0a;display:block;flex:0 0 auto;max-height:min(360px,44vh);min-height:0;overflow:auto;padding:10px;position:relative}.review-lens-composer-panel:before{background:#d4d4d4;border-radius:999px;content:"";display:block;height:3px;margin:0 auto 8px;width:44px}.review-lens-feedback-form label,.review-lens-form-grid label{display:grid;gap:4px;font-size:13px;font-weight:700}.review-lens-feedback-form textarea,.review-lens-detail textarea,.review-lens-panel input,.review-lens-panel select{border:1px solid #d4d4d4;border-radius:8px;box-sizing:border-box;font:inherit;padding:10px;width:100%}.review-lens-feedback-form textarea,.review-lens-detail textarea{flex:1 1 auto;min-height:96px;resize:vertical}.review-lens-panel input,.review-lens-panel select{background:#fff;color:#171717;font-size:12px;min-height:34px;padding:6px 8px}.review-lens-form-grid{display:grid;gap:8px;grid-template-columns:repeat(2,minmax(0,1fr))}.review-lens-feedback-form__hint{color:#737373;font-size:12px;line-height:1.4}.review-lens-feedback-form__hint kbd{background:#f5f5f5;border:1px solid #d4d4d4;border-radius:4px;color:#404040;font-family:inherit;font-size:11px;padding:1px 4px}.review-lens-actions{display:flex;gap:8px;justify-content:flex-end}.review-lens-comments{display:grid;flex:1 1 auto;gap:10px;grid-template-rows:auto minmax(108px,1.35fr) minmax(200px,.9fr);min-height:0;overflow:hidden;padding-right:4px}.review-lens-filter-shell{display:grid;gap:8px}.review-lens-filter-bar{align-items:center;display:flex;gap:8px;justify-content:space-between}.review-lens-filter-bar button{align-items:center;background:#fff;border-color:#d4d4d4;color:#404040;display:inline-flex;gap:6px;min-height:30px;padding:5px 9px}.review-lens-filter-bar button:first-child{font-weight:700}.review-lens-filter-bar button[aria-expanded=true]{background:#171717;border-color:#171717;color:#fff}.review-lens-filter-bar span{align-items:center;background:#f97316;border-radius:999px;color:#fff;display:inline-flex;font-size:10px;height:17px;justify-content:center;min-width:17px;padding:0 5px}.review-lens-filters{background:#f5f5f5;border:1px solid #e5e5e5;border-radius:8px;display:grid;flex:0 0 auto;gap:8px;grid-template-columns:repeat(2,minmax(0,1fr));padding:8px}.review-lens-filters label{color:#525252;display:grid;font-size:10px;font-weight:800;gap:4px;letter-spacing:0;text-transform:uppercase}.review-lens-filters label:last-child{grid-column:1 / -1}.review-lens-list-panel{background:#fff;border:1px solid #d4d4d4;border-radius:10px;display:grid;gap:8px;grid-template-rows:auto minmax(0,1fr);min-height:0;overflow:hidden;padding:10px}.review-lens-comments__header{align-items:center;color:#171717;display:flex;gap:8px;justify-content:space-between}.review-lens-comments__header h3{color:#525252;font-size:11px;font-weight:800;letter-spacing:0;margin:0;text-transform:uppercase}.review-lens-comments__header span{align-items:center;background:#e5e5e5;border-radius:999px;color:#404040;display:inline-flex;font-size:11px;justify-content:center;min-width:22px;padding:2px 7px}.review-lens-comments__list{align-content:start;display:grid;gap:8px;min-height:0;overflow:auto;padding-right:2px}.review-lens-selected-panel{background:#171717;border-radius:10px;box-shadow:0 -10px 28px #0f172a2e;display:grid;gap:0;grid-template-rows:auto minmax(0,1fr);min-height:0;overflow:hidden;padding:4px}.review-lens-selected-panel__label{color:#d4d4d4;font-size:10px;font-weight:800;letter-spacing:0;padding:7px 8px 8px;text-transform:uppercase}.review-lens-selected-panel--empty{color:#d4d4d4;font-size:12px;line-height:1.4;padding:4px 12px 12px}.review-lens-selected-panel--empty p{color:#f5f5f5}.review-lens-detail{background:linear-gradient(180deg,#fff,#f8fafc);border:0;border-radius:8px;box-shadow:none;box-sizing:border-box;display:grid;gap:10px;min-height:0;overflow:auto;padding:12px}.review-lens-detail__header{align-items:start;display:flex;gap:12px;justify-content:space-between}.review-lens-detail__header h3{color:#171717;font-size:13px;line-height:1.2;margin:0;text-transform:capitalize}.review-lens-detail__header strong,.review-lens-comment__header strong{background:#fee2e2;border:1px solid #fecaca;border-radius:999px;color:#991b1b;flex:0 0 auto;font-size:10px;line-height:1;padding:5px 7px;text-transform:uppercase}.review-lens-detail blockquote{background:#f8fafc;border:1px solid #dbeafe;border-left:3px solid #2563eb;border-radius:6px;color:#171717;font-size:15px;font-weight:700;line-height:1.35;margin:0;padding:9px 10px}.review-lens-detail-meta{background:#fff;border:1px solid #e5e5e5;border-radius:6px;display:grid;gap:0;margin:0;overflow:hidden}.review-lens-detail-meta div{display:grid;gap:2px;grid-template-columns:72px minmax(0,1fr);padding:6px 8px}.review-lens-detail-meta div+div{border-top:1px solid #eeeeee}.review-lens-detail-meta dt,.review-lens-detail-meta dd{font-size:11px;line-height:1.25;margin:0;min-width:0}.review-lens-detail-meta dt{color:#737373;font-weight:800;text-transform:uppercase}.review-lens-detail-meta dd{color:#262626;overflow-wrap:anywhere}.review-lens-detail a{color:#2563eb;font-weight:700}.review-lens-comment{background:#fff;border:1px solid #e5e5e5;border-radius:8px;box-shadow:0 1px 2px #0f172a0a;cursor:pointer;display:grid;gap:10px;padding:12px;position:relative;text-align:left}.review-lens-comment:before{background:#737373;border-radius:999px;bottom:12px;content:"";left:8px;position:absolute;top:12px;width:3px}.review-lens-comment--high:before{background:#dc2626}.review-lens-comment--medium:before{background:#f97316}.review-lens-comment--low:before{background:#16a34a}.review-lens-comment:focus-visible{outline:2px solid #2563eb;outline-offset:2px}.review-lens-comment--selected{background:#eff6ff;border-color:#2563eb;box-shadow:0 8px 18px #2563eb1f}.review-lens-comment__header{align-items:center;display:flex;gap:8px;justify-content:space-between;padding-left:8px}.review-lens-comment__header span{color:#525252;font-size:10px;font-weight:800;letter-spacing:0;text-transform:uppercase}.review-lens-comment__content{display:grid;gap:6px;padding-left:8px}.review-lens-comment__content p{color:#171717;font-size:14px;line-height:1.35}.review-lens-comment__content span{color:#525252;font-size:11px;line-height:1}.review-lens-tags{display:flex;flex-wrap:wrap;gap:5px;padding-left:8px}.review-lens-tags span{background:#f5f5f5;border:1px solid #e5e5e5;border-radius:999px;color:#404040;font-size:10px;font-weight:700;line-height:1;padding:4px 6px}.review-lens-thread{border-top:1px solid #e5e5e5;display:grid;gap:6px;padding-top:10px}.review-lens-thread__header{align-items:center;display:flex;justify-content:space-between}.review-lens-thread h3{margin:0}.review-lens-thread__header span{color:#737373;font-size:11px;font-weight:800}.review-lens-detail .review-lens-thread textarea{min-height:48px}.review-lens-status-actions{align-items:center;display:grid;gap:8px;grid-template-columns:minmax(0,1fr) auto}.review-lens-status-actions button{min-height:30px}.review-lens-status-actions .review-lens-button-secondary{background:#fff;border-color:#d4d4d4;color:#404040}.review-lens-status-actions .review-lens-button-primary{background:#171717;border-color:#171717;color:#fff}.review-lens-thread__message{background:#f5f5f5;border-radius:6px;display:grid;gap:4px;padding:8px}.review-lens-thread__message p{font-size:12px;line-height:1.35}.review-lens-thread__message span{color:#737373;font-size:10px}.review-lens-comment__actions{display:flex;justify-content:flex-end}.review-lens-comment__actions button{background:transparent;border-color:#d4d4d4;color:#404040;min-height:28px;padding:4px 10px}.review-lens-comment__actions button:hover{background:#171717;border-color:#171717;color:#fff}.review-lens-marker{background:#f97316;border:2px solid #ffffff;border-radius:999px;box-shadow:0 8px 20px #0f172a3d;cursor:pointer;height:18px;pointer-events:auto;position:fixed;transform:translate(-50%,-50%);width:18px}.review-lens-marker--selected{background:#2563eb}.review-lens-minimap{background:#1717171f;border-radius:999px;bottom:24px;pointer-events:auto;position:fixed;right:8px;top:24px;width:10px}.review-lens-minimap__point{background:#f97316;border:2px solid #ffffff;border-radius:999px;box-shadow:0 2px 8px #0f172a38;height:14px;left:50%;min-height:0;padding:0;position:absolute;transform:translate(-50%,-50%);width:14px}.review-lens-minimap__point--selected{background:#2563eb}.review-lens-insights,.review-lens-summary{border:1px solid #e5e5e5;border-radius:8px;display:grid;gap:8px;padding:10px}.review-lens-insights h3,.review-lens-summary h3{margin:0}.review-lens-insights p,.review-lens-insights li,.review-lens-summary dt,.review-lens-summary dd{color:#404040;font-size:12px;line-height:1.35}.review-lens-insights ul{display:grid;gap:6px;margin:0;padding-left:18px}.review-lens-summary{align-content:start;flex:1 1 auto;min-height:0;overflow:auto;padding-right:4px}.review-lens-summary section{border-bottom:1px solid #e5e5e5;display:grid;gap:8px;padding-bottom:10px}.review-lens-summary section:last-child{border-bottom:0;padding-bottom:0}.review-lens-summary dl,.review-lens-summary div{display:grid;gap:6px;margin:0}.review-lens-summary div{grid-template-columns:minmax(0,1fr) auto}.review-lens-summary dd{font-weight:700;margin:0}
package/dist/types.d.ts CHANGED
@@ -1,7 +1,10 @@
1
1
  import type { ReactNode } from "react";
2
- export type FeedbackStatus = "open" | "resolved";
2
+ export type FeedbackStatus = "open" | "in_progress" | "needs_clarification" | "fixed" | "wontfix" | "resolved";
3
+ export type FeedbackSeverity = "low" | "medium" | "high";
4
+ export type FeedbackCategory = "bug" | "visual" | "copy" | "accessibility" | "responsive";
3
5
  export type ReviewLensRole = "designer" | "developer" | "admin";
4
- export type ReviewLensPermission = "create" | "read" | "resolve";
6
+ export type ReviewLensPermission = "create" | "read" | "reply" | "update" | "assign";
7
+ export type ReviewLensViewportPreset = "mobile" | "tablet" | "desktop" | "custom";
5
8
  export type CssSnapshot = {
6
9
  margin: string;
7
10
  marginTop: string;
@@ -23,6 +26,7 @@ export type CssSnapshot = {
23
26
  lineHeight: string;
24
27
  color: string;
25
28
  backgroundColor: string;
29
+ borderRadius: string;
26
30
  width: number;
27
31
  height: number;
28
32
  };
@@ -51,16 +55,58 @@ export type ReviewLensFeedback = {
51
55
  selector: string;
52
56
  selectorStrategy: ReviewLensTarget["selectorStrategy"];
53
57
  elementFingerprint: ElementFingerprint;
54
- cssSnapshot: CssSnapshot;
58
+ createdCssSnapshot: CssSnapshot;
59
+ fixedCssSnapshot?: CssSnapshot;
55
60
  comment: string;
56
61
  status: FeedbackStatus;
62
+ severity: FeedbackSeverity;
63
+ category: FeedbackCategory;
64
+ assigneeEmail?: string;
65
+ viewportWidth: number;
66
+ viewportHeight: number;
67
+ viewportPreset: ReviewLensViewportPreset;
68
+ screenshotUrl?: string;
69
+ screenshotThumbnailUrl?: string;
70
+ attachments: ReviewLensAttachment[];
57
71
  authorEmail: string;
58
72
  createdAt: string;
59
73
  updatedAt: string;
74
+ fixedAt?: string;
75
+ fixedBy?: string;
60
76
  resolvedAt?: string;
61
77
  resolvedBy?: string;
62
78
  };
63
- export type CreateFeedbackInput = Omit<ReviewLensFeedback, "id" | "status" | "createdAt" | "updatedAt" | "resolvedAt" | "resolvedBy">;
79
+ export type CreateFeedbackInput = Omit<ReviewLensFeedback, "id" | "attachments" | "createdAt" | "updatedAt" | "fixedAt" | "fixedBy" | "resolvedAt" | "resolvedBy">;
80
+ export type UpdateFeedbackInput = Partial<Pick<ReviewLensFeedback, "status" | "severity" | "category" | "assigneeEmail" | "screenshotUrl" | "screenshotThumbnailUrl" | "attachments" | "fixedCssSnapshot" | "fixedAt" | "fixedBy" | "resolvedAt" | "resolvedBy">>;
81
+ export type ReviewLensThreadMessage = {
82
+ id: string;
83
+ feedbackId: string;
84
+ body: string;
85
+ authorEmail: string;
86
+ createdAt: string;
87
+ };
88
+ export type CreateMessageInput = Omit<ReviewLensThreadMessage, "id" | "createdAt">;
89
+ export type ReviewLensAttachment = {
90
+ id: string;
91
+ feedbackId: string;
92
+ type: "screenshot";
93
+ url: string;
94
+ thumbnailUrl?: string;
95
+ createdAt: string;
96
+ createdBy: string;
97
+ };
98
+ export type CreateAttachmentInput = {
99
+ type: "screenshot";
100
+ data: Blob | string;
101
+ createdBy: string;
102
+ };
103
+ export type ReviewLensDesignTokens = {
104
+ spacing?: string[];
105
+ fontSize?: string[];
106
+ lineHeight?: string[];
107
+ color?: string[];
108
+ radius?: string[];
109
+ };
64
110
  export type ReviewLensAdapter = {
65
111
  getCurrentUser(): Promise<{
66
112
  email: string;
@@ -72,7 +118,10 @@ export type ReviewLensAdapter = {
72
118
  normalizedPath: string;
73
119
  }): Promise<ReviewLensFeedback[]>;
74
120
  createFeedback(input: CreateFeedbackInput): Promise<ReviewLensFeedback>;
75
- resolveFeedback(id: string, resolvedBy: string): Promise<ReviewLensFeedback>;
121
+ updateFeedback(id: string, patch: UpdateFeedbackInput): Promise<ReviewLensFeedback>;
122
+ listMessages(feedbackId: string): Promise<ReviewLensThreadMessage[]>;
123
+ createMessage(input: CreateMessageInput): Promise<ReviewLensThreadMessage>;
124
+ uploadAttachment?(feedbackId: string, input: CreateAttachmentInput): Promise<ReviewLensAttachment>;
76
125
  };
77
126
  export type ReviewLensConfig = {
78
127
  googleClientId?: string;
@@ -82,6 +131,9 @@ export type ReviewLensConfig = {
82
131
  contentId: string;
83
132
  currentUrl?: string;
84
133
  normalizeUrl?: (url: string) => string;
134
+ designTokens?: ReviewLensDesignTokens;
135
+ captureScreenshot?: (target: ReviewLensTarget) => Promise<Blob | string>;
136
+ uploadAttachment?: (feedbackId: string, input: CreateAttachmentInput) => Promise<ReviewLensAttachment>;
85
137
  adapter?: ReviewLensAdapter;
86
138
  };
87
139
  export type ReviewLensProviderProps = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "review-lens-react",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "React overlay for UX review feedback backed by Google Sheets.",
5
5
  "type": "module",
6
6
  "main": "./dist/review-lens-react.umd.cjs",