@01.works/visual-review 0.11.0 → 0.13.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.
@@ -0,0 +1 @@
1
+ import{a as e,c as t,i as n,o as r,r as i,s as a,t as o}from"./src-CjYM9---.js";import{a as s,n as c,t as l}from"./reviewer-auth-presentation-CAm-YbaR.js";import{i as u,init as d}from"@instantdb/core";new Uint8Array([107,167,184,17,157,173,17,209,128,180,0,192,79,212,48,200]);const f=u.schema({entities:{$files:u.entity({path:u.string().unique().indexed(),url:u.string()}),$users:u.entity({email:u.string().unique().indexed()}),projects:u.entity({name:u.string(),slug:u.string().unique().indexed(),allowedOrigin:u.string().optional(),originDisconnectedAt:u.number().optional(),originModelVersion:u.number().optional(),createdAt:u.number().indexed(),updatedAt:u.number()}),projectOrigins:u.entity({projectId:u.string().indexed(),originKey:u.string().unique().indexed(),origin:u.string().indexed(),label:u.string().optional(),createdAt:u.number().indexed(),updatedAt:u.number()}),reviewPages:u.entity({projectId:u.string().indexed(),autoRegistrationSlot:u.string().unique().indexed().optional(),name:u.string(),pathname:u.string(),stagingOrigin:u.string(),createdAt:u.number().indexed(),updatedAt:u.number()}),reviewPageBuilds:u.entity({projectId:u.string().indexed(),pageId:u.string().unique().indexed(),buildId:u.string(),gitCommit:u.string().optional(),generatedAt:u.number().optional(),mode:u.string(),updatedAt:u.number().indexed()}),reviewers:u.entity({authUserId:u.string().indexed(),projectId:u.string().indexed(),email:u.string().indexed(),displayName:u.string(),role:u.string(),createdAt:u.number()}),reviewerIdentityClaims:u.entity({reviewerId:u.string().unique().indexed(),projectId:u.string().indexed(),authUserId:u.string().indexed(),email:u.string(),displayName:u.string(),role:u.string(),createdAt:u.number()}),invitations:u.entity({projectId:u.string().indexed(),reviewerId:u.string().indexed().optional(),email:u.string().indexed(),status:u.string().indexed(),claimRevision:u.number(),claimedByAuthUserId:u.string().indexed().optional(),expiresAt:u.number().indexed(),createdAt:u.number(),acceptedAt:u.number().optional(),revokedAt:u.number().optional(),emailTokenHash:u.string().optional(),emailSentAt:u.number().optional(),reviewUrl:u.string().optional()}),comments:u.entity({pageId:u.string().indexed(),pageNumber:u.number().optional(),authorId:u.string().indexed(),authorAuthUserId:u.string().indexed(),authorName:u.string(),body:u.string(),bodyContentMarker:u.string().optional(),anchorSelector:u.string().optional(),anchorOffsetX:u.number().optional(),anchorOffsetY:u.number().optional(),anchorViewportX:u.number().optional(),anchorViewportY:u.number().optional(),targetKind:u.string().optional(),targetPageX:u.number().optional(),targetPageY:u.number().optional(),targetWidth:u.number().optional(),targetHeight:u.number().optional(),targetViewportX:u.number().optional(),targetViewportY:u.number().optional(),targetViewportWidth:u.number().optional(),targetViewportHeight:u.number().optional(),targetPageOrigin:u.string().optional(),targetPagePathname:u.string().optional(),targetPageSearch:u.string().optional(),targetPageHash:u.string().optional(),targetPageTitle:u.string().optional(),targetCapturedAt:u.number().optional(),environmentBrowser:u.string().optional(),environmentOperatingSystem:u.string().optional(),environmentDeviceType:u.string().optional(),environmentScreenWidth:u.number().optional(),environmentScreenHeight:u.number().optional(),environmentDevicePixelRatio:u.number().optional(),status:u.string().indexed(),workflowRevision:u.number(),createdAt:u.number().indexed(),updatedAt:u.number().indexed(),resolvedAt:u.number().optional(),resolvedById:u.string().optional(),deletingAt:u.number().optional()}),commentWorkflowClaims:u.entity({claimKey:u.string().unique().indexed(),commentId:u.string().indexed(),expectedWorkflowRevision:u.number(),createdAt:u.number().indexed()}),commentPageNumberClaims:u.entity({claimKey:u.string().unique().indexed(),pageId:u.string().indexed(),pageNumber:u.number(),commentId:u.string().indexed(),createdAt:u.number().indexed()}),commentTargetElements:u.entity({commentId:u.string().indexed(),slotKey:u.string().unique().indexed().optional(),ordinal:u.number(),selector:u.string(),tagName:u.string(),textPreview:u.string(),pageX:u.number(),pageY:u.number(),width:u.number(),height:u.number(),viewportX:u.number(),viewportY:u.number(),viewportWidth:u.number(),viewportHeight:u.number(),authorAuthUserId:u.string().indexed()}),commentScopeEntries:u.entity({commentId:u.string().indexed(),slotKey:u.string().unique().indexed().optional(),ordinal:u.number(),key:u.string(),valueType:u.string(),stringValue:u.string().optional(),numberValue:u.number().optional(),booleanValue:u.boolean().optional(),authorAuthUserId:u.string().indexed()}),commentSourceContexts:u.entity({commentId:u.string().unique().indexed(),pageId:u.string().indexed(),authorAuthUserId:u.string().indexed(),contextJson:u.string(),requestPayloadHash:u.string().optional(),createdAt:u.number().indexed()}),replies:u.entity({pageId:u.string().indexed(),commentId:u.string().indexed(),authorId:u.string().indexed(),authorAuthUserId:u.string().indexed(),authorName:u.string(),body:u.string(),bodyContentMarker:u.string().optional(),requestPayloadHash:u.string().optional(),createdAt:u.number().indexed(),updatedAt:u.number().indexed()})},links:{projectOwners:{forward:{on:`projects`,has:`many`,label:`owners`},reverse:{on:`$users`,has:`many`,label:`ownedProjects`}},projectPages:{forward:{on:`projects`,has:`many`,label:`pages`},reverse:{on:`reviewPages`,has:`one`,label:`project`}},projectOriginProject:{forward:{on:`projects`,has:`many`,label:`origins`},reverse:{on:`projectOrigins`,has:`one`,label:`project`}},pageCurrentBuild:{forward:{on:`reviewPageBuilds`,has:`one`,label:`page`,required:!0,onDelete:`cascade`},reverse:{on:`reviewPages`,has:`one`,label:`currentBuild`}},projectReviewers:{forward:{on:`projects`,has:`many`,label:`reviewers`},reverse:{on:`reviewers`,has:`one`,label:`project`}},reviewerUser:{forward:{on:`reviewers`,has:`one`,label:`$user`},reverse:{on:`$users`,has:`many`,label:`reviewerMemberships`}},projectInvitations:{forward:{on:`projects`,has:`many`,label:`invitations`},reverse:{on:`invitations`,has:`one`,label:`project`}},invitationReviewer:{forward:{on:`invitations`,has:`one`,label:`reviewer`},reverse:{on:`reviewers`,has:`many`,label:`acceptedInvitations`}},invitationActiveUser:{forward:{on:`invitations`,has:`one`,label:`activeUser`},reverse:{on:`$users`,has:`many`,label:`activeReviewInvitations`}},pageComments:{forward:{on:`reviewPages`,has:`many`,label:`comments`},reverse:{on:`comments`,has:`one`,label:`page`}},commentReplies:{forward:{on:`comments`,has:`many`,label:`replies`},reverse:{on:`replies`,has:`one`,label:`comment`,required:!0,onDelete:`cascade`}},commentTargetElements:{forward:{on:`commentTargetElements`,has:`one`,label:`comment`,required:!0,onDelete:`cascade`},reverse:{on:`comments`,has:`many`,label:`targetElements`}},commentScopeEntries:{forward:{on:`commentScopeEntries`,has:`one`,label:`comment`,required:!0,onDelete:`cascade`},reverse:{on:`comments`,has:`many`,label:`scopeEntries`}},commentSourceContext:{forward:{on:`commentSourceContexts`,has:`one`,label:`comment`,required:!0,onDelete:`cascade`},reverse:{on:`comments`,has:`one`,label:`sourceContext`}},pageSourceContexts:{forward:{on:`commentSourceContexts`,has:`one`,label:`page`,required:!0},reverse:{on:`reviewPages`,has:`many`,label:`sourceContexts`}},sourceContextAuthor:{forward:{on:`commentSourceContexts`,has:`one`,label:`author`,required:!0},reverse:{on:`reviewers`,has:`many`,label:`sourceContexts`}},commentAuthor:{forward:{on:`comments`,has:`one`,label:`author`},reverse:{on:`reviewers`,has:`many`,label:`comments`}},replyAuthor:{forward:{on:`replies`,has:`one`,label:`author`},reverse:{on:`reviewers`,has:`many`,label:`replies`}}}}),p={$default:`false`},m={$default:`false`},h=`$user.activeReviewInvitations`,g=`${h}.project.id`;function _(e){return[`auth.id != null`,`size(auth.ref('${h}.id')) > 0`,`size(auth.ref('${g}')) == size(auth.ref('${h}.id'))`,`size(auth.ref('${h}.status')) == size(auth.ref('${h}.id'))`,`size(auth.ref('${h}.expiresAt')) == size(auth.ref('${h}.id'))`,`size(auth.ref('${h}.revokedAt')) == 0`,`auth.ref('${h}.status').all(status, status == 'accepted')`,`auth.ref('${h}.expiresAt').all(expiresAt, request.time < timestamp(expiresAt))`,e].join(` && `)}function v(...e){return e.map(e=>`(${e})`).join(` && `)}function y(...e){return e.map(e=>`(${e})`).join(` || `)}function b(e){let t=`data.ref('${e}project.origins.origin')`,n=`data.ref('${e}project.allowedOrigin')`,r=`data.ref('${e}project.originModelVersion')`;return y(`request.origin in ${t}`,v(`size(${r}) == 0`,`request.origin in ${n}`))}_(`data.ref('project.id')[0] in auth.ref('${g}')`),y(`request.origin in data.ref('project.origins.origin')`,v(`size(data.ref('project.originModelVersion')) == 0`,`request.origin in data.ref('project.allowedOrigin')`)),y(`request.origin in data.ref('origins.origin')`,v(`size(data.ref('originModelVersion')) == 0`,`request.origin in data.ref('allowedOrigin')`)),_(`data.ref('page.project.id')[0] in auth.ref('${g}')`),b(`page.`),y(`auth.id != null && auth.id in data.ref('comment.page.project.owners.id')`,v(_(`data.ref('comment.page.project.id')[0] in auth.ref('${g}')`),b(`comment.page.`))),_(`data.ref('comment.page.project.id')[0] in auth.ref('${g}')`),b(`comment.page.`),b(``);const x=v(`data.reviewerId != null`,`data.projectId in data.ref('project.id')`,`data.reviewerId in data.ref('reviewer.id')`,`data.projectId in data.ref('reviewer.project.id')`,`auth.id in data.ref('reviewer.$user.id')`,`auth.email in data.ref('reviewer.email')`);v(`auth.id != null`,`data.revokedAt == null`,`data.status in ['pending', 'accepted']`,`data.status == 'pending' || data.claimedByAuthUserId == auth.id`,`request.time < timestamp(data.expiresAt)`,`request.modifiedFields.all(field, field in ['status', 'claimedByAuthUserId', 'acceptedAt', 'claimRevision', 'activeUser'])`,`'status' in request.modifiedFields`,`'claimedByAuthUserId' in request.modifiedFields`,`'acceptedAt' in request.modifiedFields`,`'claimRevision' in request.modifiedFields`,`'activeUser' in request.modifiedFields`,`newData.status == 'accepted'`,`newData.claimedByAuthUserId == auth.id`,`newData.claimRevision == data.claimRevision + 1`,`math.abs((request.time - timestamp(newData.acceptedAt)).getMinutes()) <= 1`,x),v(`actions.data == 'update'`,`actions.linkedData == 'update'`,`auth.id != null`,`auth.email != null`,`linkedData.id == auth.id`,`linkedData.email == auth.email`,`data.email == auth.email`,`data.revokedAt == null`,`data.status in ['pending', 'accepted']`,`data.status == 'pending' || data.claimedByAuthUserId == auth.id`,`request.time < timestamp(data.expiresAt)`,`newData.status == 'accepted'`,`newData.claimedByAuthUserId == auth.id`,`newData.claimRevision == data.claimRevision + 1`,`math.abs((request.time - timestamp(newData.acceptedAt)).getMinutes()) <= 1`,x),v(`actions.data == 'update'`,`actions.linkedData == 'update'`,`auth.id != null`,`auth.email != null`,`data.id == auth.id`,`data.email == auth.email`,`linkedData.email == auth.email`,`linkedData.revokedAt == null`,`linkedData.status == 'accepted'`,`linkedData.claimedByAuthUserId == auth.id`,`request.time < timestamp(linkedData.expiresAt)`,`math.abs((request.time - timestamp(linkedData.acceptedAt)).getMinutes()) <= 1`,`linkedData.projectId in linkedData.ref('project.id')`,`linkedData.reviewerId in linkedData.ref('reviewer.id')`,`linkedData.projectId in linkedData.ref('reviewer.project.id')`,`auth.id in linkedData.ref('reviewer.$user.id')`,`auth.email in linkedData.ref('reviewer.email')`),v(`auth.id != null`,`auth.email != null`,`linkedData.id == auth.id`,`linkedData.email == auth.email`,`data.email == auth.email`,`data.claimedByAuthUserId == auth.id`,`auth.id in data.ref('activeUser.id')`),v(`auth.id != null`,`auth.email != null`,`data.id == auth.id`,`data.email == auth.email`,`linkedData.email == auth.email`,`linkedData.claimedByAuthUserId == auth.id`,`auth.id in linkedData.ref('activeUser.id')`),{...p},{...p},{...m},{...m},_(`data.id in auth.ref('${g}')`),{...p},{...m},{...p},{...p},{...m},{...p},{...m},_(`data.ref('project.id')[0] in auth.ref('${g}')`),{...p},{...m},{...p},{...p},{...m},{...m},{...p},{...m},{...p},{...p},{...p},{...p},{...p},{...p};function ee(e){if(!e.trim())throw Error(`InstantDB app ID is required`);return d({appId:e,schema:f,devtool:!1})}var te=class{db;session;#e;attachScreenshot;constructor(e,t,n={}){this.db=e,this.session=t,this.#e=n.commentTransport,this.#e&&(this.attachScreenshot=(e,t,n)=>this.#e.uploadScreenshot({pageId:e,commentId:t,screenshot:n}))}async getSnapshot(e){let{data:t}=await this.db.queryOnce(S(e,this.session.authUserId));return w(e,t.comments,t.replies)}async getProjectSnapshot(e){this.#t(e);let{data:t}=await this.db.queryOnce(C(e,this.session.authUserId));return T(e,t.comments,t.replies)}subscribe(e,t,n){return this.db.subscribeQuery(S(e,this.session.authUserId),r=>{if(r.error){n?.(M(r.error));return}if(r.data)try{t(w(e,r.data.comments,r.data.replies))}catch{n?.({type:`error`,code:`data-integrity`,recovery:`retry`,clearSnapshot:!0})}})}subscribeProject(e,t,n){return this.#t(e),this.db.subscribeQuery(C(e,this.session.authUserId),r=>{if(r.error){n?.(M(r.error));return}if(r.data)try{t(T(e,r.data.comments,r.data.replies))}catch{n?.({type:`error`,code:`data-integrity`,recovery:`retry`,clearSnapshot:!0})}})}async mutate(e,t){switch(t.type){case`comment.upsert`:{if(t.comment.pageId!==e)throw Error(`Comment page mismatch`);let n=t.comment;if(n.authorId!==this.session.reviewerId)throw Error(`Comment author mismatch`);let i=a(n.body).trim(),s=R(n.createdAt,`Comment createdAt`);if(R(n.updatedAt,`Comment updatedAt`)<s)throw new o(`Comment updatedAt precedes createdAt`);if(n.createdAt!==n.updatedAt)throw new o(`Comment edits must go through the review service`);if(n.status!==`open`||n.resolvedAt!==void 0||n.resolvedById!==void 0)throw new o(`New comments must start open`);let c=r(n.target);if(!k(n.anchor,c.publicTarget.anchor))throw new o(`Comment anchor does not match its target`);if(!this.#e)throw new o(`Comment creation requires the review service transport`);await this.#e.createComment({pageId:e,commentId:n.id,body:i,target:n.target,...n.scope===void 0?{}:{scope:n.scope}});return}case`comment.delete`:throw new o(`Comment deletion must go through the review service`);case`reply.upsert`:{let e=t.reply;if(e.authorId!==this.session.reviewerId)throw Error(`Reply author mismatch`);let n=a(e.body,`Reply body`).trim(),r=R(e.createdAt,`Reply createdAt`);if(R(e.updatedAt,`Reply updatedAt`)<r)throw new o(`Reply updatedAt precedes createdAt`);if(e.createdAt!==e.updatedAt)throw new o(`Reply edits must go through the review service`);if(!this.#e)throw new o(`Reply creation requires the review service transport`);await this.#e.createReply({commentId:e.commentId,replyId:e.id,body:n});return}case`reply.delete`:throw new o(`Reply deletion must go through the review service`)}}#t(e){if(!e.trim()||e!==this.session.projectId)throw Error(`Project does not belong to the reviewer session`)}};function S(e,t){return{comments:{$:{where:{pageId:e},order:{createdAt:`asc`}},targetElements:{},scopeEntries:{}},replies:{$:{where:{pageId:e},order:{createdAt:`asc`}}},$users:{$:{where:{id:t}},activeReviewInvitations:{project:{}}}}}function C(e,t){return{comments:{$:{where:{"page.project.id":e},order:{createdAt:`asc`}},page:{project:{}},targetElements:{},scopeEntries:{}},replies:{$:{where:{"comment.page.project.id":e},order:{createdAt:`asc`}},comment:{page:{project:{}}}},$users:{$:{where:{id:t}},activeReviewInvitations:{project:{}}}}}function w(e,t,n){let r=t.map(t=>D(t,e));j(r,`comment`);let i=new Set(r.map(({id:e})=>e)),a=n.map(t=>O(t,e));j(a,`reply`);for(let e of a)if(!i.has(e.commentId))throw new o(`Reply ${e.id} references a comment outside the page snapshot`);return{pageId:e,comments:r,replies:a}}function T(e,t,n){let r=new Map,i=t.map(t=>{let n=I(t.pageId,`comment pageId`);E(t.page,n,e,`comment ${String(t.id)}`);let i=P(t.page,`comment ${String(t.id)} page`),a=D(t,n),o=`/`;try{o=new URL(a.target.pageUrl).pathname||`/`}catch{}return r.set(n,{id:n,name:typeof i.name==`string`&&i.name.trim()?i.name.trim():a.target.pageTitle.trim()||o,pathname:typeof i.pathname==`string`&&i.pathname.trim()?i.pathname.trim():o}),a});j(i,`comment`);let a=new Map(i.map(e=>[e.id,e])),s=n.map(t=>{let n=I(t.pageId,`reply pageId`),r=P(t.comment,`reply ${String(t.id)} comment`),i=I(t.commentId,`reply ${String(t.id)} commentId`);if(I(r.id,`reply ${String(t.id)} comment relation id`)!==i)throw new o(`Reply ${String(t.id)} has an inconsistent comment relation`);E(r.page,n,e,`reply ${String(t.id)}`);let s=O(t,n),c=a.get(s.commentId);if(!c||c.pageId!==n)throw new o(`Reply ${s.id} references a comment outside the project snapshot`);return s});return j(s,`reply`),{projectId:e,pages:[...r.values()].sort((e,t)=>e.pathname.localeCompare(t.pathname)||e.id.localeCompare(t.id)),comments:i,replies:s}}function E(e,t,n,r){let i=P(e,`${r} page`);if(I(i.id,`${r} page id`)!==t)throw new o(`${r} escaped its page relation`);if(I(P(i.project,`${r} project`).id,`${r} project id`)!==n)throw new o(`${r} escaped its project scope`)}function D(r,s){let c=P(r,`comment`),l=I(c.id,`comment id`);if(I(c.pageId,`comment ${l} pageId`)!==s)throw new o(`Comment ${l} escaped its page scope`);I(c.authorAuthUserId,`comment ${l} authorAuthUserId`);let u=!(Object.hasOwn(c,`anchor`)&&Object.hasOwn(c,`target`)),d=u?n(c,c.targetElements,`comment ${l} target`):e(c.target,`comment ${l} target`),f=u?{...d.anchor}:ne(c.anchor,`comment ${l} anchor`);if(!k(f,d.anchor))throw new o(`Comment ${l} has inconsistent anchors`);let p=c.status;if(p!==`open`&&p!==`resolved`)throw new o(`Comment ${l} has an invalid status`);let m=R(c.createdAt,`comment ${l} createdAt`),h=R(c.updatedAt,`comment ${l} updatedAt`);if(h<m)throw new o(`Comment ${l} updatedAt precedes createdAt`);let g=re(c.resolvedAt,`comment ${l} resolvedAt`),_=ie(c.resolvedById,`comment ${l} resolvedById`);if(p===`open`&&(g!==void 0||_!==void 0))throw new o(`Open comment ${l} contains resolution metadata`);if(p===`resolved`&&(g===void 0||_===void 0||g!==h))throw new o(`Resolved comment ${l} has invalid workflow metadata`);return{id:l,pageId:s,...ae(c.pageNumber,`comment ${l} pageNumber`),authorId:I(c.authorId,`comment ${l} authorId`),authorName:I(c.authorName,`comment ${l} authorName`),body:a(c.body,`comment ${l} body`),anchor:f,target:d,...u?A(i(c.scopeEntries)):c.scope==null?{}:{scope:t(c.scope)},status:p,workflowRevision:z(c.workflowRevision,`comment ${l} workflowRevision`),createdAt:m,updatedAt:h,...g===void 0?{}:{resolvedAt:g},..._===void 0?{}:{resolvedById:_}}}function O(e,t){let n=P(e,`reply`),r=I(n.id,`reply id`);if(I(n.pageId,`reply ${r} pageId`)!==t)throw new o(`Reply ${r} escaped its page scope`);I(n.authorAuthUserId,`reply ${r} authorAuthUserId`);let i=R(n.createdAt,`reply ${r} createdAt`),s=R(n.updatedAt,`reply ${r} updatedAt`);if(s<i)throw new o(`Reply ${r} updatedAt precedes createdAt`);return{id:r,commentId:I(n.commentId,`reply ${r} commentId`),authorId:I(n.authorId,`reply ${r} authorId`),authorName:I(n.authorName,`reply ${r} authorName`),body:a(n.body,`reply ${r} body`),createdAt:i,updatedAt:s}}function ne(e,t){let n=P(e,t);if(Object.keys(n).length!==5||!Object.hasOwn(n,`selector`)||!Object.hasOwn(n,`offsetX`)||!Object.hasOwn(n,`offsetY`)||!Object.hasOwn(n,`viewportX`)||!Object.hasOwn(n,`viewportY`))throw new o(`${t} has invalid fields`);return{selector:F(n.selector,`${t} selector`),offsetX:L(n.offsetX,`${t} offsetX`),offsetY:L(n.offsetY,`${t} offsetY`),viewportX:L(n.viewportX,`${t} viewportX`),viewportY:L(n.viewportY,`${t} viewportY`)}}function k(e,t){return e.selector===t.selector&&e.offsetX===t.offsetX&&e.offsetY===t.offsetY&&e.viewportX===t.viewportX&&e.viewportY===t.viewportY}function A(e){return e===void 0?{}:{scope:e}}function j(e,t){let n=new Set;for(let r of e){if(n.has(r.id))throw new o(`Duplicate ${t} ${r.id}`);n.add(r.id)}}function M(e){let t=N(e).toLowerCase();return t.includes(`expired`)||t.includes(`token`)?{type:`access-lost`,code:`session-expired`,recovery:`reauthenticate`,clearSnapshot:!0}:t.includes(`permission`)||t.includes(`forbidden`)||t.includes(`unauthorized`)?{type:`access-lost`,code:`permission-denied`,recovery:`reauthenticate`,clearSnapshot:!0}:{type:`error`,code:`transport`,recovery:`retry`,clearSnapshot:!0}}function N(e){return e instanceof Error?e.message:e&&typeof e==`object`&&`message`in e?String(e.message):``}function P(e,t){if(!e||typeof e!=`object`||Array.isArray(e))throw new o(`${t} is missing or invalid`);return e}function F(e,t){if(typeof e!=`string`)throw new o(`${t} is invalid`);return e}function I(e,t){let n=F(e,t);if(!n.trim())throw new o(`${t} is empty`);return n}function L(e,t){if(typeof e!=`number`||!Number.isFinite(e)||e<0||e>1)throw new o(`${t} is invalid`);return e}function R(e,t){if(!Number.isSafeInteger(e)||e<0||e>0xe677d21fdbff)throw new o(`${t} is invalid`);return e}function z(e,t){if(!Number.isSafeInteger(e)||e<0)throw new o(`${t} is invalid`);return e}function re(e,t){return e==null?void 0:R(e,t)}function ie(e,t){return e==null?void 0:I(e,t)}function ae(e,t){if(e==null)return{};let n=z(e,t);if(n<1)throw new o(`${t} is invalid`);return{pageNumber:n}}Object.freeze({field:`createdAt`,direction:`desc`,tieBreaker:`id`,tieDirection:`desc`}),Object.freeze({comments:200,replies:1e3,sourceContexts:200}),Object.freeze({field:`createdAt`,direction:`desc`,tieBreaker:`id`,tieDirection:`desc`});var B=class extends Error{code;constructor(e,t){super(t),this.code=e,this.name=`ReviewerCommentSaveError`}};function oe(e,t){let n=e.replace(/\/+$/,``),r=async(e,r)=>{let i=await t();if(!i)throw new B(`AUTH_REQUIRED`,`로그인 세션이 만료되었습니다. 새로고침 후 다시 시도해 주세요.`);let a;try{a=await fetch(`${n}${e}`,{method:`POST`,headers:{authorization:`Bearer ${i}`,"content-type":`application/json`},body:JSON.stringify(r)})}catch{throw new B(`NETWORK`,`리뷰 서버에 연결하지 못했습니다. 연결을 확인하고 다시 시도해 주세요.`)}if(!a.ok)throw new B(...await V(a))};return{createComment:({target:e,...t})=>{let{environment:n,...i}=e;return r(`/v1/comments`,{...t,target:i,...n?{environment:n}:{}})},createReply:e=>r(`/v1/replies`,e),uploadScreenshot:async({pageId:e,commentId:r,screenshot:i})=>{let a=await t();if(!a)throw new B(`AUTH_REQUIRED`,`로그인 세션이 만료되어 위치 캡처를 저장하지 못했습니다.`);let o;try{o=await fetch(`${n}/v1/comments/${encodeURIComponent(r)}/screenshot`,{method:`PUT`,headers:{authorization:`Bearer ${a}`,"content-type":i.contentType,"x-review-page-id":e,"x-review-image-width":String(i.width),"x-review-image-height":String(i.height)},body:se(i.bytes)})}catch{throw new B(`NETWORK`,`리뷰 서버에 연결하지 못해 위치 캡처를 저장하지 못했습니다.`)}if(!o.ok)throw new B(...await V(o))}}}function se(e){let t=new Uint8Array(e.byteLength);return t.set(e),t.buffer}async function V(e){let t=e.status>=500?`리뷰 서버가 코멘트를 저장하지 못했습니다. 잠시 후 다시 시도해 주세요.`:`코멘트를 저장하지 못했습니다.`;try{let n=await e.json();if(!n||typeof n!=`object`)return[`UNKNOWN`,t];let r=n;return[typeof r.error==`string`?r.error:`UNKNOWN`,typeof r.message==`string`&&r.message.trim()?r.message:t]}catch{return[`UNKNOWN`,t]}}function H(e,t={}){let n=e.trim();if(!n)throw Error(`InstantDB app ID is required`);return{async resolve(e){U(e.routeKey,e.location.pathname);let{db:r,user:i}=await W(n,e,t);t.resolveReviewPage&&await t.resolveReviewPage({refreshToken:i.refresh_token,invitationId:e.invitationId,origin:e.location.origin,pathname:e.location.pathname,signal:e.signal});let{data:a}=await r.queryOnce({reviewers:{$:{where:{authUserId:i.id}}},reviewPages:{},invitations:{}}),o=ce(a.invitations,a.reviewers,a.reviewPages,{invitationId:e.invitationId,authUserId:i.id,email:i.email??``,location:e.location,now:Date.now()});if(!o.ok)throw new s(o.code,$(o.code));let c={authUserId:i.id,reviewerId:o.reviewer.id,projectId:o.reviewer.projectId,invitationId:e.invitationId,email:o.reviewer.email||i.email||``,displayName:o.reviewer.displayName||i.email||`Reviewer`,role:o.reviewer.role===`agency`?`agency`:`reviewer`},l=0;return{pageId:o.page.id,resolvePage:async n=>{let i=++l,a=()=>i!==l||n.signal.aborted;if(n.location.origin!==e.location.origin)throw new s(`PAGE_NOT_FOUND`,$(`PAGE_NOT_FOUND`));U(n.routeKey,n.location.pathname);let{data:o}=await r.queryOnce({reviewPages:{$:{where:{projectId:c.projectId}}}});if(a())throw X();let u=Z(n.location.pathname),d=K(o.reviewPages,c.projectId,n.location.origin,u);if(!d&&t.resolveReviewPage){let i=await r.getAuth();if(a())throw X();if(!i?.refresh_token)throw Error(`리뷰 세션이 만료되어 페이지를 등록할 수 없습니다.`);if(await t.resolveReviewPage({refreshToken:i.refresh_token,invitationId:e.invitationId,origin:n.location.origin,pathname:u,signal:n.signal}),a()||({data:o}=await r.queryOnce({reviewPages:{$:{where:{projectId:c.projectId}}}}),a()))throw X();d=K(o.reviewPages,c.projectId,n.location.origin,u)}if(!d)throw new s(`PAGE_NOT_FOUND`,$(`PAGE_NOT_FOUND`));return{pageId:d.id}},session:c,repository:new te(r,c,{...t.serviceUrl===void 0?{}:{commentTransport:oe(t.serviceUrl,async()=>(await r.getAuth())?.refresh_token??null)}}),allowedStagingHosts:[e.location.hostname],sessionScope:`browser`}}}}function U(e,t){if(e!==void 0&&e!==Z(t))throw Error(`Query/hash page identity requires the Convex review runtime.`)}async function W(e,t,n){let r=(n.databaseFactory??ee)(e),i=!!t.emailToken,a;if(t.emailToken){if(!n.redeemEmailInvitation)throw new s(`INVITATION_EMAIL_REQUIRED`,`이 리뷰는 이메일 초대 링크에서만 시작할 수 있습니다.`);await l(t.authHost,t.signal,t.styleNonce);let e=await n.redeemEmailInvitation({invitationId:t.invitationId,emailToken:t.emailToken,origin:t.location.origin,pathname:t.location.pathname,signal:t.signal});await q(t.signal,()=>r.getAuth())&&await q(t.signal,async()=>r.auth.signOut()),await q(t.signal,async()=>r.auth.signInWithToken(e));let i=await q(t.signal,()=>r.getAuth());if(!i)throw Error(`인증 세션을 확인할 수 없습니다.`);a=i}else a=await le(r,t.signal);if(!i)try{await G(n.acceptInvitation,{refreshToken:a.refresh_token,invitationId:t.invitationId,suggestedDisplayName:ue(a.email),authHost:t.authHost,signal:t.signal,styleNonce:t.styleNonce})}catch(e){throw Y(e,`INVITATION_EMAIL_MISMATCH`)?new s(`INVITATION_EMAIL_REQUIRED`,`초대받은 이메일의 링크를 다시 열어 주세요.`):e}return{db:r,user:a}}async function G(e,t){if(!e)return;let n={refreshToken:t.refreshToken,invitationId:t.invitationId,signal:t.signal};try{await e(n);return}catch(e){if(!Y(e,`DISPLAY_NAME_REQUIRED`))throw e}let r=await c(t.authHost,t.signal,t.suggestedDisplayName,t.styleNonce);await e({...n,displayName:r})}function ce(e,t,n,r){let i=e.find(({id:e})=>e===r.invitationId);if(!i||Q(i.email)!==Q(r.email))return{ok:!1,code:`INVITATION_REQUIRED`};if(i.status===`revoked`)return{ok:!1,code:`INVITATION_REVOKED`};if(i.status===`expired`||i.expiresAt<=r.now)return{ok:!1,code:`INVITATION_EXPIRED`};if(i.status!==`accepted`)return{ok:!1,code:`INVITATION_NOT_ACCEPTED`};let a=t.find(e=>e.authUserId===r.authUserId&&e.projectId===i.projectId);if(!a)return{ok:!1,code:`MEMBERSHIP_REQUIRED`};let o=Z(r.location.pathname),s=K(n,i.projectId,r.location.origin,o);return s?{ok:!0,invitation:i,reviewer:a,page:s}:{ok:!1,code:`PAGE_NOT_FOUND`}}function K(e,t,n,r){let i=e.filter(e=>e.projectId===t&&Z(e.pathname)===r),a=i.filter(({stagingOrigin:e})=>e===n);return(a.length>0?a:i).sort((e,t)=>(e.createdAt??2**53-1)-(t.createdAt??2**53-1)||e.id.localeCompare(t.id))[0]}async function le(e,t){let n=await q(t,()=>e.getAuth());if(n)return n;throw new s(`INVITATION_EMAIL_REQUIRED`,`초대받은 이메일의 링크를 열어 주세요.`)}async function q(e,t){J(e);let n=await t();return J(e),n}function J(e){if(e.aborted)throw de()}function ue(e){return e?.split(`@`)[0]?.trim().slice(0,120)??``}function Y(e,t){return!!(e&&typeof e==`object`&&`code`in e&&e.code===t)}function X(){return new DOMException(`Aborted`,`AbortError`)}function de(){return new s(`AUTH_CANCELLED`,`리뷰 로그인이 취소되었습니다.`)}function Z(e){return e.replace(/\/+$/,``)||`/`}function Q(e){return e.trim().toLowerCase()}function $(e){switch(e){case`INVITATION_REQUIRED`:return`현재 계정과 일치하는 리뷰 초대를 찾을 수 없습니다.`;case`INVITATION_NOT_ACCEPTED`:return`아직 수락되지 않은 리뷰 초대입니다.`;case`INVITATION_REVOKED`:return`취소된 리뷰 초대입니다. 계속 검토하려면 담당자에게 다시 요청하세요.`;case`INVITATION_EXPIRED`:return`만료된 리뷰 초대입니다. 담당자에게 새 링크를 요청하세요.`;case`MEMBERSHIP_REQUIRED`:return`이 프로젝트의 reviewer membership이 없습니다.`;case`PAGE_NOT_FOUND`:return`이 페이지는 아직 리뷰 대상이 아닙니다. 담당자에게 문의하세요.`}}export{H as createInstantReviewRuntime};
package/dist/react.js CHANGED
@@ -1 +1 @@
1
- import{t as e}from"./install-Crgjt0uw.js";import{useEffect as t,useRef as n}from"react";function r({activationParam:r,build:i,collaboration:a,developerTools:o,enabled:s,navigateToFeedback:c,offset:l,onError:u,onInactive:d,onReady:f,pageIdentity:p,position:m,runtime:h,serviceUrl:g,sourceContextResolver:_,styleNonce:v}){let y=n(u),b=n(d),x=n(f),S=n(null),C=i?.buildId,w=i?.gitCommit,T=i?.generatedAt,E=i?.mode,D=o?.codeContextCopy===!0||o?.reactGrab===!0,O=a?.cursors===!0,k=a?.chat===!0;return y.current=u,b.current=d,x.current=f,t(()=>{if(s===!1)return;let t=e({activationParam:r,build:C===void 0&&w===void 0&&T===void 0&&E===void 0?void 0:{buildId:C,gitCommit:w,generatedAt:T,mode:E},collaboration:O||k?{cursors:O,chat:k}:void 0,developerTools:D?{codeContextCopy:!0}:void 0,enabled:s,navigateToFeedback:c,offset:l,onError:e=>y.current?.(e),onInactive:e=>{let t=`${e.reason}\n${e.origin}`;S.current!==t&&(S.current=t,b.current?.(e))},onReady:e=>{S.current=null,x.current?.(e)},pageIdentity:p,runtime:h,position:m,serviceUrl:g,sourceContextResolver:_,styleNonce:v});return()=>t.dispose()},[r,C,E,k,O,D,s,T,w,c,l?.x,l?.y,p,m,h,g,_,v]),null}r.displayName=`VisualReview`;export{r as VisualReview};
1
+ import{t as e}from"./install-BXWJcrQ8.js";import{useEffect as t,useRef as n}from"react";function r({activationParam:r,build:i,cursorChat:a,enabled:o,navigateToFeedback:s,offset:c,onError:l,onInactive:u,onReady:d,pageIdentity:f,position:p,runtime:m,serviceUrl:h,sourceContextResolver:g,styleNonce:_}){let v=n(l),y=n(u),b=n(d),x=n(null),S=i?.buildId,C=i?.gitCommit,w=i?.generatedAt,T=i?.mode;return v.current=l,y.current=u,b.current=d,t(()=>{if(o===!1)return;let t=e({activationParam:r,build:S===void 0&&C===void 0&&w===void 0&&T===void 0?void 0:{buildId:S,gitCommit:C,generatedAt:w,mode:T},cursorChat:a,enabled:o,navigateToFeedback:s,offset:c,onError:e=>v.current?.(e),onInactive:e=>{let t=`${e.reason}\n${e.origin}`;x.current!==t&&(x.current=t,y.current?.(e))},onReady:e=>{x.current=null,b.current?.(e)},pageIdentity:f,runtime:m,position:p,serviceUrl:h,sourceContextResolver:g,styleNonce:_});return()=>t.dispose()},[r,S,T,a,o,w,C,s,c?.x,c?.y,f,p,m,h,g,_]),null}r.displayName=`VisualReview`;export{r as VisualReview};
@@ -0,0 +1 @@
1
+ import{c as e,o as t,s as n}from"./src-CjYM9---.js";const r=Symbol.for(`functionName`),i=Symbol.for(`toReferencePath`);function a(e){return e[i]??null}function o(e){return e.startsWith(`function://`)}function s(e){let t;if(typeof e==`string`)t=o(e)?{functionHandle:e}:{name:e};else if(e[r])t={name:e[r]};else{let n=a(e);if(!n)throw Error(`${e} is not a functionReference`);t={reference:n}}return t}function c(e){let t=s(e);if(t.name===void 0)throw t.functionHandle===void 0?t.reference===void 0?Error(`Expected function reference like "api.file.func" or "internal.file.func", but received ${JSON.stringify(t)}`):Error(`Expected function reference in the current component like "api.file.func" or "internal.file.func", but received reference ${t.reference}`):Error(`Expected function reference like "api.file.func" or "internal.file.func", but received function handle ${t.functionHandle}`);if(typeof e==`string`)return e;let n=e[r];if(!n)throw Error(`${e} is not a functionReference`);return n}function l(e){return{[r]:e}}function u(e=[]){return new Proxy({},{get(t,n){if(typeof n==`string`)return u([...e,n]);if(n===r){if(e.length<2){let t=[`api`,...e].join(`.`);throw Error(`API path is expected to be of the form \`api.moduleName.functionName\`. Found: \`${t}\``)}let t=e.slice(0,-1).join(`/`),n=e[e.length-1];return n==="default"?t:t+`:`+n}else if(n===Symbol.toStringTag)return`FunctionReference`;else return}})}u();const d=Object.freeze({resolveSession:l(`reviews:resolveSession`),pageSnapshot:l(`reviews:pageSnapshot`),projectSnapshot:l(`reviews:projectSnapshot`),createComment:l(`reviews:createComment`),createReply:l(`reviews:createReply`),moveCommentPin:l(`reviews:moveCommentPin`),presenceList:l(`presence:list`),presenceUpdate:l(`presence:update`),presenceDisconnect:l(`presence:disconnect`),screenshotUploadUrl:l(`screenshots:generateUploadUrl`),screenshotCommit:l(`screenshots:commitUpload`),screenshotDiscard:l(`screenshots:discardUpload`)});Object.freeze({catalog:l(`owners:catalog`),listProjects:l(`owners:listProjects`),projectOverview:l(`owners:projectOverview`),feedbackPage:l(`owners:feedbackPage`),createProject:l(`owners:createProject`),createProjectWithOrigins:l(`owners:createProjectWithOrigins`),configureProjectLinkAccess:l(`owners:configureProjectLinkAccess`),deleteProject:l(`owners:deleteProject`),addOrigin:l(`owners:addOrigin`),removeOrigin:l(`owners:removeOrigin`),replaceOrigins:l(`owners:replaceOrigins`),createPage:l(`owners:createPage`),deletePage:l(`owners:deletePage`),upsertPageBuild:l(`owners:upsertPageBuild`),createInvitation:l(`owners:createInvitation`),revokeInvitation:l(`owners:revokeInvitation`),revokeReviewerAccess:l(`owners:revokeReviewerAccess`),setCommentStatus:l(`owners:setCommentStatus`),createReply:l(`owners:createReply`),deleteComment:l(`owners:deleteComment`),deleteReply:l(`owners:deleteReply`),screenshotDownload:l(`screenshots:ownerDownload`)});var f=class{client;session;#e;#t;#n;constructor(e,t,n){this.client=e,this.session=t,this.#e=_(n.invitationId??t.invitationId,`invitationId`),this.#t=v(n.origin),this.#n=n.fetch??globalThis.fetch.bind(globalThis)}async attachScreenshot(e,t,n){_(e,`pageId`);let r=_(t,`commentId`);if(n.bytes.byteLength===0||n.bytes.byteLength>15e5)throw Error(`Screenshot size is invalid`);let i=await this.client.mutation(d.screenshotUploadUrl,{...this.#r(),commentId:r}),a=await this.#n(i.uploadUrl,{method:`POST`,headers:{"content-type":n.contentType},body:p(n.bytes)});if(!a.ok)throw Error(`Screenshot upload failed`);let o=await a.json();if(typeof o.storageId!=`string`||!o.storageId)throw Error(`Screenshot upload returned an invalid storage ID`);try{await this.client.mutation(d.screenshotCommit,{...this.#r(),commentId:r,grantId:i.grantId,storageId:o.storageId,contentType:n.contentType,width:n.width,height:n.height})}catch(e){throw await this.client.mutation(d.screenshotDiscard,{...this.#r(),commentId:r,grantId:i.grantId,storageId:o.storageId}).catch(()=>void 0),e}}async getSnapshot(e){return g(await this.client.query(d.pageSnapshot,{...this.#r(),pageId:_(e,`pageId`)}))}async moveCommentPin(e,t,n,r){return this.client.mutation(d.moveCommentPin,{...this.#r(),pageId:_(e,`pageId`),commentId:_(t,`commentId`),anchorJson:JSON.stringify(n),expectedRevision:r})}async getProjectSnapshot(e){return this.#i(e),structuredClone(await this.client.query(d.projectSnapshot,{...this.#r(),projectId:e}))}subscribe(e,t,n){return this.client.onUpdate(d.pageSnapshot,{...this.#r(),pageId:_(e,`pageId`)},e=>t(g(e)),e=>n?.(m(e)))}subscribeProject(e,t,n){return this.#i(e),this.client.onUpdate(d.projectSnapshot,{...this.#r(),projectId:e},e=>t(structuredClone(e)),e=>n?.(m(e)))}async mutate(r,i){switch(i.type){case`comment.upsert`:{let a=i.comment;if(a.pageId!==r)throw Error(`Comment page mismatch`);if(a.authorId!==this.session.reviewerId)throw Error(`Comment author mismatch`);if(a.status!==`open`||a.resolvedAt!==void 0||a.resolvedById!==void 0||a.createdAt!==a.updatedAt)throw Error(`Only new open comments can be created by a reviewer`);let o=n(a.body).trim(),s=t(a.target),c=a.scope===void 0?void 0:e(a.scope);await this.client.mutation(d.createComment,{...this.#r(),pageId:r,commentId:a.id,body:o,targetJson:JSON.stringify(s.publicTarget),sourceContextJson:s.sourceContextJson,...c===void 0?{}:{scopeJson:JSON.stringify(c)}});return}case`reply.upsert`:{let e=i.reply;if(e.authorId!==this.session.reviewerId)throw Error(`Reply author mismatch`);if(e.createdAt!==e.updatedAt)throw Error(`Reply edits require an owner command`);await this.client.mutation(d.createReply,{...this.#r(),commentId:e.commentId,replyId:e.id,body:n(e.body,`Reply body`).trim()});return}case`comment.delete`:throw Error(`Comment deletion requires an owner command`);case`reply.delete`:throw Error(`Reply deletion requires an owner command`)}}#r(){return{invitationId:this.#e,origin:this.#t}}#i(e){if(!e.trim()||e!==this.session.projectId)throw Error(`Project does not belong to the reviewer session`)}};function p(e){let t=new Uint8Array(e.byteLength);return t.set(e),t.buffer}function m(e){let t=h(e);return t===`UNAUTHENTICATED`||t===`INVITATION_EXPIRED`?{type:`access-lost`,code:`session-expired`,recovery:`reauthenticate`,clearSnapshot:!0}:t===`INVITATION_REVOKED`||t===`MEMBERSHIP_REVOKED`?{type:`access-lost`,code:`membership-revoked`,recovery:`reauthenticate`,clearSnapshot:!0}:t===`ORIGIN_MISMATCH`||t===`ORIGIN_NOT_ALLOWED`||t===`INVITATION_REQUIRED`?{type:`access-lost`,code:`permission-denied`,recovery:`reauthenticate`,clearSnapshot:!0}:{type:`error`,code:e instanceof Error?`transport`:`unknown`,recovery:`retry`,clearSnapshot:!0}}function h(e){if(!e||typeof e!=`object`)return;let t=e.data;if(typeof t==`string`)return t;if(t&&typeof t==`object`){let e=t.code;if(typeof e==`string`)return e}}function g(e){return structuredClone(e)}function _(e,t){let n=e.trim();if(!n)throw Error(`${t} is required`);return n}function v(e){let t=_(e,`origin`);try{let e=new URL(t);if(e.origin!==t)throw Error();return e.origin}catch{throw Error(`A valid browser origin is required`)}}export{c as i,m as n,d as r,f as t};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@01.works/visual-review",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
4
  "description": "Invitation-only visual feedback for staging websites, hosted by 01.works.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -17,6 +17,9 @@
17
17
  "THIRD_PARTY_NOTICES.md",
18
18
  "LICENSES"
19
19
  ],
20
+ "bin": {
21
+ "visual-review": "./dist/cli.js"
22
+ },
20
23
  "types": "./dist/index.d.ts",
21
24
  "module": "./dist/index.js",
22
25
  "exports": {
@@ -47,7 +50,7 @@
47
50
  }
48
51
  },
49
52
  "scripts": {
50
- "build": "tsdown --config tsdown.config.ts && tsdown --config tsdown.node.config.ts && node scripts/emit-public-types.mjs",
53
+ "build": "tsdown --config tsdown.config.ts && tsdown --config tsdown.node.config.ts && node scripts/build-cli.mjs && node scripts/emit-public-types.mjs",
51
54
  "prepack": "npm run build"
52
55
  },
53
56
  "engines": {
@@ -71,8 +74,10 @@
71
74
  },
72
75
  "devDependencies": {
73
76
  "@agency-review/convex-adapter": "0.0.0",
77
+ "@agency-review/review-agent": "0.0.0",
74
78
  "@agency-review/review-widget": "0.0.0",
75
79
  "@types/react": "^19.2.0",
80
+ "esbuild": "^0.28.1",
76
81
  "react": "^19.2.8",
77
82
  "react-dom": "^19.2.8"
78
83
  },