commerce-kit 0.39.1 → 0.39.2
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
|
-
{"version":3,"sources":["../src/feedback-toolbar.tsx"],"sourcesContent":["/**\n * Feedback session toolbar — side-effect entry.\n *\n * Importing `commerce-kit/feedback-toolbar` (or `commerce-kit/browser` for the\n * combined entry) mounts a floating toolbar onto the page that lets reviewers\n * leave click-anchored comments. Comments persist via YNS API endpoints,\n * authenticated with the `better-auth` session cookie sent through\n * `credentials: \"include\"`.\n *\n * Gated on `process.env.NEXT_PUBLIC_VERCEL_ENV === \"preview\"` — only Vercel\n * preview deploys get the toolbar. Sandbox dev (env undefined) and production\n * (env === \"production\") skip it.\n */\n\nimport { type CSSProperties, type FormEvent, useEffect, useRef, useState } from \"react\";\nimport { createRoot } from \"react-dom/client\";\n\nconst MOUNT_NODE_ID = \"yns-feedback-toolbar-root\";\n\ninterface FeedbackAttachment {\n\turl: string;\n\tcontentType: string;\n\twidth?: number;\n\theight?: number;\n}\n\ninterface FeedbackComment {\n\tid: string;\n\tpagePath: string;\n\tcssSelector: string;\n\tcontent: string;\n\tstatus: \"todo\" | \"done\";\n\toffsetXRatio: number;\n\toffsetYRatio: number;\n\tattachments: FeedbackAttachment[];\n}\n\ntype SessionStatus = \"created\" | \"in_progress\" | \"processing\" | \"in_review\" | \"done\";\n\ninterface ReviewProgress {\n\tfillPct: number;\n\tlabel: string;\n}\n\ninterface ActiveSession {\n\tfeedbackSessionId: string;\n\tcomments: FeedbackComment[];\n\tcanComment: boolean;\n\tsessionStatus: SessionStatus;\n\tcommentTotal: number;\n\tcommentDone: number;\n\teta: string;\n\tprogress: ReviewProgress;\n}\n\ninterface PendingPin {\n\tcssSelector: string;\n\tpagePath: string;\n\tsurroundingHtml: string;\n\trect: { top: number; left: number; width: number; height: number };\n\tclickX: number;\n\tclickY: number;\n\toffsetXRatio: number;\n\toffsetYRatio: number;\n}\n\ninterface User {\n\tid: string;\n\tname: string | null;\n\temail: string;\n\timage: string | null;\n}\n\ninterface AnonymousResponse {\n\tviewer: \"anonymous\";\n\tsession: { id: string; status: SessionStatus };\n\tloginUrl: string;\n}\n\ninterface NonMemberResponse {\n\tviewer: \"non-member\";\n\tsession: { id: string; status: SessionStatus };\n\tuser: User;\n\taccessDenied: true;\n}\n\ntype MemberResponse = ActiveSession & { viewer: \"member\"; user: User };\n\ntype FeedbackResponse = AnonymousResponse | NonMemberResponse | MemberResponse;\n\nconst buildApiBase = (): string | null => {\n\tif (typeof window === \"undefined\") return null;\n\tconst override = (process.env.NEXT_PUBLIC_YNS_API_BASE ?? \"\").trim();\n\tif (override) return override.replace(/\\/$/, \"\");\n\n\t// Toolbar runs on a per-store preview deploy (mystore-preview.yns.{store|cx})\n\t// served by the merchant's Vercel project — that host has no YNS API. Aim\n\t// at the apex `yns.store` / `yns.cx`, which yns-app serves and where the\n\t// API lives. Cookies scoped to `.yns.store` / `.yns.cx` travel along.\n\tconst host = window.location.hostname;\n\tconst protocol = window.location.protocol;\n\tif (host.endsWith(\".yns.store\")) return `${protocol}//yns.store`;\n\tif (host.endsWith(\".yns.cx\")) return `${protocol}//yns.cx`;\n\treturn window.location.origin;\n};\n\nconst computeCssSelector = (el: Element): string => {\n\tif (el.id) return `#${CSS.escape(el.id)}`;\n\tconst path: string[] = [];\n\tlet node: Element | null = el;\n\twhile (node && node.nodeType === Node.ELEMENT_NODE && path.length < 6) {\n\t\tlet part = node.tagName.toLowerCase();\n\t\tconst className = node.getAttribute(\"class\");\n\t\tif (className) {\n\t\t\tconst classes = className\n\t\t\t\t.split(/\\s+/)\n\t\t\t\t.filter(Boolean)\n\t\t\t\t.slice(0, 2)\n\t\t\t\t.map((c) => `.${CSS.escape(c)}`)\n\t\t\t\t.join(\"\");\n\t\t\tpart += classes;\n\t\t}\n\t\tconst parent: Element | null = node.parentElement;\n\t\tconst tag = node.tagName;\n\t\tif (parent) {\n\t\t\tconst siblings: Element[] = Array.from(parent.children).filter((c) => c.tagName === tag);\n\t\t\tif (siblings.length > 1) {\n\t\t\t\tconst idx = siblings.indexOf(node) + 1;\n\t\t\t\tpart += `:nth-of-type(${idx})`;\n\t\t\t}\n\t\t}\n\t\tpath.unshift(part);\n\t\tnode = parent;\n\t}\n\treturn path.join(\" > \");\n};\n\nconst ATTRS_OF_INTEREST = [\"id\", \"class\", \"data-testid\", \"aria-label\", \"role\", \"name\", \"href\", \"src\"];\n\nconst formatAttrs = (el: Element): string => {\n\tconst parts: string[] = [];\n\tfor (const attr of ATTRS_OF_INTEREST) {\n\t\tconst v = el.getAttribute(attr);\n\t\tif (!v) continue;\n\t\tconst trimmed = v.length > 80 ? `${v.slice(0, 80)}…` : v;\n\t\tparts.push(` ${attr}=\"${trimmed.replace(/\"/g, \""\")}\"`);\n\t}\n\treturn parts.join(\"\");\n};\n\nconst formatTextContent = (el: Element): string => {\n\tconst text = (el.textContent ?? \"\").replace(/\\s+/g, \" \").trim();\n\tif (!text) return \"\";\n\treturn text.length > 100 ? `${text.slice(0, 100)}…` : text;\n};\n\nconst buildSurroundingHtml = (target: Element): string => {\n\tconst ancestors: Element[] = [];\n\tlet cur: Element | null = target;\n\twhile (cur && cur !== document.documentElement && ancestors.length < 5) {\n\t\tconst parent: Element | null = cur.parentElement;\n\t\tif (!parent) break;\n\t\tancestors.unshift(parent);\n\t\tcur = parent;\n\t}\n\n\tconst lines: string[] = [];\n\tlet depth = 0;\n\tfor (const ancestor of ancestors) {\n\t\tconst indent = \" \".repeat(depth);\n\t\tlines.push(`${indent}<${ancestor.tagName.toLowerCase()}${formatAttrs(ancestor)}>`);\n\t\tdepth++;\n\t}\n\n\tconst targetIndent = \" \".repeat(depth);\n\tconst targetTag = target.tagName.toLowerCase();\n\tconst targetText = formatTextContent(target);\n\tlines.push(\n\t\t`${targetIndent}<${targetTag}${formatAttrs(target)}>${\n\t\t\ttargetText ? `${targetText}</${targetTag}>` : \"\"\n\t\t} ← TARGET`,\n\t);\n\n\tif (!targetText) {\n\t\tconst childIndent = \" \".repeat(depth + 1);\n\t\tconst children = Array.from(target.children).slice(0, 6);\n\t\tfor (const child of children) {\n\t\t\tconst childText = formatTextContent(child);\n\t\t\tlines.push(\n\t\t\t\t`${childIndent}<${child.tagName.toLowerCase()}${formatAttrs(child)}>${\n\t\t\t\t\tchildText ? `${childText}</${child.tagName.toLowerCase()}>` : \"\"\n\t\t\t\t}`,\n\t\t\t);\n\t\t}\n\t\tif (target.children.length > 6) {\n\t\t\tlines.push(`${childIndent}… (${target.children.length - 6} more children)`);\n\t\t}\n\t\tlines.push(`${targetIndent}</${targetTag}>`);\n\t}\n\n\tfor (let i = ancestors.length - 1; i >= 0; i--) {\n\t\tconst indent = \" \".repeat(i);\n\t\tconst ancestor = ancestors[i];\n\t\tif (!ancestor) continue;\n\t\tlines.push(`${indent}</${ancestor.tagName.toLowerCase()}>`);\n\t}\n\n\treturn lines.join(\"\\n\");\n};\n\nconst isInsideToolbar = (el: Element | null): boolean => {\n\tlet node = el;\n\twhile (node) {\n\t\tif (node instanceof HTMLElement && node.dataset.ynsFeedbackUi === \"true\") return true;\n\t\tnode = node.parentElement;\n\t}\n\treturn false;\n};\n\nconst IGNORED_TAGS = new Set([\"HTML\", \"HEAD\", \"SCRIPT\", \"STYLE\", \"NOSCRIPT\"]);\n\nconst POLL_INTERVAL_MEMBER_MS = 10_000;\nconst POLL_INTERVAL_LOBBY_MS = 30_000;\n\nconst isFeedbackResponse = (data: unknown): data is FeedbackResponse =>\n\ttypeof data === \"object\" &&\n\tdata !== null &&\n\t\"viewer\" in data &&\n\t(data.viewer === \"anonymous\" || data.viewer === \"non-member\" || data.viewer === \"member\");\n\n// Format a server-computed ETA as remaining + absolute label. The deadline\n// math itself lives in yns-app's `lib/feedback-comments-api.ts` so the toolbar\n// and YNS lock screen stay in sync.\nconst formatEta = (etaIso: string): string => {\n\tconst eta = new Date(etaIso);\n\tconst remainingMs = eta.getTime() - Date.now();\n\tconst dateLabel = eta.toLocaleString(undefined, {\n\t\tweekday: \"short\",\n\t\tmonth: \"short\",\n\t\tday: \"numeric\",\n\t\thour: \"numeric\",\n\t\tminute: \"2-digit\",\n\t});\n\n\tif (remainingMs <= 0) return `Any moment now (estimated by ${dateLabel})`;\n\tconst remainingMin = Math.ceil(remainingMs / 60_000);\n\tif (remainingMin < 60) return `~${remainingMin} minutes (by ${dateLabel})`;\n\tconst remainingHours = Math.round(remainingMs / 3_600_000);\n\tif (remainingHours < 24) {\n\t\treturn `~${remainingHours} ${remainingHours === 1 ? \"hour\" : \"hours\"} (by ${dateLabel})`;\n\t}\n\tconst remainingDays = Math.round(remainingMs / 86_400_000);\n\treturn `~${remainingDays} ${remainingDays === 1 ? \"day\" : \"days\"} (by ${dateLabel})`;\n};\n\n// Sign-out hits the YNS API origin — the same cross-origin host the poll\n// targets. Auth cookies live on the YNS host; the preview host has no auth\n// routes. `credentials: \"include\"` + CORS carries the YNS cookie both ways.\nasync function signOutAndPoll(apiBase: string, pollNow: () => void) {\n\ttry {\n\t\tconst res = await fetch(`${apiBase}/api/auth/sign-out`, {\n\t\t\tmethod: \"POST\",\n\t\t\tcredentials: \"include\",\n\t\t});\n\t\tif (!res.ok) {\n\t\t\tconsole.warn(\"[YNS Feedback Toolbar] sign-out responded non-OK\", res.status);\n\t\t}\n\t} catch (err) {\n\t\t// Network failures don't block the re-poll; the next tick reflects truth.\n\t\tconsole.warn(\"[YNS Feedback Toolbar] sign-out fetch failed\", err);\n\t}\n\tpollNow();\n}\n\nfunction FeedbackToolbar() {\n\tconst [response, setResponse] = useState<FeedbackResponse | null>(null);\n\tconst [loading, setLoading] = useState(true);\n\tconst [pinMode, setPinMode] = useState(false);\n\tconst [pending, setPending] = useState<PendingPin | null>(null);\n\tconst [editingId, setEditingId] = useState<string | null>(null);\n\tconst [sidebarOpen, setSidebarOpen] = useState(false);\n\tconst [finalizing, setFinalizing] = useState(false);\n\tconst apiBase = useRef<string | null>(null);\n\tconst pollNowRef = useRef<() => void>(() => {});\n\n\tuseEffect(() => {\n\t\tapiBase.current = buildApiBase();\n\t\tif (!apiBase.current) {\n\t\t\tsetLoading(false);\n\t\t\treturn;\n\t\t}\n\n\t\tlet cancelled = false;\n\t\t// Latest controller across in-flight fetch + the apply-id that gates which\n\t\t// response is allowed to commit. Both refs together fence the polling loop\n\t\t// against user actions (sign-out, finalize) and visibility transitions.\n\t\tlet abortController: AbortController | null = null;\n\t\tlet latestApplyId = 0;\n\t\tlet pollTimeout: number | null = null;\n\t\t// Tracks the latest applied viewer so cadence can be read synchronously\n\t\t// at scheduling time without waiting for setResponse to flush.\n\t\tlet latestViewer: FeedbackResponse[\"viewer\"] | null = null;\n\n\t\tconst clearTimer = () => {\n\t\t\tif (pollTimeout !== null) {\n\t\t\t\twindow.clearTimeout(pollTimeout);\n\t\t\t\tpollTimeout = null;\n\t\t\t}\n\t\t};\n\n\t\tconst applyResponse = (next: FeedbackResponse | null, myId: number) => {\n\t\t\tif (cancelled || myId < latestApplyId) return;\n\t\t\tlatestViewer = next?.viewer ?? null;\n\t\t\tsetResponse(next);\n\t\t\tsetLoading(false);\n\t\t};\n\n\t\tconst scheduleNext = () => {\n\t\t\tif (cancelled) return;\n\t\t\tconst cadence = latestViewer === \"member\" ? POLL_INTERVAL_MEMBER_MS : POLL_INTERVAL_LOBBY_MS;\n\t\t\tpollTimeout = window.setTimeout(() => {\n\t\t\t\tvoid fetchOnce();\n\t\t\t}, cadence);\n\t\t};\n\n\t\tconst fetchOnce = async () => {\n\t\t\tif (cancelled) return;\n\t\t\tabortController?.abort();\n\t\t\tconst controller = new AbortController();\n\t\t\tabortController = controller;\n\t\t\tconst myId = ++latestApplyId;\n\t\t\ttry {\n\t\t\t\tconst res = await fetch(\n\t\t\t\t\t`${apiBase.current}/api/feedback-comments?host=${encodeURIComponent(window.location.host)}`,\n\t\t\t\t\t{ credentials: \"include\", signal: controller.signal },\n\t\t\t\t);\n\t\t\t\tif (cancelled || myId < latestApplyId) return;\n\t\t\t\tif (res.status === 404 || !res.ok) {\n\t\t\t\t\tapplyResponse(null, myId);\n\t\t\t\t\tscheduleNext();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst data = (await res.json()) as unknown;\n\t\t\t\tif (cancelled || myId < latestApplyId) return;\n\t\t\t\tif (!isFeedbackResponse(data)) {\n\t\t\t\t\tapplyResponse(null, myId);\n\t\t\t\t\tscheduleNext();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tapplyResponse(data, myId);\n\t\t\t\tscheduleNext();\n\t\t\t} catch (err) {\n\t\t\t\t// AbortError fires whenever pollNow/visibilitychange cancels an\n\t\t\t\t// in-flight request — that's not a polling failure, just a normal\n\t\t\t\t// preemption. Don't clobber state or reschedule (the preempting\n\t\t\t\t// call is responsible for the next tick).\n\t\t\t\tif ((err as { name?: string } | null)?.name === \"AbortError\") return;\n\t\t\t\tif (cancelled) return;\n\t\t\t\tapplyResponse(null, myId);\n\t\t\t\tscheduleNext();\n\t\t\t}\n\t\t};\n\n\t\tconst pollNow = () => {\n\t\t\tif (cancelled) return;\n\t\t\tclearTimer();\n\t\t\tvoid fetchOnce();\n\t\t};\n\t\tpollNowRef.current = pollNow;\n\n\t\tconst onVisibility = () => {\n\t\t\tif (document.hidden) {\n\t\t\t\tabortController?.abort();\n\t\t\t\tclearTimer();\n\t\t\t} else {\n\t\t\t\tpollNow();\n\t\t\t}\n\t\t};\n\n\t\tdocument.addEventListener(\"visibilitychange\", onVisibility);\n\t\tvoid fetchOnce();\n\n\t\treturn () => {\n\t\t\tcancelled = true;\n\t\t\tdocument.removeEventListener(\"visibilitychange\", onVisibility);\n\t\t\tabortController?.abort();\n\t\t\tclearTimer();\n\t\t\tpollNowRef.current = () => {};\n\t\t};\n\t}, []);\n\n\t// Derived alias so the existing member-branch code (which reads `session.foo`)\n\t// keeps working without rewrites. Anonymous/non-member render paths return\n\t// before this is consulted.\n\tconst session: MemberResponse | null = response?.viewer === \"member\" ? response : null;\n\n\t// When the session stops accepting comments (finalized → in_review, or\n\t// closed), tear down any in-flight UI so the hover overlay/popover/sidebar\n\t// don't linger after the toolbar's main render returns null.\n\tuseEffect(() => {\n\t\tif (!session || session.canComment) return;\n\t\tsetPinMode(false);\n\t\tsetPending(null);\n\t\tsetSidebarOpen(false);\n\t\tsetEditingId(null);\n\t}, [session]);\n\n\tuseEffect(() => {\n\t\tif (!pinMode) return;\n\t\tdocument.body.style.cursor = \"crosshair\";\n\t\treturn () => {\n\t\t\tdocument.body.style.cursor = \"\";\n\t\t};\n\t}, [pinMode]);\n\n\tuseEffect(() => {\n\t\tif (!pinMode) return;\n\n\t\tconst overlay = document.createElement(\"div\");\n\t\toverlay.dataset.ynsFeedbackUi = \"true\";\n\t\toverlay.style.cssText = [\n\t\t\t\"position: fixed\",\n\t\t\t\"pointer-events: none\",\n\t\t\t\"z-index: 2147483644\",\n\t\t\t\"border: 2px dashed #10b981\",\n\t\t\t\"background: rgba(16, 185, 129, 0.08)\",\n\t\t\t\"border-radius: 3px\",\n\t\t\t\"display: none\",\n\t\t\t\"transition: top 0.05s, left 0.05s, width 0.05s, height 0.05s\",\n\t\t].join(\";\");\n\n\t\tconst label = document.createElement(\"div\");\n\t\tlabel.dataset.ynsFeedbackUi = \"true\";\n\t\tlabel.textContent = \"Click to comment\";\n\t\tlabel.style.cssText = [\n\t\t\t\"position: fixed\",\n\t\t\t\"pointer-events: none\",\n\t\t\t\"z-index: 2147483645\",\n\t\t\t\"background: #059669\",\n\t\t\t\"color: #fff\",\n\t\t\t\"font-size: 11px\",\n\t\t\t\"font-family: ui-sans-serif, system-ui, sans-serif\",\n\t\t\t\"padding: 3px 8px\",\n\t\t\t\"border-radius: 4px\",\n\t\t\t\"white-space: nowrap\",\n\t\t\t\"display: none\",\n\t\t\t\"box-shadow: 0 2px 6px rgba(0,0,0,0.15)\",\n\t\t].join(\";\");\n\n\t\tdocument.documentElement.appendChild(overlay);\n\t\tdocument.documentElement.appendChild(label);\n\n\t\tlet hovered: Element | null = null;\n\t\tconst handleMove = (e: MouseEvent) => {\n\t\t\tconst el = document.elementFromPoint(e.clientX, e.clientY);\n\t\t\tif (!el || IGNORED_TAGS.has(el.tagName) || isInsideToolbar(el)) {\n\t\t\t\toverlay.style.display = \"none\";\n\t\t\t\tlabel.style.display = \"none\";\n\t\t\t\thovered = null;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\thovered = el;\n\t\t\trequestAnimationFrame(() => {\n\t\t\t\tif (hovered !== el) return;\n\t\t\t\tconst rect = el.getBoundingClientRect();\n\t\t\t\toverlay.style.top = `${rect.top}px`;\n\t\t\t\toverlay.style.left = `${rect.left}px`;\n\t\t\t\toverlay.style.width = `${rect.width}px`;\n\t\t\t\toverlay.style.height = `${rect.height}px`;\n\t\t\t\toverlay.style.display = \"block\";\n\t\t\t\tlabel.style.left = `${e.clientX + 14}px`;\n\t\t\t\tlabel.style.top = `${e.clientY + 14}px`;\n\t\t\t\tlabel.style.display = \"block\";\n\t\t\t});\n\t\t};\n\n\t\tconst handleLeave = () => {\n\t\t\toverlay.style.display = \"none\";\n\t\t\tlabel.style.display = \"none\";\n\t\t\thovered = null;\n\t\t};\n\n\t\tdocument.addEventListener(\"mousemove\", handleMove, true);\n\t\tdocument.addEventListener(\"mouseleave\", handleLeave);\n\n\t\treturn () => {\n\t\t\tdocument.removeEventListener(\"mousemove\", handleMove, true);\n\t\t\tdocument.removeEventListener(\"mouseleave\", handleLeave);\n\t\t\toverlay.remove();\n\t\t\tlabel.remove();\n\t\t};\n\t}, [pinMode]);\n\n\tuseEffect(() => {\n\t\tif (!pinMode) return;\n\t\tconst handleClick = (event: MouseEvent) => {\n\t\t\tconst target = event.target;\n\t\t\tif (!(target instanceof Element)) return;\n\t\t\tif (isInsideToolbar(target)) return;\n\n\t\t\tevent.preventDefault();\n\t\t\tevent.stopPropagation();\n\n\t\t\tconst rect = target.getBoundingClientRect();\n\t\t\tconst offsetXRatio = rect.width > 0 ? (event.clientX - rect.left) / rect.width : 0.5;\n\t\t\tconst offsetYRatio = rect.height > 0 ? (event.clientY - rect.top) / rect.height : 0.5;\n\t\t\tsetPending({\n\t\t\t\tcssSelector: computeCssSelector(target),\n\t\t\t\tpagePath: window.location.pathname,\n\t\t\t\tsurroundingHtml: buildSurroundingHtml(target),\n\t\t\t\trect: {\n\t\t\t\t\ttop: rect.top + window.scrollY,\n\t\t\t\t\tleft: rect.left + window.scrollX,\n\t\t\t\t\twidth: rect.width,\n\t\t\t\t\theight: rect.height,\n\t\t\t\t},\n\t\t\t\tclickX: event.clientX + window.scrollX,\n\t\t\t\tclickY: event.clientY + window.scrollY,\n\t\t\t\toffsetXRatio: Math.min(1, Math.max(0, offsetXRatio)),\n\t\t\t\toffsetYRatio: Math.min(1, Math.max(0, offsetYRatio)),\n\t\t\t});\n\t\t\tsetPinMode(false);\n\t\t};\n\t\tdocument.addEventListener(\"click\", handleClick, { capture: true });\n\t\treturn () => document.removeEventListener(\"click\", handleClick, { capture: true });\n\t}, [pinMode]);\n\n\t// After a mutation (POST/PATCH/DELETE), route through the polling loop so the\n\t// discriminated-union handler validates the response and the apply-id guard\n\t// still fences against races. A non-member/anonymous shape coerced into\n\t// ActiveSession here would corrupt state otherwise.\n\tconst refreshComments = () => {\n\t\tpollNowRef.current();\n\t};\n\n\tconst submitNewComment = async (content: string, attachments: FeedbackAttachment[]) => {\n\t\tif (!apiBase.current || !session || !pending) return;\n\t\tconst res = await fetch(`${apiBase.current}/api/feedback-comments`, {\n\t\t\tmethod: \"POST\",\n\t\t\tcredentials: \"include\",\n\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\tbody: JSON.stringify({\n\t\t\t\tfeedbackSessionId: session.feedbackSessionId,\n\t\t\t\tcontent,\n\t\t\t\tpagePath: pending.pagePath,\n\t\t\t\tcssSelector: pending.cssSelector,\n\t\t\t\tsurroundingHtml: pending.surroundingHtml,\n\t\t\t\toffsetXRatio: pending.offsetXRatio,\n\t\t\t\toffsetYRatio: pending.offsetYRatio,\n\t\t\t\t...(attachments.length > 0 ? { attachments } : {}),\n\t\t\t}),\n\t\t});\n\t\tif (!res.ok) return;\n\t\tsetPending(null);\n\t\tsetPinMode(true);\n\t\trefreshComments();\n\t};\n\n\tconst updateComment = async (id: string, content: string, attachments: FeedbackAttachment[]) => {\n\t\tif (!apiBase.current) return;\n\t\tconst res = await fetch(`${apiBase.current}/api/feedback-comments/${id}`, {\n\t\t\tmethod: \"PATCH\",\n\t\t\tcredentials: \"include\",\n\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\tbody: JSON.stringify({ content, attachments }),\n\t\t});\n\t\tif (!res.ok) return;\n\t\tsetEditingId(null);\n\t\trefreshComments();\n\t};\n\n\tconst removeComment = async (id: string) => {\n\t\tif (!apiBase.current) return;\n\t\tconst res = await fetch(`${apiBase.current}/api/feedback-comments/${id}`, {\n\t\t\tmethod: \"DELETE\",\n\t\t\tcredentials: \"include\",\n\t\t});\n\t\tif (!res.ok) return;\n\t\trefreshComments();\n\t};\n\n\tconst finalizeSession = async () => {\n\t\tif (!apiBase.current || !session) return;\n\t\tif (!window.confirm(\"Finalize this feedback session? You won't be able to add more comments.\")) return;\n\t\tsetFinalizing(true);\n\t\ttry {\n\t\t\tconst res = await fetch(\n\t\t\t\t`${apiBase.current}/api/feedback-sessions/${session.feedbackSessionId}/finalize`,\n\t\t\t\t{ method: \"POST\", credentials: \"include\" },\n\t\t\t);\n\t\t\tif (!res.ok) return;\n\t\t\t// Flip BOTH canComment and sessionStatus locally. Without the status\n\t\t\t// flip, the render guard sees `canComment=false` + `status=in_progress`\n\t\t\t// (a state the SubmittedPanel branch returns null for) and the toolbar\n\t\t\t// disappears for the ~10s gap until the next poll catches up.\n\t\t\tconst body = (await res.json().catch(() => null)) as { status?: SessionStatus } | null;\n\t\t\tconst nextStatus: SessionStatus = body?.status ?? \"processing\";\n\t\t\tsetResponse((prev) =>\n\t\t\t\tprev?.viewer === \"member\" ? { ...prev, canComment: false, sessionStatus: nextStatus } : prev,\n\t\t\t);\n\t\t} finally {\n\t\t\tsetFinalizing(false);\n\t\t}\n\t};\n\n\tconst scrollToComment = (comment: FeedbackComment) => {\n\t\tif (comment.pagePath !== window.location.pathname) {\n\t\t\twindow.location.assign(comment.pagePath);\n\t\t\treturn;\n\t\t}\n\t\tlet el: Element | null = null;\n\t\ttry {\n\t\t\tel = document.querySelector(comment.cssSelector);\n\t\t} catch {\n\t\t\tel = null;\n\t\t}\n\t\tif (!el) return;\n\t\tel.scrollIntoView({ behavior: \"smooth\", block: \"center\" });\n\t\tsetEditingId(comment.id);\n\t};\n\n\tif (loading || !response) return null;\n\n\tif (response.viewer === \"anonymous\") {\n\t\treturn <AnonymousPill loginUrl={response.loginUrl} />;\n\t}\n\n\tif (response.viewer === \"non-member\") {\n\t\treturn (\n\t\t\t<NonMemberPill\n\t\t\t\tuser={response.user}\n\t\t\t\tonSignOut={() => {\n\t\t\t\t\tif (!apiBase.current) return;\n\t\t\t\t\tvoid signOutAndPoll(apiBase.current, () => pollNowRef.current());\n\t\t\t\t}}\n\t\t\t/>\n\t\t);\n\t}\n\n\t// response.viewer === \"member\" — `session` (derived above) mirrors `response`.\n\t// The redundant null guard satisfies TypeScript since the derivation can't\n\t// narrow across the early returns above.\n\tif (!session) return null;\n\n\t// Session has been finalized for AI review. Show a small status panel with\n\t// the same progress + ETA shown in YNS, but no commenting controls.\n\tif (!session.canComment) {\n\t\tif (session.sessionStatus !== \"processing\" && session.sessionStatus !== \"in_review\") return null;\n\t\treturn (\n\t\t\t<div data-yns-feedback-ui=\"true\">\n\t\t\t\t<SubmittedPanel progress={session.progress} eta={session.eta} status={session.sessionStatus} />\n\t\t\t</div>\n\t\t);\n\t}\n\n\tconst visiblePins = session.comments.filter(\n\t\t(c) => c.pagePath === window.location.pathname && c.status !== \"done\",\n\t);\n\n\treturn (\n\t\t<div data-yns-feedback-ui=\"true\">\n\t\t\t{visiblePins.map((pin, idx) => (\n\t\t\t\t<PinOverlay\n\t\t\t\t\tkey={pin.id}\n\t\t\t\t\tpin={pin}\n\t\t\t\t\tnumber={idx + 1}\n\t\t\t\t\tediting={editingId === pin.id}\n\t\t\t\t\tfeedbackSessionId={session.feedbackSessionId}\n\t\t\t\t\tapiBase={apiBase.current}\n\t\t\t\t\tonStartEdit={() => setEditingId(pin.id)}\n\t\t\t\t\tonCancelEdit={() => setEditingId(null)}\n\t\t\t\t\tonSave={(content, attachments) => updateComment(pin.id, content, attachments)}\n\t\t\t\t\tonRemove={() => removeComment(pin.id)}\n\t\t\t\t/>\n\t\t\t))}\n\n\t\t\t{pending && (\n\t\t\t\t<PendingCommentPopover\n\t\t\t\t\tpending={pending}\n\t\t\t\t\tfeedbackSessionId={session.feedbackSessionId}\n\t\t\t\t\tapiBase={apiBase.current}\n\t\t\t\t\tonCancel={() => {\n\t\t\t\t\t\tsetPending(null);\n\t\t\t\t\t\tsetPinMode(true);\n\t\t\t\t\t}}\n\t\t\t\t\tonSave={(content, attachments) => submitNewComment(content, attachments)}\n\t\t\t\t/>\n\t\t\t)}\n\n\t\t\t{sidebarOpen && (\n\t\t\t\t<CommentsSidebar\n\t\t\t\t\tcomments={session.comments}\n\t\t\t\t\tcurrentPath={window.location.pathname}\n\t\t\t\t\tonClose={() => setSidebarOpen(false)}\n\t\t\t\t\tonSelect={scrollToComment}\n\t\t\t\t/>\n\t\t\t)}\n\n\t\t\t<div style={toolbarStyle}>\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tonClick={() => setPinMode((v) => !v)}\n\t\t\t\t\tstyle={pinMode ? toolbarButtonActiveStyle : toolbarButtonStyle}\n\t\t\t\t>\n\t\t\t\t\t{pinMode ? \"Cancel\" : \"Add comment\"}\n\t\t\t\t</button>\n\t\t\t\t<button type=\"button\" onClick={() => setSidebarOpen((v) => !v)} style={toolbarButtonGhostStyle}>\n\t\t\t\t\t{sidebarOpen ? \"Hide list\" : `List (${session.comments.length})`}\n\t\t\t\t</button>\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tonClick={finalizeSession}\n\t\t\t\t\tdisabled={finalizing}\n\t\t\t\t\tstyle={toolbarButtonFinalizeStyle}\n\t\t\t\t>\n\t\t\t\t\t{finalizing ? \"Finalizing…\" : \"Finalize\"}\n\t\t\t\t</button>\n\t\t\t\t<span style={toolbarHintStyle}>\n\t\t\t\t\t{pinMode ? \"Click any element to comment\" : `${visiblePins.length} on this page`}\n\t\t\t\t</span>\n\t\t\t\t<IdentityChip\n\t\t\t\t\tuser={session.user}\n\t\t\t\t\tonSignOut={() => {\n\t\t\t\t\t\tif (!apiBase.current) return;\n\t\t\t\t\t\tvoid signOutAndPoll(apiBase.current, () => pollNowRef.current());\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nfunction SubmittedPanel({\n\tprogress,\n\teta: etaIso,\n\tstatus,\n}: {\n\tprogress: ReviewProgress;\n\teta: string;\n\tstatus: SessionStatus;\n}) {\n\t// Re-render every minute so the relative ETA label stays fresh between\n\t// the 10s data polls (which only re-render when the API payload changes).\n\tconst [, setTick] = useState(0);\n\tuseEffect(() => {\n\t\tconst id = window.setInterval(() => setTick((t) => t + 1), 60_000);\n\t\treturn () => window.clearInterval(id);\n\t}, []);\n\n\tconst eta = formatEta(etaIso);\n\tconst isInReview = status === \"in_review\";\n\tconst headline = isInReview ? \"Feedback under review\" : \"Applying feedback\";\n\n\treturn (\n\t\t<div style={submittedPanelStyle}>\n\t\t\t<style>{spinnerKeyframes}</style>\n\t\t\t<div style={submittedHeaderStyle}>\n\t\t\t\t<Spinner />\n\t\t\t\t<span style={submittedBadgeStyle}>Submitted</span>\n\t\t\t\t<strong style={{ fontSize: 13 }}>{headline}</strong>\n\t\t\t</div>\n\t\t\t<div style={submittedProgressLabelStyle}>\n\t\t\t\t<span>Review progress</span>\n\t\t\t\t<span style={{ opacity: 0.7 }}>{progress.label}</span>\n\t\t\t</div>\n\t\t\t<div style={submittedProgressTrackStyle}>\n\t\t\t\t<div style={{ ...submittedProgressFillStyle, width: `${progress.fillPct}%` }} />\n\t\t\t</div>\n\t\t\t<p style={submittedEtaStyle}>\n\t\t\t\tEstimated delivery: <span style={{ fontWeight: 600 }}>{eta}</span>\n\t\t\t</p>\n\t\t</div>\n\t);\n}\n\n// Inline keyframes — toolbar avoids relying on a global stylesheet.\nconst spinnerKeyframes = `@keyframes yns-feedback-spin { to { transform: rotate(360deg); } }`;\n\nfunction Spinner({ size = 14 }: { size?: number }) {\n\treturn (\n\t\t<span\n\t\t\taria-hidden\n\t\t\tstyle={{\n\t\t\t\tdisplay: \"inline-block\",\n\t\t\t\twidth: size,\n\t\t\t\theight: size,\n\t\t\t\tborder: \"2px solid rgba(16, 185, 129, 0.25)\",\n\t\t\t\tborderTopColor: \"#10b981\",\n\t\t\t\tborderRadius: 999,\n\t\t\t\tanimation: \"yns-feedback-spin 0.9s linear infinite\",\n\t\t\t}}\n\t\t/>\n\t);\n}\n\nfunction PinOverlay({\n\tpin,\n\tnumber,\n\tediting,\n\tfeedbackSessionId,\n\tapiBase,\n\tonStartEdit,\n\tonCancelEdit,\n\tonSave,\n\tonRemove,\n}: {\n\tpin: FeedbackComment;\n\tnumber: number;\n\tediting: boolean;\n\tfeedbackSessionId: string;\n\tapiBase: string | null;\n\tonStartEdit: () => void;\n\tonCancelEdit: () => void;\n\tonSave: (content: string, attachments: FeedbackAttachment[]) => Promise<void>;\n\tonRemove: () => Promise<void>;\n}) {\n\tconst target = useTargetPosition(pin.cssSelector, pin.offsetXRatio, pin.offsetYRatio);\n\tif (!target) return null;\n\n\treturn (\n\t\t<div\n\t\t\tstyle={{\n\t\t\t\tposition: \"absolute\",\n\t\t\t\ttop: target.top,\n\t\t\t\tleft: target.left,\n\t\t\t\tzIndex: 2147483600,\n\t\t\t\tpointerEvents: \"auto\",\n\t\t\t}}\n\t\t>\n\t\t\t<button type=\"button\" onClick={onStartEdit} style={pinDotStyle} title={pin.content}>\n\t\t\t\t{number}\n\t\t\t</button>\n\t\t\t{editing && (\n\t\t\t\t<EditPopover\n\t\t\t\t\tinitial={pin.content}\n\t\t\t\t\tinitialAttachments={pin.attachments}\n\t\t\t\t\tfeedbackSessionId={feedbackSessionId}\n\t\t\t\t\tapiBase={apiBase}\n\t\t\t\t\tonCancel={onCancelEdit}\n\t\t\t\t\tonSave={onSave}\n\t\t\t\t\tonRemove={onRemove}\n\t\t\t\t/>\n\t\t\t)}\n\t\t</div>\n\t);\n}\n\nfunction PendingCommentPopover({\n\tpending,\n\tfeedbackSessionId,\n\tapiBase,\n\tonCancel,\n\tonSave,\n}: {\n\tpending: PendingPin;\n\tfeedbackSessionId: string;\n\tapiBase: string | null;\n\tonCancel: () => void;\n\tonSave: (content: string, attachments: FeedbackAttachment[]) => Promise<void>;\n}) {\n\treturn (\n\t\t<>\n\t\t\t<div\n\t\t\t\tstyle={{\n\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\ttop: pending.rect.top + pending.rect.height * pending.offsetYRatio - 12,\n\t\t\t\t\tleft: pending.rect.left + pending.rect.width * pending.offsetXRatio - 12,\n\t\t\t\t\tzIndex: 2147483600,\n\t\t\t\t\tpointerEvents: \"none\",\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t<div style={pinDotStyle}>•</div>\n\t\t\t</div>\n\t\t\t<div\n\t\t\t\tstyle={{\n\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\ttop: pending.clickY + 10,\n\t\t\t\t\tleft: pending.clickX + 10,\n\t\t\t\t\tzIndex: 2147483647,\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t<EditPopover\n\t\t\t\t\tinitial=\"\"\n\t\t\t\t\tinitialAttachments={[]}\n\t\t\t\t\tfeedbackSessionId={feedbackSessionId}\n\t\t\t\t\tapiBase={apiBase}\n\t\t\t\t\tonCancel={onCancel}\n\t\t\t\t\tonSave={onSave}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</>\n\t);\n}\n\nfunction CommentsSidebar({\n\tcomments,\n\tcurrentPath,\n\tonClose,\n\tonSelect,\n}: {\n\tcomments: FeedbackComment[];\n\tcurrentPath: string;\n\tonClose: () => void;\n\tonSelect: (comment: FeedbackComment) => void;\n}) {\n\tconst groups = new Map<string, FeedbackComment[]>();\n\tfor (const c of comments) {\n\t\tconst list = groups.get(c.pagePath) ?? [];\n\t\tlist.push(c);\n\t\tgroups.set(c.pagePath, list);\n\t}\n\tconst paths = Array.from(groups.keys()).sort((a, b) => {\n\t\tif (a === currentPath) return -1;\n\t\tif (b === currentPath) return 1;\n\t\treturn a.localeCompare(b);\n\t});\n\n\treturn (\n\t\t<div style={sidebarStyle}>\n\t\t\t<div style={sidebarHeaderStyle}>\n\t\t\t\t<strong style={{ fontSize: 14 }}>Comments ({comments.length})</strong>\n\t\t\t\t<button type=\"button\" onClick={onClose} style={ghostButtonStyle}>\n\t\t\t\t\tClose\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t\t<div style={sidebarScrollStyle}>\n\t\t\t\t{comments.length === 0 ? (\n\t\t\t\t\t<div style={sidebarEmptyStyle}>No comments yet. Click “Add comment” to leave one.</div>\n\t\t\t\t) : (\n\t\t\t\t\tpaths.map((path) => (\n\t\t\t\t\t\t<div key={path} style={{ marginBottom: 12 }}>\n\t\t\t\t\t\t\t<div style={sidebarPathStyle}>{path === currentPath ? `${path} · current` : path}</div>\n\t\t\t\t\t\t\t{(groups.get(path) ?? []).map((c, idx) => (\n\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\tkey={c.id}\n\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\tonClick={() => onSelect(c)}\n\t\t\t\t\t\t\t\t\tstyle={sidebarItemStyle}\n\t\t\t\t\t\t\t\t\tdisabled={c.status === \"done\" && false}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<span style={sidebarItemIndexStyle}>{idx + 1}</span>\n\t\t\t\t\t\t\t\t\t<span style={sidebarItemTextStyle}>\n\t\t\t\t\t\t\t\t\t\t{c.content.length > 140 ? `${c.content.slice(0, 140)}…` : c.content}\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t{c.status === \"done\" && <span style={sidebarItemDoneStyle}>done</span>}\n\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t))\n\t\t\t\t)}\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nconst MAX_ATTACHMENTS = 5;\nconst MAX_ATTACHMENT_BYTES = 5 * 1024 * 1024;\n\nconst readImageDimensions = (file: File): Promise<{ width: number; height: number } | null> =>\n\tnew Promise((resolve) => {\n\t\tconst url = URL.createObjectURL(file);\n\t\tconst img = new window.Image();\n\t\timg.onload = () => {\n\t\t\tURL.revokeObjectURL(url);\n\t\t\tresolve({ width: img.naturalWidth, height: img.naturalHeight });\n\t\t};\n\t\timg.onerror = () => {\n\t\t\tURL.revokeObjectURL(url);\n\t\t\tresolve(null);\n\t\t};\n\t\timg.src = url;\n\t});\n\nfunction EditPopover({\n\tinitial,\n\tinitialAttachments,\n\tfeedbackSessionId,\n\tapiBase,\n\tonCancel,\n\tonSave,\n\tonRemove,\n}: {\n\tinitial: string;\n\tinitialAttachments: FeedbackAttachment[];\n\tfeedbackSessionId: string;\n\tapiBase: string | null;\n\tonCancel: () => void;\n\tonSave: (content: string, attachments: FeedbackAttachment[]) => Promise<void>;\n\tonRemove?: () => Promise<void>;\n}) {\n\tconst [value, setValue] = useState(initial);\n\tconst [attachments, setAttachments] = useState<FeedbackAttachment[]>(initialAttachments);\n\tconst [saving, setSaving] = useState(false);\n\tconst [uploading, setUploading] = useState(false);\n\tconst [error, setError] = useState<string | null>(null);\n\tconst textareaRef = useRef<HTMLTextAreaElement | null>(null);\n\tconst fileInputRef = useRef<HTMLInputElement | null>(null);\n\n\tuseEffect(() => {\n\t\tconst el = textareaRef.current;\n\t\tif (!el) return;\n\t\tel.focus();\n\t\tconst len = el.value.length;\n\t\tel.setSelectionRange(len, len);\n\t}, []);\n\n\tconst handleSubmit = async (e: FormEvent) => {\n\t\te.preventDefault();\n\t\tconst content = value.trim();\n\t\tif (!content) return;\n\t\tsetSaving(true);\n\t\ttry {\n\t\t\tawait onSave(content, attachments);\n\t\t} finally {\n\t\t\tsetSaving(false);\n\t\t}\n\t};\n\n\tconst handleFiles = async (files: FileList | null) => {\n\t\tif (!files || files.length === 0 || !apiBase) return;\n\t\tsetError(null);\n\t\tconst slots = MAX_ATTACHMENTS - attachments.length;\n\t\tif (slots <= 0) {\n\t\t\tsetError(`Max ${MAX_ATTACHMENTS} images per comment`);\n\t\t\treturn;\n\t\t}\n\t\tconst picked = Array.from(files).slice(0, slots);\n\t\tfor (const f of picked) {\n\t\t\tif (!f.type.startsWith(\"image/\")) {\n\t\t\t\tsetError(`\"${f.name}\" is not an image`);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (f.size > MAX_ATTACHMENT_BYTES) {\n\t\t\t\tsetError(`\"${f.name}\" exceeds 5 MB`);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tsetUploading(true);\n\t\ttry {\n\t\t\tconst dims = await Promise.all(picked.map(readImageDimensions));\n\t\t\tconst fd = new FormData();\n\t\t\tpicked.forEach((file, i) => {\n\t\t\t\tfd.append(\"file\", file);\n\t\t\t\tfd.append(\"width\", dims[i]?.width ? String(dims[i]?.width) : \"\");\n\t\t\t\tfd.append(\"height\", dims[i]?.height ? String(dims[i]?.height) : \"\");\n\t\t\t});\n\t\t\tconst res = await fetch(\n\t\t\t\t`${apiBase}/api/feedback-comments/uploads?feedbackSessionId=${encodeURIComponent(feedbackSessionId)}`,\n\t\t\t\t{ method: \"POST\", credentials: \"include\", body: fd },\n\t\t\t);\n\t\t\tif (!res.ok) {\n\t\t\t\tconst body = (await res.json().catch(() => null)) as { error?: string } | null;\n\t\t\t\tsetError(body?.error ?? \"Upload failed\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst body = (await res.json()) as { uploads: FeedbackAttachment[] };\n\t\t\tsetAttachments((prev) => [...prev, ...body.uploads]);\n\t\t} catch {\n\t\t\tsetError(\"Upload failed\");\n\t\t} finally {\n\t\t\tsetUploading(false);\n\t\t\tif (fileInputRef.current) fileInputRef.current.value = \"\";\n\t\t}\n\t};\n\n\tconst removeAttachment = (url: string) => {\n\t\tsetAttachments((prev) => prev.filter((a) => a.url !== url));\n\t};\n\n\treturn (\n\t\t<form onSubmit={handleSubmit} style={popoverStyle}>\n\t\t\t<textarea\n\t\t\t\tref={textareaRef}\n\t\t\t\tvalue={value}\n\t\t\t\tonChange={(e) => setValue(e.target.value)}\n\t\t\t\tplaceholder=\"Leave a comment…\"\n\t\t\t\tstyle={textareaStyle}\n\t\t\t\trows={3}\n\t\t\t/>\n\t\t\t{attachments.length > 0 && (\n\t\t\t\t<div style={attachmentRowStyle}>\n\t\t\t\t\t{attachments.map((att) => (\n\t\t\t\t\t\t<div key={att.url} style={attachmentThumbWrapStyle}>\n\t\t\t\t\t\t\t<img src={att.url} alt=\"\" style={attachmentThumbStyle} />\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\tonClick={() => removeAttachment(att.url)}\n\t\t\t\t\t\t\t\tstyle={attachmentRemoveStyle}\n\t\t\t\t\t\t\t\tdisabled={saving || uploading}\n\t\t\t\t\t\t\t\taria-label=\"Remove attachment\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t×\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t))}\n\t\t\t\t</div>\n\t\t\t)}\n\t\t\t{error && <div style={errorTextStyle}>{error}</div>}\n\t\t\t<input\n\t\t\t\tref={fileInputRef}\n\t\t\t\ttype=\"file\"\n\t\t\t\taccept=\"image/*\"\n\t\t\t\tmultiple\n\t\t\t\tonChange={(e) => void handleFiles(e.target.files)}\n\t\t\t\tstyle={{ display: \"none\" }}\n\t\t\t/>\n\t\t\t<div style={popoverActionsStyle}>\n\t\t\t\t{onRemove && (\n\t\t\t\t\t<button type=\"button\" onClick={() => onRemove()} style={dangerButtonStyle} disabled={saving}>\n\t\t\t\t\t\tDelete\n\t\t\t\t\t</button>\n\t\t\t\t)}\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tonClick={() => fileInputRef.current?.click()}\n\t\t\t\t\tstyle={attachIconButtonStyle}\n\t\t\t\t\tdisabled={saving || uploading || attachments.length >= MAX_ATTACHMENTS}\n\t\t\t\t\taria-label={uploading ? \"Uploading…\" : \"Attach image\"}\n\t\t\t\t\ttitle={uploading ? \"Uploading…\" : \"Attach image\"}\n\t\t\t\t>\n\t\t\t\t\t{uploading ? <SpinnerGlyph /> : <PaperclipGlyph />}\n\t\t\t\t</button>\n\t\t\t\t<div style={{ flex: 1 }} />\n\t\t\t\t<button type=\"button\" onClick={onCancel} style={ghostButtonStyle} disabled={saving}>\n\t\t\t\t\tCancel\n\t\t\t\t</button>\n\t\t\t\t<button type=\"submit\" style={primaryButtonStyle} disabled={saving || uploading || !value.trim()}>\n\t\t\t\t\t{saving ? \"Saving…\" : \"Save\"}\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t</form>\n\t);\n}\n\nfunction useTargetPosition(selector: string, offsetXRatio: number, offsetYRatio: number) {\n\tconst [pos, setPos] = useState<{ top: number; left: number } | null>(null);\n\n\tuseEffect(() => {\n\t\tconst update = () => {\n\t\t\tlet el: Element | null = null;\n\t\t\ttry {\n\t\t\t\tel = document.querySelector(selector);\n\t\t\t} catch {\n\t\t\t\tel = null;\n\t\t\t}\n\t\t\tif (!el) {\n\t\t\t\tsetPos(null);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst r = el.getBoundingClientRect();\n\t\t\tsetPos({\n\t\t\t\ttop: r.top + window.scrollY + r.height * offsetYRatio - 12,\n\t\t\t\tleft: r.left + window.scrollX + r.width * offsetXRatio - 12,\n\t\t\t});\n\t\t};\n\n\t\tupdate();\n\t\tconst ro = new ResizeObserver(update);\n\t\ttry {\n\t\t\tconst el = document.querySelector(selector);\n\t\t\tif (el) ro.observe(el);\n\t\t} catch {\n\t\t\t// swallow invalid selector\n\t\t}\n\t\twindow.addEventListener(\"scroll\", update, true);\n\t\twindow.addEventListener(\"resize\", update);\n\t\treturn () => {\n\t\t\tro.disconnect();\n\t\t\twindow.removeEventListener(\"scroll\", update, true);\n\t\t\twindow.removeEventListener(\"resize\", update);\n\t\t};\n\t}, [selector, offsetXRatio, offsetYRatio]);\n\n\treturn pos;\n}\n\n// Inline styles — toolbar is injected into arbitrary stores, so we avoid relying\n// on any CSS framework being present.\nconst toolbarStyle: CSSProperties = {\n\tposition: \"fixed\",\n\tbottom: 16,\n\tleft: \"50%\",\n\ttransform: \"translateX(-50%)\",\n\tzIndex: 2147483646,\n\tdisplay: \"flex\",\n\talignItems: \"center\",\n\tgap: 8,\n\tpadding: \"8px 12px\",\n\tbackground: \"rgba(17, 17, 17, 0.92)\",\n\tcolor: \"white\",\n\tborderRadius: 999,\n\tboxShadow: \"0 8px 24px rgba(0,0,0,0.25)\",\n\tfontFamily:\n\t\t'-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\"',\n\tfontSize: 14,\n\tpointerEvents: \"auto\",\n};\n\nconst toolbarButtonStyle: CSSProperties = {\n\tborder: \"none\",\n\tbackground: \"white\",\n\tcolor: \"#111\",\n\tpadding: \"6px 12px\",\n\tborderRadius: 999,\n\tcursor: \"pointer\",\n\tfontWeight: 600,\n\tfontSize: 13,\n};\n\nconst toolbarButtonActiveStyle: CSSProperties = {\n\t...toolbarButtonStyle,\n\tbackground: \"#ef4444\",\n\tcolor: \"white\",\n};\n\nconst toolbarButtonGhostStyle: CSSProperties = {\n\tborder: \"1px solid rgba(255,255,255,0.4)\",\n\tbackground: \"transparent\",\n\tcolor: \"white\",\n\tpadding: \"6px 12px\",\n\tborderRadius: 999,\n\tcursor: \"pointer\",\n\tfontWeight: 500,\n\tfontSize: 13,\n};\n\nconst toolbarButtonFinalizeStyle: CSSProperties = {\n\tborder: \"none\",\n\tbackground: \"#10b981\",\n\tcolor: \"white\",\n\tpadding: \"6px 12px\",\n\tborderRadius: 999,\n\tcursor: \"pointer\",\n\tfontWeight: 600,\n\tfontSize: 13,\n};\n\nconst toolbarHintStyle: CSSProperties = {\n\topacity: 0.8,\n\tfontSize: 12,\n};\n\nconst pinDotStyle: CSSProperties = {\n\twidth: 24,\n\theight: 24,\n\tborderRadius: 999,\n\tbackground: \"#10b981\",\n\tcolor: \"white\",\n\tborder: \"2px solid white\",\n\tcursor: \"pointer\",\n\tfontSize: 12,\n\tfontWeight: 700,\n\tboxShadow: \"0 2px 6px rgba(0,0,0,0.3)\",\n\tdisplay: \"inline-flex\",\n\talignItems: \"center\",\n\tjustifyContent: \"center\",\n\tpadding: 0,\n};\n\nconst popoverStyle: CSSProperties = {\n\tbackground: \"white\",\n\tborder: \"1px solid #e5e7eb\",\n\tborderRadius: 8,\n\tpadding: 12,\n\twidth: 280,\n\tboxShadow: \"0 12px 32px rgba(0,0,0,0.18)\",\n\tfontFamily:\n\t\t'-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\"',\n\tdisplay: \"flex\",\n\tflexDirection: \"column\",\n\tgap: 8,\n\tcolor: \"#111\",\n};\n\nconst textareaStyle: CSSProperties = {\n\twidth: \"100%\",\n\tborder: \"1px solid #d1d5db\",\n\tborderRadius: 6,\n\tpadding: 8,\n\tfontSize: 14,\n\tresize: \"vertical\",\n\tfontFamily: \"inherit\",\n\tcolor: \"#111\",\n\tbackground: \"white\",\n\tboxSizing: \"border-box\",\n};\n\nconst popoverActionsStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\talignItems: \"center\",\n\tgap: 6,\n};\n\nconst baseButton: CSSProperties = {\n\tborder: \"1px solid transparent\",\n\tborderRadius: 6,\n\tpadding: \"6px 10px\",\n\tfontSize: 13,\n\tcursor: \"pointer\",\n\tfontWeight: 500,\n};\n\nconst primaryButtonStyle: CSSProperties = {\n\t...baseButton,\n\tbackground: \"#111\",\n\tcolor: \"white\",\n};\n\nconst ghostButtonStyle: CSSProperties = {\n\t...baseButton,\n\tbackground: \"transparent\",\n\tcolor: \"#374151\",\n\tborderColor: \"#d1d5db\",\n};\n\nconst dangerButtonStyle: CSSProperties = {\n\t...baseButton,\n\tbackground: \"transparent\",\n\tcolor: \"#b91c1c\",\n\tborderColor: \"#fecaca\",\n};\n\nconst attachmentRowStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\tflexWrap: \"wrap\",\n\tgap: 6,\n};\n\nconst attachmentThumbWrapStyle: CSSProperties = {\n\tposition: \"relative\",\n\twidth: 56,\n\theight: 56,\n\tborderRadius: 6,\n\toverflow: \"hidden\",\n\tborder: \"1px solid #e5e7eb\",\n\tbackground: \"#f9fafb\",\n};\n\nconst attachmentThumbStyle: CSSProperties = {\n\twidth: \"100%\",\n\theight: \"100%\",\n\tobjectFit: \"cover\",\n\tdisplay: \"block\",\n};\n\nconst attachmentRemoveStyle: CSSProperties = {\n\tposition: \"absolute\",\n\ttop: 2,\n\tright: 2,\n\twidth: 18,\n\theight: 18,\n\tborderRadius: 999,\n\tborder: \"none\",\n\tbackground: \"rgba(17, 17, 17, 0.85)\",\n\tcolor: \"white\",\n\tfontSize: 13,\n\tlineHeight: \"16px\",\n\tcursor: \"pointer\",\n\tpadding: 0,\n\tdisplay: \"inline-flex\",\n\talignItems: \"center\",\n\tjustifyContent: \"center\",\n};\n\nconst errorTextStyle: CSSProperties = {\n\tcolor: \"#b91c1c\",\n\tfontSize: 12,\n\tmargin: 0,\n};\n\nconst attachIconButtonStyle: CSSProperties = {\n\t...baseButton,\n\tbackground: \"transparent\",\n\tcolor: \"#374151\",\n\tborderColor: \"#d1d5db\",\n\twidth: 30,\n\theight: 30,\n\tpadding: 0,\n\tdisplay: \"inline-flex\",\n\talignItems: \"center\",\n\tjustifyContent: \"center\",\n\tflexShrink: 0,\n};\n\nfunction PaperclipGlyph() {\n\treturn (\n\t\t<svg\n\t\t\trole=\"img\"\n\t\t\taria-label=\"Attach image\"\n\t\t\twidth=\"14\"\n\t\t\theight=\"14\"\n\t\t\tviewBox=\"0 0 24 24\"\n\t\t\tfill=\"none\"\n\t\t\tstroke=\"currentColor\"\n\t\t\tstrokeWidth=\"2\"\n\t\t\tstrokeLinecap=\"round\"\n\t\t\tstrokeLinejoin=\"round\"\n\t\t>\n\t\t\t<title>Attach image</title>\n\t\t\t<path d=\"m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 17.93 8.83l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48\" />\n\t\t</svg>\n\t);\n}\n\nfunction SpinnerGlyph() {\n\treturn (\n\t\t<>\n\t\t\t<style>{`@keyframes yns-attach-spin { to { transform: rotate(360deg); } }`}</style>\n\t\t\t<span\n\t\t\t\taria-hidden\n\t\t\t\tstyle={{\n\t\t\t\t\tdisplay: \"inline-block\",\n\t\t\t\t\twidth: 12,\n\t\t\t\t\theight: 12,\n\t\t\t\t\tborder: \"2px solid rgba(55, 65, 81, 0.25)\",\n\t\t\t\t\tborderTopColor: \"#374151\",\n\t\t\t\t\tborderRadius: 999,\n\t\t\t\t\tanimation: \"yns-attach-spin 0.9s linear infinite\",\n\t\t\t\t}}\n\t\t\t/>\n\t\t</>\n\t);\n}\n\nconst NARROW_BREAKPOINT_PX = 480;\n\nfunction useIsNarrow(): boolean {\n\tconst [narrow, setNarrow] = useState(false);\n\tuseEffect(() => {\n\t\tconst mq = window.matchMedia(`(max-width: ${NARROW_BREAKPOINT_PX}px)`);\n\t\tconst update = () => setNarrow(mq.matches);\n\t\tupdate();\n\t\tmq.addEventListener(\"change\", update);\n\t\treturn () => mq.removeEventListener(\"change\", update);\n\t}, []);\n\treturn narrow;\n}\n\nconst getInitials = (user: User): string => {\n\tconst name = user.name?.trim();\n\tif (name) {\n\t\tconst parts = name.split(/\\s+/).filter(Boolean).slice(0, 2);\n\t\treturn parts.map((p) => p[0]?.toUpperCase() ?? \"\").join(\"\");\n\t}\n\treturn user.email[0]?.toUpperCase() ?? \"?\";\n};\n\nfunction Avatar({ user, size = 22 }: { user: User; size?: number }) {\n\tif (user.image) {\n\t\treturn (\n\t\t\t<img\n\t\t\t\tsrc={user.image}\n\t\t\t\talt=\"\"\n\t\t\t\twidth={size}\n\t\t\t\theight={size}\n\t\t\t\tstyle={{\n\t\t\t\t\twidth: size,\n\t\t\t\t\theight: size,\n\t\t\t\t\tborderRadius: 999,\n\t\t\t\t\tobjectFit: \"cover\",\n\t\t\t\t\tdisplay: \"block\",\n\t\t\t\t\tflexShrink: 0,\n\t\t\t\t}}\n\t\t\t/>\n\t\t);\n\t}\n\treturn (\n\t\t<span\n\t\t\taria-hidden\n\t\t\tstyle={{\n\t\t\t\twidth: size,\n\t\t\t\theight: size,\n\t\t\t\tborderRadius: 999,\n\t\t\t\tbackground: \"rgba(255,255,255,0.18)\",\n\t\t\t\tcolor: \"white\",\n\t\t\t\tdisplay: \"inline-flex\",\n\t\t\t\talignItems: \"center\",\n\t\t\t\tjustifyContent: \"center\",\n\t\t\t\tfontSize: Math.max(10, Math.round(size * 0.45)),\n\t\t\t\tfontWeight: 600,\n\t\t\t\tflexShrink: 0,\n\t\t\t}}\n\t\t>\n\t\t\t{getInitials(user)}\n\t\t</span>\n\t);\n}\n\nfunction AnonymousPill({ loginUrl }: { loginUrl: string }) {\n\treturn (\n\t\t<div data-yns-feedback-ui=\"true\">\n\t\t\t<div style={toolbarStyle}>\n\t\t\t\t<span style={toolbarHintStyle}>Log in to provide feedback</span>\n\t\t\t\t<button type=\"button\" onClick={() => window.location.assign(loginUrl)} style={toolbarButtonStyle}>\n\t\t\t\t\tLog in\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nfunction NonMemberPill({ user, onSignOut }: { user: User; onSignOut: () => void }) {\n\tconst narrow = useIsNarrow();\n\treturn (\n\t\t<div data-yns-feedback-ui=\"true\">\n\t\t\t<div style={toolbarStyle}>\n\t\t\t\t<Avatar user={user} />\n\t\t\t\t{!narrow && <span style={identityTextStyle}>{user.email}</span>}\n\t\t\t\t<span style={toolbarHintStyle}>You don't have access to this store</span>\n\t\t\t\t<button type=\"button\" onClick={onSignOut} style={toolbarButtonStyle}>\n\t\t\t\t\tSign out\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nfunction IdentityChip({ user, onSignOut }: { user: User; onSignOut: () => void }) {\n\tconst [open, setOpen] = useState(false);\n\tconst wrapRef = useRef<HTMLDivElement | null>(null);\n\tconst narrow = useIsNarrow();\n\n\tuseEffect(() => {\n\t\tif (!open) return;\n\t\tconst onDown = (e: MouseEvent) => {\n\t\t\tif (!wrapRef.current) return;\n\t\t\tif (e.target instanceof Node && wrapRef.current.contains(e.target)) return;\n\t\t\tsetOpen(false);\n\t\t};\n\t\tdocument.addEventListener(\"mousedown\", onDown);\n\t\treturn () => document.removeEventListener(\"mousedown\", onDown);\n\t}, [open]);\n\n\tconst displayName = user.name?.trim() || user.email;\n\tconst truncated = displayName.length > 16 ? `${displayName.slice(0, 16)}…` : displayName;\n\n\treturn (\n\t\t<div ref={wrapRef} style={{ position: \"relative\" }}>\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={() => setOpen((v) => !v)}\n\t\t\t\tstyle={identityChipStyle}\n\t\t\t\taria-haspopup=\"menu\"\n\t\t\t\taria-expanded={open}\n\t\t\t\ttitle={displayName}\n\t\t\t>\n\t\t\t\t<Avatar user={user} size={20} />\n\t\t\t\t{!narrow && <span style={identityChipNameStyle}>{truncated}</span>}\n\t\t\t\t<span aria-hidden style={identityChipCaretStyle}>\n\t\t\t\t\t▾\n\t\t\t\t</span>\n\t\t\t</button>\n\t\t\t{open && (\n\t\t\t\t<div role=\"menu\" style={identityMenuStyle}>\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\trole=\"menuitem\"\n\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\tsetOpen(false);\n\t\t\t\t\t\t\tonSignOut();\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tstyle={identityMenuItemStyle}\n\t\t\t\t\t>\n\t\t\t\t\t\tSign out\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t)}\n\t\t</div>\n\t);\n}\n\nconst identityTextStyle: CSSProperties = {\n\tfontSize: 13,\n\tcolor: \"white\",\n\twhiteSpace: \"nowrap\",\n\toverflow: \"hidden\",\n\ttextOverflow: \"ellipsis\",\n\tmaxWidth: 200,\n};\n\nconst identityChipStyle: CSSProperties = {\n\tdisplay: \"inline-flex\",\n\talignItems: \"center\",\n\tgap: 6,\n\tbackground: \"rgba(255,255,255,0.08)\",\n\tborder: \"1px solid rgba(255,255,255,0.16)\",\n\tcolor: \"white\",\n\tpadding: \"4px 8px 4px 4px\",\n\tborderRadius: 999,\n\tcursor: \"pointer\",\n\tfontSize: 12,\n\tfontWeight: 500,\n\tfontFamily: \"inherit\",\n};\n\nconst identityChipNameStyle: CSSProperties = {\n\tmaxWidth: \"16ch\",\n\toverflow: \"hidden\",\n\ttextOverflow: \"ellipsis\",\n\twhiteSpace: \"nowrap\",\n};\n\nconst identityChipCaretStyle: CSSProperties = {\n\topacity: 0.7,\n\tfontSize: 10,\n};\n\nconst identityMenuStyle: CSSProperties = {\n\tposition: \"absolute\",\n\tright: 0,\n\tbottom: \"calc(100% + 8px)\",\n\tbackground: \"white\",\n\tcolor: \"#111\",\n\tborder: \"1px solid #e5e7eb\",\n\tborderRadius: 8,\n\tboxShadow: \"0 12px 32px rgba(0,0,0,0.18)\",\n\tminWidth: 140,\n\tpadding: 4,\n\tzIndex: 2147483647,\n};\n\nconst identityMenuItemStyle: CSSProperties = {\n\tdisplay: \"block\",\n\twidth: \"100%\",\n\ttextAlign: \"left\",\n\tbackground: \"transparent\",\n\tborder: \"none\",\n\tpadding: \"6px 10px\",\n\tfontSize: 13,\n\tcolor: \"#111\",\n\tcursor: \"pointer\",\n\tborderRadius: 6,\n};\n\nconst sidebarStyle: CSSProperties = {\n\tposition: \"fixed\",\n\ttop: 0,\n\tright: 0,\n\tbottom: 0,\n\twidth: 360,\n\tmaxWidth: \"92vw\",\n\tbackground: \"white\",\n\tborderLeft: \"1px solid #e5e7eb\",\n\tboxShadow: \"-12px 0 32px rgba(0,0,0,0.12)\",\n\tzIndex: 2147483646,\n\tdisplay: \"flex\",\n\tflexDirection: \"column\",\n\tfontFamily:\n\t\t'-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\"',\n\tcolor: \"#111\",\n};\n\nconst sidebarHeaderStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\talignItems: \"center\",\n\tjustifyContent: \"space-between\",\n\tpadding: \"12px 16px\",\n\tborderBottom: \"1px solid #e5e7eb\",\n};\n\nconst sidebarScrollStyle: CSSProperties = {\n\tflex: 1,\n\toverflow: \"auto\",\n\tpadding: \"12px 12px 24px\",\n};\n\nconst sidebarEmptyStyle: CSSProperties = {\n\tcolor: \"#6b7280\",\n\tfontSize: 13,\n\tpadding: \"24px 8px\",\n\ttextAlign: \"center\",\n};\n\nconst sidebarPathStyle: CSSProperties = {\n\tfontSize: 11,\n\ttextTransform: \"uppercase\",\n\tletterSpacing: 0.5,\n\tcolor: \"#6b7280\",\n\tpadding: \"4px 4px 6px\",\n\twordBreak: \"break-all\",\n};\n\nconst sidebarItemStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\talignItems: \"flex-start\",\n\tgap: 8,\n\twidth: \"100%\",\n\ttextAlign: \"left\",\n\tbackground: \"white\",\n\tborder: \"1px solid #e5e7eb\",\n\tborderRadius: 6,\n\tpadding: \"8px 10px\",\n\tcursor: \"pointer\",\n\tfontSize: 13,\n\tmarginBottom: 6,\n\tcolor: \"#111\",\n};\n\nconst sidebarItemIndexStyle: CSSProperties = {\n\tflexShrink: 0,\n\twidth: 22,\n\theight: 22,\n\tborderRadius: 999,\n\tbackground: \"#10b981\",\n\tcolor: \"white\",\n\tfontWeight: 700,\n\tfontSize: 11,\n\tdisplay: \"inline-flex\",\n\talignItems: \"center\",\n\tjustifyContent: \"center\",\n};\n\nconst sidebarItemTextStyle: CSSProperties = {\n\tflex: 1,\n\twhiteSpace: \"pre-wrap\",\n\twordBreak: \"break-word\",\n};\n\nconst sidebarItemDoneStyle: CSSProperties = {\n\tflexShrink: 0,\n\tbackground: \"#d1fae5\",\n\tcolor: \"#065f46\",\n\tfontSize: 11,\n\tfontWeight: 600,\n\tpadding: \"2px 6px\",\n\tborderRadius: 4,\n\talignSelf: \"center\",\n};\n\nconst submittedPanelStyle: CSSProperties = {\n\tposition: \"fixed\",\n\tbottom: 16,\n\tleft: \"50%\",\n\ttransform: \"translateX(-50%)\",\n\tzIndex: 2147483646,\n\tdisplay: \"flex\",\n\tflexDirection: \"column\",\n\tgap: 8,\n\tpadding: \"12px 16px\",\n\twidth: \"min(420px, calc(100vw - 32px))\",\n\tbackground: \"white\",\n\tborder: \"1px solid #e5e7eb\",\n\tborderRadius: 12,\n\tboxShadow: \"0 12px 32px rgba(0,0,0,0.18)\",\n\tfontFamily:\n\t\t'-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\"',\n\tcolor: \"#111\",\n\tpointerEvents: \"auto\",\n};\n\nconst submittedHeaderStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\talignItems: \"center\",\n\tgap: 8,\n};\n\nconst submittedBadgeStyle: CSSProperties = {\n\tbackground: \"#d1fae5\",\n\tcolor: \"#065f46\",\n\tfontSize: 11,\n\tfontWeight: 700,\n\tpadding: \"2px 8px\",\n\tborderRadius: 999,\n\ttextTransform: \"uppercase\",\n\tletterSpacing: 0.5,\n};\n\nconst submittedProgressLabelStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\tjustifyContent: \"space-between\",\n\tfontSize: 12,\n};\n\nconst submittedProgressTrackStyle: CSSProperties = {\n\twidth: \"100%\",\n\theight: 6,\n\tbackground: \"#f3f4f6\",\n\tborderRadius: 999,\n\toverflow: \"hidden\",\n};\n\nconst submittedProgressFillStyle: CSSProperties = {\n\theight: \"100%\",\n\tbackground: \"#10b981\",\n\tborderRadius: 999,\n\ttransition: \"width 500ms\",\n};\n\nconst submittedEtaStyle: CSSProperties = {\n\tmargin: 0,\n\tfontSize: 12,\n\tcolor: \"#6b7280\",\n};\n\nexport function mountFeedbackToolbar(): { unmount: () => void } | null {\n\tconsole.log(\"[YNS Feedback Toolbar] mountFeedbackToolbar() called\", {\n\t\tisWindow: typeof window !== \"undefined\",\n\t\tvercelEnv: process.env.NEXT_PUBLIC_VERCEL_ENV,\n\t\talreadyMounted: typeof document !== \"undefined\" && Boolean(document.getElementById(MOUNT_NODE_ID)),\n\t});\n\tif (typeof window === \"undefined\") return null;\n\tif (process.env.NEXT_PUBLIC_VERCEL_ENV !== \"preview\") {\n\t\tconsole.log(\n\t\t\t\"[YNS Feedback Toolbar] gate failed — NEXT_PUBLIC_VERCEL_ENV is\",\n\t\t\tprocess.env.NEXT_PUBLIC_VERCEL_ENV,\n\t\t);\n\t\treturn null;\n\t}\n\tif (document.getElementById(MOUNT_NODE_ID)) return null;\n\n\tconst container = document.createElement(\"div\");\n\tcontainer.id = MOUNT_NODE_ID;\n\tcontainer.dataset.ynsFeedbackUi = \"true\";\n\tdocument.body.appendChild(container);\n\n\tconst root = createRoot(container);\n\troot.render(<FeedbackToolbar />);\n\n\treturn {\n\t\tunmount: () => {\n\t\t\troot.unmount();\n\t\t\tcontainer.remove();\n\t\t},\n\t};\n}\n\n// Auto-mount on import. Wait for DOM ready.\nif (typeof window !== \"undefined\") {\n\tconsole.log(\"[YNS Feedback Toolbar] module evaluated\", {\n\t\tvercelEnv: process.env.NEXT_PUBLIC_VERCEL_ENV,\n\t\treadyState: document.readyState,\n\t\twillAutoMount: process.env.NEXT_PUBLIC_VERCEL_ENV === \"preview\",\n\t});\n}\nif (typeof window !== \"undefined\" && process.env.NEXT_PUBLIC_VERCEL_ENV === \"preview\") {\n\tif (document.readyState === \"loading\") {\n\t\tdocument.addEventListener(\"DOMContentLoaded\", () => {\n\t\t\tmountFeedbackToolbar();\n\t\t});\n\t} else {\n\t\tmountFeedbackToolbar();\n\t}\n}\n"],"mappings":"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","names":["useEffect","useRef","useState","createRoot","Fragment","jsx","jsxs","MOUNT_NODE_ID","buildApiBase","override","host","protocol","computeCssSelector","el","path","node","part","className","classes","c","parent","tag","siblings","idx","ATTRS_OF_INTEREST","formatAttrs","parts","attr","v","trimmed","formatTextContent","text","buildSurroundingHtml","target","ancestors","cur","lines","depth","ancestor","indent","targetIndent","targetTag","targetText","childIndent","children","child","childText","i","isInsideToolbar","IGNORED_TAGS","POLL_INTERVAL_MEMBER_MS","POLL_INTERVAL_LOBBY_MS","isFeedbackResponse","data","formatEta","etaIso","eta","remainingMs","dateLabel","remainingMin","remainingHours","remainingDays","signOutAndPoll","apiBase","pollNow","res","err","FeedbackToolbar","response","setResponse","loading","setLoading","pinMode","setPinMode","pending","setPending","editingId","setEditingId","sidebarOpen","setSidebarOpen","finalizing","setFinalizing","pollNowRef","cancelled","abortController","latestApplyId","pollTimeout","latestViewer","clearTimer","applyResponse","next","myId","scheduleNext","cadence","fetchOnce","controller","onVisibility","session","overlay","label","hovered","handleMove","e","rect","handleLeave","handleClick","event","offsetXRatio","offsetYRatio","refreshComments","submitNewComment","content","attachments","updateComment","id","removeComment","finalizeSession","nextStatus","prev","scrollToComment","comment","AnonymousPill","NonMemberPill","SubmittedPanel","visiblePins","pin","PinOverlay","PendingCommentPopover","CommentsSidebar","toolbarStyle","toolbarButtonActiveStyle","toolbarButtonStyle","toolbarButtonGhostStyle","toolbarButtonFinalizeStyle","toolbarHintStyle","IdentityChip","progress","status","setTick","t","submittedPanelStyle","spinnerKeyframes","submittedHeaderStyle","Spinner","submittedBadgeStyle","submittedProgressLabelStyle","submittedProgressTrackStyle","submittedProgressFillStyle","submittedEtaStyle","size","number","editing","feedbackSessionId","onStartEdit","onCancelEdit","onSave","onRemove","useTargetPosition","pinDotStyle","EditPopover","onCancel","comments","currentPath","onClose","onSelect","groups","list","paths","a","b","sidebarStyle","sidebarHeaderStyle","ghostButtonStyle","sidebarScrollStyle","sidebarEmptyStyle","sidebarPathStyle","sidebarItemStyle","sidebarItemIndexStyle","sidebarItemTextStyle","sidebarItemDoneStyle","MAX_ATTACHMENTS","MAX_ATTACHMENT_BYTES","readImageDimensions","file","resolve","url","img","initial","initialAttachments","value","setValue","setAttachments","saving","setSaving","uploading","setUploading","error","setError","textareaRef","fileInputRef","len","handleSubmit","handleFiles","files","slots","picked","f","dims","fd","body","removeAttachment","popoverStyle","textareaStyle","attachmentRowStyle","att","attachmentThumbWrapStyle","attachmentThumbStyle","attachmentRemoveStyle","errorTextStyle","popoverActionsStyle","dangerButtonStyle","attachIconButtonStyle","SpinnerGlyph","PaperclipGlyph","primaryButtonStyle","selector","pos","setPos","update","r","ro","baseButton","NARROW_BREAKPOINT_PX","useIsNarrow","narrow","setNarrow","mq","getInitials","user","name","p","Avatar","loginUrl","onSignOut","identityTextStyle","open","setOpen","wrapRef","onDown","displayName","truncated","identityChipStyle","identityChipNameStyle","identityChipCaretStyle","identityMenuStyle","identityMenuItemStyle","mountFeedbackToolbar","container","root"]}
|
|
1
|
+
{"version":3,"sources":["../src/feedback-toolbar.tsx"],"sourcesContent":["/**\n * Feedback session toolbar — side-effect entry.\n *\n * Importing `commerce-kit/feedback-toolbar` (or `commerce-kit/browser` for the\n * combined entry) mounts a floating toolbar onto the page that lets reviewers\n * leave click-anchored comments. Comments persist via YNS API endpoints,\n * authenticated with the `better-auth` session cookie sent through\n * `credentials: \"include\"`.\n *\n * Gated on `process.env.NEXT_PUBLIC_VERCEL_ENV === \"preview\"` — only Vercel\n * preview deploys get the toolbar. Sandbox dev (env undefined) and production\n * (env === \"production\") skip it.\n */\n\nimport { type CSSProperties, type FormEvent, useEffect, useRef, useState } from \"react\";\nimport { createRoot } from \"react-dom/client\";\n\nconst MOUNT_NODE_ID = \"yns-feedback-toolbar-root\";\n\ninterface FeedbackAttachment {\n\turl: string;\n\tcontentType: string;\n\twidth?: number;\n\theight?: number;\n}\n\ninterface CommentAuthor {\n\tid: string;\n\tname: string;\n\temail: string;\n\timage: string | null;\n}\n\ninterface FeedbackComment {\n\tid: string;\n\tpagePath: string;\n\tcssSelector: string;\n\tcontent: string;\n\tstatus: \"todo\" | \"done\";\n\toffsetXRatio: number;\n\toffsetYRatio: number;\n\tattachments: FeedbackAttachment[];\n\t// Optional: older yns-app builds (pre-authorship migration) don't return\n\t// these. `author === null` means legacy/anonymous; `canMutate === true`\n\t// means the current viewer is the author (or it's a legacy unauthored row).\n\tauthor?: CommentAuthor | null;\n\tcanMutate?: boolean;\n}\n\ninterface AuthorSummary extends CommentAuthor {\n\tcommentCount: number;\n}\n\ntype SessionStatus = \"created\" | \"in_progress\" | \"processing\" | \"in_review\" | \"done\";\n\ninterface ReviewProgress {\n\tfillPct: number;\n\tlabel: string;\n}\n\ninterface ActiveSession {\n\tfeedbackSessionId: string;\n\tcomments: FeedbackComment[];\n\tcanComment: boolean;\n\tsessionStatus: SessionStatus;\n\tcommentTotal: number;\n\tcommentDone: number;\n\teta: string;\n\tprogress: ReviewProgress;\n\t// Optional — added when yns-app exposes authorship. Tolerated as undefined\n\t// against older API versions so the toolbar keeps working mid-deploy.\n\tsessionCreator?: CommentAuthor | null;\n\tauthors?: AuthorSummary[];\n\tanonymousCount?: number;\n}\n\ninterface PendingPin {\n\tcssSelector: string;\n\tpagePath: string;\n\tsurroundingHtml: string;\n\trect: { top: number; left: number; width: number; height: number };\n\tclickX: number;\n\tclickY: number;\n\toffsetXRatio: number;\n\toffsetYRatio: number;\n}\n\ninterface User {\n\tid: string;\n\tname: string | null;\n\temail: string;\n\timage: string | null;\n}\n\ninterface AnonymousResponse {\n\tviewer: \"anonymous\";\n\tsession: { id: string; status: SessionStatus };\n\tloginUrl: string;\n}\n\ninterface NonMemberResponse {\n\tviewer: \"non-member\";\n\tsession: { id: string; status: SessionStatus };\n\tuser: User;\n\taccessDenied: true;\n}\n\ntype MemberResponse = ActiveSession & { viewer: \"member\"; user: User };\n\ntype FeedbackResponse = AnonymousResponse | NonMemberResponse | MemberResponse;\n\nconst buildApiBase = (): string | null => {\n\tif (typeof window === \"undefined\") return null;\n\tconst override = (process.env.NEXT_PUBLIC_YNS_API_BASE ?? \"\").trim();\n\tif (override) return override.replace(/\\/$/, \"\");\n\n\t// Toolbar runs on a per-store preview deploy (mystore-preview.yns.{store|cx})\n\t// served by the merchant's Vercel project — that host has no YNS API. Aim\n\t// at the apex `yns.store` / `yns.cx`, which yns-app serves and where the\n\t// API lives. Cookies scoped to `.yns.store` / `.yns.cx` travel along.\n\tconst host = window.location.hostname;\n\tconst protocol = window.location.protocol;\n\tif (host.endsWith(\".yns.store\")) return `${protocol}//yns.store`;\n\tif (host.endsWith(\".yns.cx\")) return `${protocol}//yns.cx`;\n\treturn window.location.origin;\n};\n\nconst computeCssSelector = (el: Element): string => {\n\tif (el.id) return `#${CSS.escape(el.id)}`;\n\tconst path: string[] = [];\n\tlet node: Element | null = el;\n\twhile (node && node.nodeType === Node.ELEMENT_NODE && path.length < 6) {\n\t\tlet part = node.tagName.toLowerCase();\n\t\tconst className = node.getAttribute(\"class\");\n\t\tif (className) {\n\t\t\tconst classes = className\n\t\t\t\t.split(/\\s+/)\n\t\t\t\t.filter(Boolean)\n\t\t\t\t.slice(0, 2)\n\t\t\t\t.map((c) => `.${CSS.escape(c)}`)\n\t\t\t\t.join(\"\");\n\t\t\tpart += classes;\n\t\t}\n\t\tconst parent: Element | null = node.parentElement;\n\t\tconst tag = node.tagName;\n\t\tif (parent) {\n\t\t\tconst siblings: Element[] = Array.from(parent.children).filter((c) => c.tagName === tag);\n\t\t\tif (siblings.length > 1) {\n\t\t\t\tconst idx = siblings.indexOf(node) + 1;\n\t\t\t\tpart += `:nth-of-type(${idx})`;\n\t\t\t}\n\t\t}\n\t\tpath.unshift(part);\n\t\tnode = parent;\n\t}\n\treturn path.join(\" > \");\n};\n\nconst ATTRS_OF_INTEREST = [\"id\", \"class\", \"data-testid\", \"aria-label\", \"role\", \"name\", \"href\", \"src\"];\n\nconst formatAttrs = (el: Element): string => {\n\tconst parts: string[] = [];\n\tfor (const attr of ATTRS_OF_INTEREST) {\n\t\tconst v = el.getAttribute(attr);\n\t\tif (!v) continue;\n\t\tconst trimmed = v.length > 80 ? `${v.slice(0, 80)}…` : v;\n\t\tparts.push(` ${attr}=\"${trimmed.replace(/\"/g, \""\")}\"`);\n\t}\n\treturn parts.join(\"\");\n};\n\nconst formatTextContent = (el: Element): string => {\n\tconst text = (el.textContent ?? \"\").replace(/\\s+/g, \" \").trim();\n\tif (!text) return \"\";\n\treturn text.length > 100 ? `${text.slice(0, 100)}…` : text;\n};\n\nconst buildSurroundingHtml = (target: Element): string => {\n\tconst ancestors: Element[] = [];\n\tlet cur: Element | null = target;\n\twhile (cur && cur !== document.documentElement && ancestors.length < 5) {\n\t\tconst parent: Element | null = cur.parentElement;\n\t\tif (!parent) break;\n\t\tancestors.unshift(parent);\n\t\tcur = parent;\n\t}\n\n\tconst lines: string[] = [];\n\tlet depth = 0;\n\tfor (const ancestor of ancestors) {\n\t\tconst indent = \" \".repeat(depth);\n\t\tlines.push(`${indent}<${ancestor.tagName.toLowerCase()}${formatAttrs(ancestor)}>`);\n\t\tdepth++;\n\t}\n\n\tconst targetIndent = \" \".repeat(depth);\n\tconst targetTag = target.tagName.toLowerCase();\n\tconst targetText = formatTextContent(target);\n\tlines.push(\n\t\t`${targetIndent}<${targetTag}${formatAttrs(target)}>${\n\t\t\ttargetText ? `${targetText}</${targetTag}>` : \"\"\n\t\t} ← TARGET`,\n\t);\n\n\tif (!targetText) {\n\t\tconst childIndent = \" \".repeat(depth + 1);\n\t\tconst children = Array.from(target.children).slice(0, 6);\n\t\tfor (const child of children) {\n\t\t\tconst childText = formatTextContent(child);\n\t\t\tlines.push(\n\t\t\t\t`${childIndent}<${child.tagName.toLowerCase()}${formatAttrs(child)}>${\n\t\t\t\t\tchildText ? `${childText}</${child.tagName.toLowerCase()}>` : \"\"\n\t\t\t\t}`,\n\t\t\t);\n\t\t}\n\t\tif (target.children.length > 6) {\n\t\t\tlines.push(`${childIndent}… (${target.children.length - 6} more children)`);\n\t\t}\n\t\tlines.push(`${targetIndent}</${targetTag}>`);\n\t}\n\n\tfor (let i = ancestors.length - 1; i >= 0; i--) {\n\t\tconst indent = \" \".repeat(i);\n\t\tconst ancestor = ancestors[i];\n\t\tif (!ancestor) continue;\n\t\tlines.push(`${indent}</${ancestor.tagName.toLowerCase()}>`);\n\t}\n\n\treturn lines.join(\"\\n\");\n};\n\nconst isInsideToolbar = (el: Element | null): boolean => {\n\tlet node = el;\n\twhile (node) {\n\t\tif (node instanceof HTMLElement && node.dataset.ynsFeedbackUi === \"true\") return true;\n\t\tnode = node.parentElement;\n\t}\n\treturn false;\n};\n\nconst IGNORED_TAGS = new Set([\"HTML\", \"HEAD\", \"SCRIPT\", \"STYLE\", \"NOSCRIPT\"]);\n\nconst POLL_INTERVAL_MEMBER_MS = 10_000;\nconst POLL_INTERVAL_LOBBY_MS = 30_000;\n\nconst isFeedbackResponse = (data: unknown): data is FeedbackResponse =>\n\ttypeof data === \"object\" &&\n\tdata !== null &&\n\t\"viewer\" in data &&\n\t(data.viewer === \"anonymous\" || data.viewer === \"non-member\" || data.viewer === \"member\");\n\n// Format a server-computed ETA as remaining + absolute label. The deadline\n// math itself lives in yns-app's `lib/feedback-comments-api.ts` so the toolbar\n// and YNS lock screen stay in sync.\nconst formatEta = (etaIso: string): string => {\n\tconst eta = new Date(etaIso);\n\tconst remainingMs = eta.getTime() - Date.now();\n\tconst dateLabel = eta.toLocaleString(undefined, {\n\t\tweekday: \"short\",\n\t\tmonth: \"short\",\n\t\tday: \"numeric\",\n\t\thour: \"numeric\",\n\t\tminute: \"2-digit\",\n\t});\n\n\tif (remainingMs <= 0) return `Any moment now (estimated by ${dateLabel})`;\n\tconst remainingMin = Math.ceil(remainingMs / 60_000);\n\tif (remainingMin < 60) return `~${remainingMin} minutes (by ${dateLabel})`;\n\tconst remainingHours = Math.round(remainingMs / 3_600_000);\n\tif (remainingHours < 24) {\n\t\treturn `~${remainingHours} ${remainingHours === 1 ? \"hour\" : \"hours\"} (by ${dateLabel})`;\n\t}\n\tconst remainingDays = Math.round(remainingMs / 86_400_000);\n\treturn `~${remainingDays} ${remainingDays === 1 ? \"day\" : \"days\"} (by ${dateLabel})`;\n};\n\n// Sign-out hits the YNS API origin — the same cross-origin host the poll\n// targets. Auth cookies live on the YNS host; the preview host has no auth\n// routes. `credentials: \"include\"` + CORS carries the YNS cookie both ways.\nasync function signOutAndPoll(apiBase: string, pollNow: () => void) {\n\ttry {\n\t\tconst res = await fetch(`${apiBase}/api/auth/sign-out`, {\n\t\t\tmethod: \"POST\",\n\t\t\tcredentials: \"include\",\n\t\t});\n\t\tif (!res.ok) {\n\t\t\tconsole.warn(\"[YNS Feedback Toolbar] sign-out responded non-OK\", res.status);\n\t\t}\n\t} catch (err) {\n\t\t// Network failures don't block the re-poll; the next tick reflects truth.\n\t\tconsole.warn(\"[YNS Feedback Toolbar] sign-out fetch failed\", err);\n\t}\n\tpollNow();\n}\n\nfunction FeedbackToolbar() {\n\tconst [response, setResponse] = useState<FeedbackResponse | null>(null);\n\tconst [loading, setLoading] = useState(true);\n\tconst [pinMode, setPinMode] = useState(false);\n\tconst [pending, setPending] = useState<PendingPin | null>(null);\n\tconst [editingId, setEditingId] = useState<string | null>(null);\n\tconst [sidebarOpen, setSidebarOpen] = useState(false);\n\tconst [finalizing, setFinalizing] = useState(false);\n\tconst apiBase = useRef<string | null>(null);\n\tconst pollNowRef = useRef<() => void>(() => {});\n\n\tuseEffect(() => {\n\t\tapiBase.current = buildApiBase();\n\t\tif (!apiBase.current) {\n\t\t\tsetLoading(false);\n\t\t\treturn;\n\t\t}\n\n\t\tlet cancelled = false;\n\t\t// Latest controller across in-flight fetch + the apply-id that gates which\n\t\t// response is allowed to commit. Both refs together fence the polling loop\n\t\t// against user actions (sign-out, finalize) and visibility transitions.\n\t\tlet abortController: AbortController | null = null;\n\t\tlet latestApplyId = 0;\n\t\tlet pollTimeout: number | null = null;\n\t\t// Tracks the latest applied viewer so cadence can be read synchronously\n\t\t// at scheduling time without waiting for setResponse to flush.\n\t\tlet latestViewer: FeedbackResponse[\"viewer\"] | null = null;\n\n\t\tconst clearTimer = () => {\n\t\t\tif (pollTimeout !== null) {\n\t\t\t\twindow.clearTimeout(pollTimeout);\n\t\t\t\tpollTimeout = null;\n\t\t\t}\n\t\t};\n\n\t\tconst applyResponse = (next: FeedbackResponse | null, myId: number) => {\n\t\t\tif (cancelled || myId < latestApplyId) return;\n\t\t\tlatestViewer = next?.viewer ?? null;\n\t\t\tsetResponse(next);\n\t\t\tsetLoading(false);\n\t\t};\n\n\t\tconst scheduleNext = () => {\n\t\t\tif (cancelled) return;\n\t\t\tconst cadence = latestViewer === \"member\" ? POLL_INTERVAL_MEMBER_MS : POLL_INTERVAL_LOBBY_MS;\n\t\t\tpollTimeout = window.setTimeout(() => {\n\t\t\t\tvoid fetchOnce();\n\t\t\t}, cadence);\n\t\t};\n\n\t\tconst fetchOnce = async () => {\n\t\t\tif (cancelled) return;\n\t\t\tabortController?.abort();\n\t\t\tconst controller = new AbortController();\n\t\t\tabortController = controller;\n\t\t\tconst myId = ++latestApplyId;\n\t\t\ttry {\n\t\t\t\tconst res = await fetch(\n\t\t\t\t\t`${apiBase.current}/api/feedback-comments?host=${encodeURIComponent(window.location.host)}`,\n\t\t\t\t\t{ credentials: \"include\", signal: controller.signal },\n\t\t\t\t);\n\t\t\t\tif (cancelled || myId < latestApplyId) return;\n\t\t\t\tif (res.status === 404 || !res.ok) {\n\t\t\t\t\tapplyResponse(null, myId);\n\t\t\t\t\tscheduleNext();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst data = (await res.json()) as unknown;\n\t\t\t\tif (cancelled || myId < latestApplyId) return;\n\t\t\t\tif (!isFeedbackResponse(data)) {\n\t\t\t\t\tapplyResponse(null, myId);\n\t\t\t\t\tscheduleNext();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tapplyResponse(data, myId);\n\t\t\t\tscheduleNext();\n\t\t\t} catch (err) {\n\t\t\t\t// AbortError fires whenever pollNow/visibilitychange cancels an\n\t\t\t\t// in-flight request — that's not a polling failure, just a normal\n\t\t\t\t// preemption. Don't clobber state or reschedule (the preempting\n\t\t\t\t// call is responsible for the next tick).\n\t\t\t\tif ((err as { name?: string } | null)?.name === \"AbortError\") return;\n\t\t\t\tif (cancelled) return;\n\t\t\t\tapplyResponse(null, myId);\n\t\t\t\tscheduleNext();\n\t\t\t}\n\t\t};\n\n\t\tconst pollNow = () => {\n\t\t\tif (cancelled) return;\n\t\t\tclearTimer();\n\t\t\tvoid fetchOnce();\n\t\t};\n\t\tpollNowRef.current = pollNow;\n\n\t\tconst onVisibility = () => {\n\t\t\tif (document.hidden) {\n\t\t\t\tabortController?.abort();\n\t\t\t\tclearTimer();\n\t\t\t} else {\n\t\t\t\tpollNow();\n\t\t\t}\n\t\t};\n\n\t\tdocument.addEventListener(\"visibilitychange\", onVisibility);\n\t\tvoid fetchOnce();\n\n\t\treturn () => {\n\t\t\tcancelled = true;\n\t\t\tdocument.removeEventListener(\"visibilitychange\", onVisibility);\n\t\t\tabortController?.abort();\n\t\t\tclearTimer();\n\t\t\tpollNowRef.current = () => {};\n\t\t};\n\t}, []);\n\n\t// Derived alias so the existing member-branch code (which reads `session.foo`)\n\t// keeps working without rewrites. Anonymous/non-member render paths return\n\t// before this is consulted.\n\tconst session: MemberResponse | null = response?.viewer === \"member\" ? response : null;\n\n\t// When the session stops accepting comments (finalized → in_review, or\n\t// closed), tear down any in-flight UI so the hover overlay/popover/sidebar\n\t// don't linger after the toolbar's main render returns null.\n\tuseEffect(() => {\n\t\tif (!session || session.canComment) return;\n\t\tsetPinMode(false);\n\t\tsetPending(null);\n\t\tsetSidebarOpen(false);\n\t\tsetEditingId(null);\n\t}, [session]);\n\n\tuseEffect(() => {\n\t\tif (!pinMode) return;\n\t\tdocument.body.style.cursor = \"crosshair\";\n\t\treturn () => {\n\t\t\tdocument.body.style.cursor = \"\";\n\t\t};\n\t}, [pinMode]);\n\n\tuseEffect(() => {\n\t\tif (!pinMode) return;\n\n\t\tconst overlay = document.createElement(\"div\");\n\t\toverlay.dataset.ynsFeedbackUi = \"true\";\n\t\toverlay.style.cssText = [\n\t\t\t\"position: fixed\",\n\t\t\t\"pointer-events: none\",\n\t\t\t\"z-index: 2147483644\",\n\t\t\t\"border: 2px dashed #10b981\",\n\t\t\t\"background: rgba(16, 185, 129, 0.08)\",\n\t\t\t\"border-radius: 3px\",\n\t\t\t\"display: none\",\n\t\t\t\"transition: top 0.05s, left 0.05s, width 0.05s, height 0.05s\",\n\t\t].join(\";\");\n\n\t\tconst label = document.createElement(\"div\");\n\t\tlabel.dataset.ynsFeedbackUi = \"true\";\n\t\tlabel.textContent = \"Click to comment\";\n\t\tlabel.style.cssText = [\n\t\t\t\"position: fixed\",\n\t\t\t\"pointer-events: none\",\n\t\t\t\"z-index: 2147483645\",\n\t\t\t\"background: #059669\",\n\t\t\t\"color: #fff\",\n\t\t\t\"font-size: 11px\",\n\t\t\t\"font-family: ui-sans-serif, system-ui, sans-serif\",\n\t\t\t\"padding: 3px 8px\",\n\t\t\t\"border-radius: 4px\",\n\t\t\t\"white-space: nowrap\",\n\t\t\t\"display: none\",\n\t\t\t\"box-shadow: 0 2px 6px rgba(0,0,0,0.15)\",\n\t\t].join(\";\");\n\n\t\tdocument.documentElement.appendChild(overlay);\n\t\tdocument.documentElement.appendChild(label);\n\n\t\tlet hovered: Element | null = null;\n\t\tconst handleMove = (e: MouseEvent) => {\n\t\t\tconst el = document.elementFromPoint(e.clientX, e.clientY);\n\t\t\tif (!el || IGNORED_TAGS.has(el.tagName) || isInsideToolbar(el)) {\n\t\t\t\toverlay.style.display = \"none\";\n\t\t\t\tlabel.style.display = \"none\";\n\t\t\t\thovered = null;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\thovered = el;\n\t\t\trequestAnimationFrame(() => {\n\t\t\t\tif (hovered !== el) return;\n\t\t\t\tconst rect = el.getBoundingClientRect();\n\t\t\t\toverlay.style.top = `${rect.top}px`;\n\t\t\t\toverlay.style.left = `${rect.left}px`;\n\t\t\t\toverlay.style.width = `${rect.width}px`;\n\t\t\t\toverlay.style.height = `${rect.height}px`;\n\t\t\t\toverlay.style.display = \"block\";\n\t\t\t\tlabel.style.left = `${e.clientX + 14}px`;\n\t\t\t\tlabel.style.top = `${e.clientY + 14}px`;\n\t\t\t\tlabel.style.display = \"block\";\n\t\t\t});\n\t\t};\n\n\t\tconst handleLeave = () => {\n\t\t\toverlay.style.display = \"none\";\n\t\t\tlabel.style.display = \"none\";\n\t\t\thovered = null;\n\t\t};\n\n\t\tdocument.addEventListener(\"mousemove\", handleMove, true);\n\t\tdocument.addEventListener(\"mouseleave\", handleLeave);\n\n\t\treturn () => {\n\t\t\tdocument.removeEventListener(\"mousemove\", handleMove, true);\n\t\t\tdocument.removeEventListener(\"mouseleave\", handleLeave);\n\t\t\toverlay.remove();\n\t\t\tlabel.remove();\n\t\t};\n\t}, [pinMode]);\n\n\tuseEffect(() => {\n\t\tif (!pinMode) return;\n\t\tconst handleClick = (event: MouseEvent) => {\n\t\t\tconst target = event.target;\n\t\t\tif (!(target instanceof Element)) return;\n\t\t\tif (isInsideToolbar(target)) return;\n\n\t\t\tevent.preventDefault();\n\t\t\tevent.stopPropagation();\n\n\t\t\tconst rect = target.getBoundingClientRect();\n\t\t\tconst offsetXRatio = rect.width > 0 ? (event.clientX - rect.left) / rect.width : 0.5;\n\t\t\tconst offsetYRatio = rect.height > 0 ? (event.clientY - rect.top) / rect.height : 0.5;\n\t\t\tsetPending({\n\t\t\t\tcssSelector: computeCssSelector(target),\n\t\t\t\tpagePath: window.location.pathname,\n\t\t\t\tsurroundingHtml: buildSurroundingHtml(target),\n\t\t\t\trect: {\n\t\t\t\t\ttop: rect.top + window.scrollY,\n\t\t\t\t\tleft: rect.left + window.scrollX,\n\t\t\t\t\twidth: rect.width,\n\t\t\t\t\theight: rect.height,\n\t\t\t\t},\n\t\t\t\tclickX: event.clientX + window.scrollX,\n\t\t\t\tclickY: event.clientY + window.scrollY,\n\t\t\t\toffsetXRatio: Math.min(1, Math.max(0, offsetXRatio)),\n\t\t\t\toffsetYRatio: Math.min(1, Math.max(0, offsetYRatio)),\n\t\t\t});\n\t\t\tsetPinMode(false);\n\t\t};\n\t\tdocument.addEventListener(\"click\", handleClick, { capture: true });\n\t\treturn () => document.removeEventListener(\"click\", handleClick, { capture: true });\n\t}, [pinMode]);\n\n\t// After a mutation (POST/PATCH/DELETE), route through the polling loop so the\n\t// discriminated-union handler validates the response and the apply-id guard\n\t// still fences against races. A non-member/anonymous shape coerced into\n\t// ActiveSession here would corrupt state otherwise.\n\tconst refreshComments = () => {\n\t\tpollNowRef.current();\n\t};\n\n\tconst submitNewComment = async (content: string, attachments: FeedbackAttachment[]) => {\n\t\tif (!apiBase.current || !session || !pending) return;\n\t\tconst res = await fetch(`${apiBase.current}/api/feedback-comments`, {\n\t\t\tmethod: \"POST\",\n\t\t\tcredentials: \"include\",\n\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\tbody: JSON.stringify({\n\t\t\t\tfeedbackSessionId: session.feedbackSessionId,\n\t\t\t\tcontent,\n\t\t\t\tpagePath: pending.pagePath,\n\t\t\t\tcssSelector: pending.cssSelector,\n\t\t\t\tsurroundingHtml: pending.surroundingHtml,\n\t\t\t\toffsetXRatio: pending.offsetXRatio,\n\t\t\t\toffsetYRatio: pending.offsetYRatio,\n\t\t\t\t...(attachments.length > 0 ? { attachments } : {}),\n\t\t\t}),\n\t\t});\n\t\tif (!res.ok) return;\n\t\tsetPending(null);\n\t\tsetPinMode(true);\n\t\trefreshComments();\n\t};\n\n\tconst updateComment = async (id: string, content: string, attachments: FeedbackAttachment[]) => {\n\t\tif (!apiBase.current) return;\n\t\tconst res = await fetch(`${apiBase.current}/api/feedback-comments/${id}`, {\n\t\t\tmethod: \"PATCH\",\n\t\t\tcredentials: \"include\",\n\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\tbody: JSON.stringify({ content, attachments }),\n\t\t});\n\t\tif (!res.ok) return;\n\t\tsetEditingId(null);\n\t\trefreshComments();\n\t};\n\n\tconst removeComment = async (id: string) => {\n\t\tif (!apiBase.current) return;\n\t\tconst res = await fetch(`${apiBase.current}/api/feedback-comments/${id}`, {\n\t\t\tmethod: \"DELETE\",\n\t\t\tcredentials: \"include\",\n\t\t});\n\t\tif (!res.ok) return;\n\t\trefreshComments();\n\t};\n\n\tconst finalizeSession = async () => {\n\t\tif (!apiBase.current || !session) return;\n\t\tif (!window.confirm(buildFinalizeConfirmMessage(session))) return;\n\t\tsetFinalizing(true);\n\t\ttry {\n\t\t\tconst res = await fetch(\n\t\t\t\t`${apiBase.current}/api/feedback-sessions/${session.feedbackSessionId}/finalize`,\n\t\t\t\t{ method: \"POST\", credentials: \"include\" },\n\t\t\t);\n\t\t\tif (!res.ok) return;\n\t\t\t// Flip BOTH canComment and sessionStatus locally. Without the status\n\t\t\t// flip, the render guard sees `canComment=false` + `status=in_progress`\n\t\t\t// (a state the SubmittedPanel branch returns null for) and the toolbar\n\t\t\t// disappears for the ~10s gap until the next poll catches up.\n\t\t\tconst body = (await res.json().catch(() => null)) as { status?: SessionStatus } | null;\n\t\t\tconst nextStatus: SessionStatus = body?.status ?? \"processing\";\n\t\t\tsetResponse((prev) =>\n\t\t\t\tprev?.viewer === \"member\" ? { ...prev, canComment: false, sessionStatus: nextStatus } : prev,\n\t\t\t);\n\t\t} finally {\n\t\t\tsetFinalizing(false);\n\t\t}\n\t};\n\n\tconst scrollToComment = (comment: FeedbackComment) => {\n\t\tif (comment.pagePath !== window.location.pathname) {\n\t\t\twindow.location.assign(comment.pagePath);\n\t\t\treturn;\n\t\t}\n\t\tlet el: Element | null = null;\n\t\ttry {\n\t\t\tel = document.querySelector(comment.cssSelector);\n\t\t} catch {\n\t\t\tel = null;\n\t\t}\n\t\tif (!el) return;\n\t\tel.scrollIntoView({ behavior: \"smooth\", block: \"center\" });\n\t\tsetEditingId(comment.id);\n\t};\n\n\tif (loading || !response) return null;\n\n\tif (response.viewer === \"anonymous\") {\n\t\treturn <AnonymousPill loginUrl={response.loginUrl} />;\n\t}\n\n\tif (response.viewer === \"non-member\") {\n\t\treturn (\n\t\t\t<NonMemberPill\n\t\t\t\tuser={response.user}\n\t\t\t\tonSignOut={() => {\n\t\t\t\t\tif (!apiBase.current) return;\n\t\t\t\t\tvoid signOutAndPoll(apiBase.current, () => pollNowRef.current());\n\t\t\t\t}}\n\t\t\t/>\n\t\t);\n\t}\n\n\t// response.viewer === \"member\" — `session` (derived above) mirrors `response`.\n\t// The redundant null guard satisfies TypeScript since the derivation can't\n\t// narrow across the early returns above.\n\tif (!session) return null;\n\n\t// Session has been finalized for AI review. Show a small status panel with\n\t// the same progress + ETA shown in YNS, but no commenting controls.\n\tif (!session.canComment) {\n\t\tif (session.sessionStatus !== \"processing\" && session.sessionStatus !== \"in_review\") return null;\n\t\treturn (\n\t\t\t<div data-yns-feedback-ui=\"true\">\n\t\t\t\t<SubmittedPanel progress={session.progress} eta={session.eta} status={session.sessionStatus} />\n\t\t\t</div>\n\t\t);\n\t}\n\n\tconst visiblePins = session.comments.filter(\n\t\t(c) => c.pagePath === window.location.pathname && c.status !== \"done\",\n\t);\n\n\treturn (\n\t\t<div data-yns-feedback-ui=\"true\">\n\t\t\t{visiblePins.map((pin, idx) => (\n\t\t\t\t<PinOverlay\n\t\t\t\t\tkey={pin.id}\n\t\t\t\t\tpin={pin}\n\t\t\t\t\tnumber={idx + 1}\n\t\t\t\t\tediting={editingId === pin.id}\n\t\t\t\t\tfeedbackSessionId={session.feedbackSessionId}\n\t\t\t\t\tapiBase={apiBase.current}\n\t\t\t\t\tonStartEdit={() => setEditingId(pin.id)}\n\t\t\t\t\tonCancelEdit={() => setEditingId(null)}\n\t\t\t\t\tonSave={(content, attachments) => updateComment(pin.id, content, attachments)}\n\t\t\t\t\tonRemove={() => removeComment(pin.id)}\n\t\t\t\t/>\n\t\t\t))}\n\n\t\t\t{pending && (\n\t\t\t\t<PendingCommentPopover\n\t\t\t\t\tpending={pending}\n\t\t\t\t\tfeedbackSessionId={session.feedbackSessionId}\n\t\t\t\t\tapiBase={apiBase.current}\n\t\t\t\t\tonCancel={() => {\n\t\t\t\t\t\tsetPending(null);\n\t\t\t\t\t\tsetPinMode(true);\n\t\t\t\t\t}}\n\t\t\t\t\tonSave={(content, attachments) => submitNewComment(content, attachments)}\n\t\t\t\t/>\n\t\t\t)}\n\n\t\t\t{sidebarOpen && (\n\t\t\t\t<CommentsSidebar\n\t\t\t\t\tcomments={session.comments}\n\t\t\t\t\tcurrentPath={window.location.pathname}\n\t\t\t\t\tonClose={() => setSidebarOpen(false)}\n\t\t\t\t\tonSelect={scrollToComment}\n\t\t\t\t/>\n\t\t\t)}\n\n\t\t\t<div style={toolbarStyle}>\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tonClick={() => setPinMode((v) => !v)}\n\t\t\t\t\tstyle={pinMode ? toolbarButtonActiveStyle : toolbarButtonStyle}\n\t\t\t\t>\n\t\t\t\t\t{pinMode ? \"Cancel\" : \"Add comment\"}\n\t\t\t\t</button>\n\t\t\t\t<button type=\"button\" onClick={() => setSidebarOpen((v) => !v)} style={toolbarButtonGhostStyle}>\n\t\t\t\t\t{sidebarOpen ? \"Hide list\" : `List (${session.comments.length})`}\n\t\t\t\t</button>\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tonClick={finalizeSession}\n\t\t\t\t\tdisabled={finalizing}\n\t\t\t\t\tstyle={toolbarButtonFinalizeStyle}\n\t\t\t\t>\n\t\t\t\t\t{finalizing ? \"Finalizing…\" : \"Finalize\"}\n\t\t\t\t</button>\n\t\t\t\t<span style={toolbarHintStyle}>\n\t\t\t\t\t{pinMode ? \"Click any element to comment\" : `${visiblePins.length} on this page`}\n\t\t\t\t</span>\n\t\t\t\t<ContributorsRow authors={session.authors} viewerId={session.user.id} />\n\t\t\t\t<IdentityChip\n\t\t\t\t\tuser={session.user}\n\t\t\t\t\tonSignOut={() => {\n\t\t\t\t\t\tif (!apiBase.current) return;\n\t\t\t\t\t\tvoid signOutAndPoll(apiBase.current, () => pollNowRef.current());\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nfunction SubmittedPanel({\n\tprogress,\n\teta: etaIso,\n\tstatus,\n}: {\n\tprogress: ReviewProgress;\n\teta: string;\n\tstatus: SessionStatus;\n}) {\n\t// Re-render every minute so the relative ETA label stays fresh between\n\t// the 10s data polls (which only re-render when the API payload changes).\n\tconst [, setTick] = useState(0);\n\tuseEffect(() => {\n\t\tconst id = window.setInterval(() => setTick((t) => t + 1), 60_000);\n\t\treturn () => window.clearInterval(id);\n\t}, []);\n\n\tconst eta = formatEta(etaIso);\n\tconst isInReview = status === \"in_review\";\n\tconst headline = isInReview ? \"Feedback under review\" : \"Applying feedback\";\n\n\treturn (\n\t\t<div style={submittedPanelStyle}>\n\t\t\t<style>{spinnerKeyframes}</style>\n\t\t\t<div style={submittedHeaderStyle}>\n\t\t\t\t<Spinner />\n\t\t\t\t<span style={submittedBadgeStyle}>Submitted</span>\n\t\t\t\t<strong style={{ fontSize: 13 }}>{headline}</strong>\n\t\t\t</div>\n\t\t\t<div style={submittedProgressLabelStyle}>\n\t\t\t\t<span>Review progress</span>\n\t\t\t\t<span style={{ opacity: 0.7 }}>{progress.label}</span>\n\t\t\t</div>\n\t\t\t<div style={submittedProgressTrackStyle}>\n\t\t\t\t<div style={{ ...submittedProgressFillStyle, width: `${progress.fillPct}%` }} />\n\t\t\t</div>\n\t\t\t<p style={submittedEtaStyle}>\n\t\t\t\tEstimated delivery: <span style={{ fontWeight: 600 }}>{eta}</span>\n\t\t\t</p>\n\t\t</div>\n\t);\n}\n\n// Inline keyframes — toolbar avoids relying on a global stylesheet.\nconst spinnerKeyframes = `@keyframes yns-feedback-spin { to { transform: rotate(360deg); } }`;\n\nfunction Spinner({ size = 14 }: { size?: number }) {\n\treturn (\n\t\t<span\n\t\t\taria-hidden\n\t\t\tstyle={{\n\t\t\t\tdisplay: \"inline-block\",\n\t\t\t\twidth: size,\n\t\t\t\theight: size,\n\t\t\t\tborder: \"2px solid rgba(16, 185, 129, 0.25)\",\n\t\t\t\tborderTopColor: \"#10b981\",\n\t\t\t\tborderRadius: 999,\n\t\t\t\tanimation: \"yns-feedback-spin 0.9s linear infinite\",\n\t\t\t}}\n\t\t/>\n\t);\n}\n\nfunction PinOverlay({\n\tpin,\n\tnumber,\n\tediting,\n\tfeedbackSessionId,\n\tapiBase,\n\tonStartEdit,\n\tonCancelEdit,\n\tonSave,\n\tonRemove,\n}: {\n\tpin: FeedbackComment;\n\tnumber: number;\n\tediting: boolean;\n\tfeedbackSessionId: string;\n\tapiBase: string | null;\n\tonStartEdit: () => void;\n\tonCancelEdit: () => void;\n\tonSave: (content: string, attachments: FeedbackAttachment[]) => Promise<void>;\n\tonRemove: () => Promise<void>;\n}) {\n\tconst target = useTargetPosition(pin.cssSelector, pin.offsetXRatio, pin.offsetYRatio);\n\tif (!target) return null;\n\n\t// `canMutate` is server-authoritative — falsy (or `undefined`, before the\n\t// yns-app authorship migration deploys) means show a read-only view instead\n\t// of the edit form. Server PATCH/DELETE re-checks ownership, so a stale\n\t// client can't bypass this.\n\tconst isAuthorMutable = pin.canMutate !== false;\n\n\treturn (\n\t\t<div\n\t\t\tstyle={{\n\t\t\t\tposition: \"absolute\",\n\t\t\t\ttop: target.top,\n\t\t\t\tleft: target.left,\n\t\t\t\tzIndex: 2147483600,\n\t\t\t\tpointerEvents: \"auto\",\n\t\t\t}}\n\t\t>\n\t\t\t<button type=\"button\" onClick={onStartEdit} style={pinDotStyle} title={pin.content}>\n\t\t\t\t{number}\n\t\t\t</button>\n\t\t\t{pin.author && (\n\t\t\t\t<span style={pinAuthorBadgeStyle} title={pin.author.name || pin.author.email}>\n\t\t\t\t\t<Avatar user={pin.author} size={16} />\n\t\t\t\t</span>\n\t\t\t)}\n\t\t\t{editing &&\n\t\t\t\t(isAuthorMutable ? (\n\t\t\t\t\t<EditPopover\n\t\t\t\t\t\tinitial={pin.content}\n\t\t\t\t\t\tinitialAttachments={pin.attachments}\n\t\t\t\t\t\tfeedbackSessionId={feedbackSessionId}\n\t\t\t\t\t\tapiBase={apiBase}\n\t\t\t\t\t\tonCancel={onCancelEdit}\n\t\t\t\t\t\tonSave={onSave}\n\t\t\t\t\t\tonRemove={onRemove}\n\t\t\t\t\t/>\n\t\t\t\t) : (\n\t\t\t\t\t<ReadOnlyCommentPopover comment={pin} onClose={onCancelEdit} />\n\t\t\t\t))}\n\t\t</div>\n\t);\n}\n\nfunction ReadOnlyCommentPopover({ comment, onClose }: { comment: FeedbackComment; onClose: () => void }) {\n\treturn (\n\t\t<div style={popoverStyle}>\n\t\t\t{comment.author && (\n\t\t\t\t<div style={readOnlyAuthorRowStyle}>\n\t\t\t\t\t<Avatar user={comment.author} size={20} />\n\t\t\t\t\t<span style={readOnlyAuthorNameStyle}>{comment.author.name || comment.author.email}</span>\n\t\t\t\t</div>\n\t\t\t)}\n\t\t\t<div style={readOnlyContentStyle}>{comment.content}</div>\n\t\t\t{comment.attachments.length > 0 && (\n\t\t\t\t<div style={attachmentRowStyle}>\n\t\t\t\t\t{comment.attachments.map((att) => (\n\t\t\t\t\t\t<a key={att.url} href={att.url} target=\"_blank\" rel=\"noreferrer\" style={attachmentThumbWrapStyle}>\n\t\t\t\t\t\t\t<img src={att.url} alt=\"\" style={attachmentThumbStyle} />\n\t\t\t\t\t\t</a>\n\t\t\t\t\t))}\n\t\t\t\t</div>\n\t\t\t)}\n\t\t\t<div style={popoverActionsStyle}>\n\t\t\t\t<span style={{ flex: 1, fontSize: 12, color: \"#6b7280\" }}>Only the author can edit</span>\n\t\t\t\t<button type=\"button\" onClick={onClose} style={ghostButtonStyle}>\n\t\t\t\t\tClose\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nfunction PendingCommentPopover({\n\tpending,\n\tfeedbackSessionId,\n\tapiBase,\n\tonCancel,\n\tonSave,\n}: {\n\tpending: PendingPin;\n\tfeedbackSessionId: string;\n\tapiBase: string | null;\n\tonCancel: () => void;\n\tonSave: (content: string, attachments: FeedbackAttachment[]) => Promise<void>;\n}) {\n\treturn (\n\t\t<>\n\t\t\t<div\n\t\t\t\tstyle={{\n\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\ttop: pending.rect.top + pending.rect.height * pending.offsetYRatio - 12,\n\t\t\t\t\tleft: pending.rect.left + pending.rect.width * pending.offsetXRatio - 12,\n\t\t\t\t\tzIndex: 2147483600,\n\t\t\t\t\tpointerEvents: \"none\",\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t<div style={pinDotStyle}>•</div>\n\t\t\t</div>\n\t\t\t<div\n\t\t\t\tstyle={{\n\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\ttop: pending.clickY + 10,\n\t\t\t\t\tleft: pending.clickX + 10,\n\t\t\t\t\tzIndex: 2147483647,\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t<EditPopover\n\t\t\t\t\tinitial=\"\"\n\t\t\t\t\tinitialAttachments={[]}\n\t\t\t\t\tfeedbackSessionId={feedbackSessionId}\n\t\t\t\t\tapiBase={apiBase}\n\t\t\t\t\tonCancel={onCancel}\n\t\t\t\t\tonSave={onSave}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</>\n\t);\n}\n\nfunction CommentsSidebar({\n\tcomments,\n\tcurrentPath,\n\tonClose,\n\tonSelect,\n}: {\n\tcomments: FeedbackComment[];\n\tcurrentPath: string;\n\tonClose: () => void;\n\tonSelect: (comment: FeedbackComment) => void;\n}) {\n\tconst groups = new Map<string, FeedbackComment[]>();\n\tfor (const c of comments) {\n\t\tconst list = groups.get(c.pagePath) ?? [];\n\t\tlist.push(c);\n\t\tgroups.set(c.pagePath, list);\n\t}\n\tconst paths = Array.from(groups.keys()).sort((a, b) => {\n\t\tif (a === currentPath) return -1;\n\t\tif (b === currentPath) return 1;\n\t\treturn a.localeCompare(b);\n\t});\n\n\treturn (\n\t\t<div style={sidebarStyle}>\n\t\t\t<div style={sidebarHeaderStyle}>\n\t\t\t\t<strong style={{ fontSize: 14 }}>Comments ({comments.length})</strong>\n\t\t\t\t<button type=\"button\" onClick={onClose} style={ghostButtonStyle}>\n\t\t\t\t\tClose\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t\t<div style={sidebarScrollStyle}>\n\t\t\t\t{comments.length === 0 ? (\n\t\t\t\t\t<div style={sidebarEmptyStyle}>No comments yet. Click “Add comment” to leave one.</div>\n\t\t\t\t) : (\n\t\t\t\t\tpaths.map((path) => (\n\t\t\t\t\t\t<div key={path} style={{ marginBottom: 12 }}>\n\t\t\t\t\t\t\t<div style={sidebarPathStyle}>{path === currentPath ? `${path} · current` : path}</div>\n\t\t\t\t\t\t\t{(groups.get(path) ?? []).map((c, idx) => (\n\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\tkey={c.id}\n\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\tonClick={() => onSelect(c)}\n\t\t\t\t\t\t\t\t\tstyle={sidebarItemStyle}\n\t\t\t\t\t\t\t\t\tdisabled={c.status === \"done\" && false}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<span style={sidebarItemIndexStyle}>{idx + 1}</span>\n\t\t\t\t\t\t\t\t\t<span style={sidebarItemTextStyle}>\n\t\t\t\t\t\t\t\t\t\t{c.author && (\n\t\t\t\t\t\t\t\t\t\t\t<span style={sidebarItemAuthorStyle}>{c.author.name || c.author.email}</span>\n\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t{c.content.length > 140 ? `${c.content.slice(0, 140)}…` : c.content}\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t{c.status === \"done\" && <span style={sidebarItemDoneStyle}>done</span>}\n\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t))\n\t\t\t\t)}\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nconst MAX_ATTACHMENTS = 5;\nconst MAX_ATTACHMENT_BYTES = 5 * 1024 * 1024;\n\nconst readImageDimensions = (file: File): Promise<{ width: number; height: number } | null> =>\n\tnew Promise((resolve) => {\n\t\tconst url = URL.createObjectURL(file);\n\t\tconst img = new window.Image();\n\t\timg.onload = () => {\n\t\t\tURL.revokeObjectURL(url);\n\t\t\tresolve({ width: img.naturalWidth, height: img.naturalHeight });\n\t\t};\n\t\timg.onerror = () => {\n\t\t\tURL.revokeObjectURL(url);\n\t\t\tresolve(null);\n\t\t};\n\t\timg.src = url;\n\t});\n\nfunction EditPopover({\n\tinitial,\n\tinitialAttachments,\n\tfeedbackSessionId,\n\tapiBase,\n\tonCancel,\n\tonSave,\n\tonRemove,\n}: {\n\tinitial: string;\n\tinitialAttachments: FeedbackAttachment[];\n\tfeedbackSessionId: string;\n\tapiBase: string | null;\n\tonCancel: () => void;\n\tonSave: (content: string, attachments: FeedbackAttachment[]) => Promise<void>;\n\tonRemove?: () => Promise<void>;\n}) {\n\tconst [value, setValue] = useState(initial);\n\tconst [attachments, setAttachments] = useState<FeedbackAttachment[]>(initialAttachments);\n\tconst [saving, setSaving] = useState(false);\n\tconst [uploading, setUploading] = useState(false);\n\tconst [error, setError] = useState<string | null>(null);\n\tconst textareaRef = useRef<HTMLTextAreaElement | null>(null);\n\tconst fileInputRef = useRef<HTMLInputElement | null>(null);\n\n\tuseEffect(() => {\n\t\tconst el = textareaRef.current;\n\t\tif (!el) return;\n\t\tel.focus();\n\t\tconst len = el.value.length;\n\t\tel.setSelectionRange(len, len);\n\t}, []);\n\n\tconst handleSubmit = async (e: FormEvent) => {\n\t\te.preventDefault();\n\t\tconst content = value.trim();\n\t\tif (!content) return;\n\t\tsetSaving(true);\n\t\ttry {\n\t\t\tawait onSave(content, attachments);\n\t\t} finally {\n\t\t\tsetSaving(false);\n\t\t}\n\t};\n\n\tconst handleFiles = async (files: FileList | null) => {\n\t\tif (!files || files.length === 0 || !apiBase) return;\n\t\tsetError(null);\n\t\tconst slots = MAX_ATTACHMENTS - attachments.length;\n\t\tif (slots <= 0) {\n\t\t\tsetError(`Max ${MAX_ATTACHMENTS} images per comment`);\n\t\t\treturn;\n\t\t}\n\t\tconst picked = Array.from(files).slice(0, slots);\n\t\tfor (const f of picked) {\n\t\t\tif (!f.type.startsWith(\"image/\")) {\n\t\t\t\tsetError(`\"${f.name}\" is not an image`);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (f.size > MAX_ATTACHMENT_BYTES) {\n\t\t\t\tsetError(`\"${f.name}\" exceeds 5 MB`);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tsetUploading(true);\n\t\ttry {\n\t\t\tconst dims = await Promise.all(picked.map(readImageDimensions));\n\t\t\tconst fd = new FormData();\n\t\t\tpicked.forEach((file, i) => {\n\t\t\t\tfd.append(\"file\", file);\n\t\t\t\tfd.append(\"width\", dims[i]?.width ? String(dims[i]?.width) : \"\");\n\t\t\t\tfd.append(\"height\", dims[i]?.height ? String(dims[i]?.height) : \"\");\n\t\t\t});\n\t\t\tconst res = await fetch(\n\t\t\t\t`${apiBase}/api/feedback-comments/uploads?feedbackSessionId=${encodeURIComponent(feedbackSessionId)}`,\n\t\t\t\t{ method: \"POST\", credentials: \"include\", body: fd },\n\t\t\t);\n\t\t\tif (!res.ok) {\n\t\t\t\tconst body = (await res.json().catch(() => null)) as { error?: string } | null;\n\t\t\t\tsetError(body?.error ?? \"Upload failed\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst body = (await res.json()) as { uploads: FeedbackAttachment[] };\n\t\t\tsetAttachments((prev) => [...prev, ...body.uploads]);\n\t\t} catch {\n\t\t\tsetError(\"Upload failed\");\n\t\t} finally {\n\t\t\tsetUploading(false);\n\t\t\tif (fileInputRef.current) fileInputRef.current.value = \"\";\n\t\t}\n\t};\n\n\tconst removeAttachment = (url: string) => {\n\t\tsetAttachments((prev) => prev.filter((a) => a.url !== url));\n\t};\n\n\treturn (\n\t\t<form onSubmit={handleSubmit} style={popoverStyle}>\n\t\t\t<textarea\n\t\t\t\tref={textareaRef}\n\t\t\t\tvalue={value}\n\t\t\t\tonChange={(e) => setValue(e.target.value)}\n\t\t\t\tplaceholder=\"Leave a comment…\"\n\t\t\t\tstyle={textareaStyle}\n\t\t\t\trows={3}\n\t\t\t/>\n\t\t\t{attachments.length > 0 && (\n\t\t\t\t<div style={attachmentRowStyle}>\n\t\t\t\t\t{attachments.map((att) => (\n\t\t\t\t\t\t<div key={att.url} style={attachmentThumbWrapStyle}>\n\t\t\t\t\t\t\t<img src={att.url} alt=\"\" style={attachmentThumbStyle} />\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\tonClick={() => removeAttachment(att.url)}\n\t\t\t\t\t\t\t\tstyle={attachmentRemoveStyle}\n\t\t\t\t\t\t\t\tdisabled={saving || uploading}\n\t\t\t\t\t\t\t\taria-label=\"Remove attachment\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t×\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t))}\n\t\t\t\t</div>\n\t\t\t)}\n\t\t\t{error && <div style={errorTextStyle}>{error}</div>}\n\t\t\t<input\n\t\t\t\tref={fileInputRef}\n\t\t\t\ttype=\"file\"\n\t\t\t\taccept=\"image/*\"\n\t\t\t\tmultiple\n\t\t\t\tonChange={(e) => void handleFiles(e.target.files)}\n\t\t\t\tstyle={{ display: \"none\" }}\n\t\t\t/>\n\t\t\t<div style={popoverActionsStyle}>\n\t\t\t\t{onRemove && (\n\t\t\t\t\t<button type=\"button\" onClick={() => onRemove()} style={dangerButtonStyle} disabled={saving}>\n\t\t\t\t\t\tDelete\n\t\t\t\t\t</button>\n\t\t\t\t)}\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tonClick={() => fileInputRef.current?.click()}\n\t\t\t\t\tstyle={attachIconButtonStyle}\n\t\t\t\t\tdisabled={saving || uploading || attachments.length >= MAX_ATTACHMENTS}\n\t\t\t\t\taria-label={uploading ? \"Uploading…\" : \"Attach image\"}\n\t\t\t\t\ttitle={uploading ? \"Uploading…\" : \"Attach image\"}\n\t\t\t\t>\n\t\t\t\t\t{uploading ? <SpinnerGlyph /> : <PaperclipGlyph />}\n\t\t\t\t</button>\n\t\t\t\t<div style={{ flex: 1 }} />\n\t\t\t\t<button type=\"button\" onClick={onCancel} style={ghostButtonStyle} disabled={saving}>\n\t\t\t\t\tCancel\n\t\t\t\t</button>\n\t\t\t\t<button type=\"submit\" style={primaryButtonStyle} disabled={saving || uploading || !value.trim()}>\n\t\t\t\t\t{saving ? \"Saving…\" : \"Save\"}\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t</form>\n\t);\n}\n\nfunction useTargetPosition(selector: string, offsetXRatio: number, offsetYRatio: number) {\n\tconst [pos, setPos] = useState<{ top: number; left: number } | null>(null);\n\n\tuseEffect(() => {\n\t\tconst update = () => {\n\t\t\tlet el: Element | null = null;\n\t\t\ttry {\n\t\t\t\tel = document.querySelector(selector);\n\t\t\t} catch {\n\t\t\t\tel = null;\n\t\t\t}\n\t\t\tif (!el) {\n\t\t\t\tsetPos(null);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst r = el.getBoundingClientRect();\n\t\t\tsetPos({\n\t\t\t\ttop: r.top + window.scrollY + r.height * offsetYRatio - 12,\n\t\t\t\tleft: r.left + window.scrollX + r.width * offsetXRatio - 12,\n\t\t\t});\n\t\t};\n\n\t\tupdate();\n\t\tconst ro = new ResizeObserver(update);\n\t\ttry {\n\t\t\tconst el = document.querySelector(selector);\n\t\t\tif (el) ro.observe(el);\n\t\t} catch {\n\t\t\t// swallow invalid selector\n\t\t}\n\t\twindow.addEventListener(\"scroll\", update, true);\n\t\twindow.addEventListener(\"resize\", update);\n\t\treturn () => {\n\t\t\tro.disconnect();\n\t\t\twindow.removeEventListener(\"scroll\", update, true);\n\t\t\twindow.removeEventListener(\"resize\", update);\n\t\t};\n\t}, [selector, offsetXRatio, offsetYRatio]);\n\n\treturn pos;\n}\n\n// Inline styles — toolbar is injected into arbitrary stores, so we avoid relying\n// on any CSS framework being present.\nconst toolbarStyle: CSSProperties = {\n\tposition: \"fixed\",\n\tbottom: 16,\n\tleft: \"50%\",\n\ttransform: \"translateX(-50%)\",\n\tzIndex: 2147483646,\n\tdisplay: \"flex\",\n\talignItems: \"center\",\n\tgap: 8,\n\tpadding: \"8px 12px\",\n\tbackground: \"rgba(17, 17, 17, 0.92)\",\n\tcolor: \"white\",\n\tborderRadius: 999,\n\tboxShadow: \"0 8px 24px rgba(0,0,0,0.25)\",\n\tfontFamily:\n\t\t'-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\"',\n\tfontSize: 14,\n\tpointerEvents: \"auto\",\n};\n\nconst toolbarButtonStyle: CSSProperties = {\n\tborder: \"none\",\n\tbackground: \"white\",\n\tcolor: \"#111\",\n\tpadding: \"6px 12px\",\n\tborderRadius: 999,\n\tcursor: \"pointer\",\n\tfontWeight: 600,\n\tfontSize: 13,\n};\n\nconst toolbarButtonActiveStyle: CSSProperties = {\n\t...toolbarButtonStyle,\n\tbackground: \"#ef4444\",\n\tcolor: \"white\",\n};\n\nconst toolbarButtonGhostStyle: CSSProperties = {\n\tborder: \"1px solid rgba(255,255,255,0.4)\",\n\tbackground: \"transparent\",\n\tcolor: \"white\",\n\tpadding: \"6px 12px\",\n\tborderRadius: 999,\n\tcursor: \"pointer\",\n\tfontWeight: 500,\n\tfontSize: 13,\n};\n\nconst toolbarButtonFinalizeStyle: CSSProperties = {\n\tborder: \"none\",\n\tbackground: \"#10b981\",\n\tcolor: \"white\",\n\tpadding: \"6px 12px\",\n\tborderRadius: 999,\n\tcursor: \"pointer\",\n\tfontWeight: 600,\n\tfontSize: 13,\n};\n\nconst toolbarHintStyle: CSSProperties = {\n\topacity: 0.8,\n\tfontSize: 12,\n};\n\nconst contributorsRowStyle: CSSProperties = {\n\tdisplay: \"inline-flex\",\n\talignItems: \"center\",\n\t// Overlap avatars like the Figma/Linear stacked presence indicator.\n\tpaddingLeft: 4,\n};\n\nconst contributorsAvatarWrapStyle: CSSProperties = {\n\tmarginLeft: -6,\n\tdisplay: \"inline-flex\",\n\tborderRadius: 999,\n\tboxShadow: \"0 0 0 2px rgba(17, 17, 17, 0.92)\",\n};\n\nconst contributorsOverflowStyle: CSSProperties = {\n\tmarginLeft: -2,\n\tpadding: \"0 6px\",\n\theight: 18,\n\tminWidth: 18,\n\tborderRadius: 999,\n\tbackground: \"rgba(255,255,255,0.18)\",\n\tcolor: \"white\",\n\tfontSize: 10,\n\tfontWeight: 600,\n\tdisplay: \"inline-flex\",\n\talignItems: \"center\",\n\tjustifyContent: \"center\",\n};\n\nconst pinDotStyle: CSSProperties = {\n\twidth: 24,\n\theight: 24,\n\tborderRadius: 999,\n\tbackground: \"#10b981\",\n\tcolor: \"white\",\n\tborder: \"2px solid white\",\n\tcursor: \"pointer\",\n\tfontSize: 12,\n\tfontWeight: 700,\n\tboxShadow: \"0 2px 6px rgba(0,0,0,0.3)\",\n\tdisplay: \"inline-flex\",\n\talignItems: \"center\",\n\tjustifyContent: \"center\",\n\tpadding: 0,\n};\n\n// Tiny avatar badge anchored to the pin dot so reviewers can tell at a glance\n// who left which comment. Positioned bottom-right of the dot like a status\n// badge — same pattern Figma uses for collaborative cursor pins.\nconst pinAuthorBadgeStyle: CSSProperties = {\n\tposition: \"absolute\",\n\tbottom: -6,\n\tright: -6,\n\twidth: 20,\n\theight: 20,\n\tborderRadius: 999,\n\tborder: \"2px solid white\",\n\tbackground: \"#111\",\n\tdisplay: \"inline-flex\",\n\talignItems: \"center\",\n\tjustifyContent: \"center\",\n\tboxShadow: \"0 1px 3px rgba(0,0,0,0.3)\",\n\tpointerEvents: \"none\",\n};\n\nconst readOnlyAuthorRowStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\talignItems: \"center\",\n\tgap: 8,\n};\n\nconst readOnlyAuthorNameStyle: CSSProperties = {\n\tfontSize: 13,\n\tfontWeight: 600,\n\tcolor: \"#111\",\n};\n\nconst readOnlyContentStyle: CSSProperties = {\n\tfontSize: 14,\n\tcolor: \"#111\",\n\twhiteSpace: \"pre-wrap\",\n\twordBreak: \"break-word\",\n};\n\nconst popoverStyle: CSSProperties = {\n\tbackground: \"white\",\n\tborder: \"1px solid #e5e7eb\",\n\tborderRadius: 8,\n\tpadding: 12,\n\twidth: 280,\n\tboxShadow: \"0 12px 32px rgba(0,0,0,0.18)\",\n\tfontFamily:\n\t\t'-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\"',\n\tdisplay: \"flex\",\n\tflexDirection: \"column\",\n\tgap: 8,\n\tcolor: \"#111\",\n};\n\nconst textareaStyle: CSSProperties = {\n\twidth: \"100%\",\n\tborder: \"1px solid #d1d5db\",\n\tborderRadius: 6,\n\tpadding: 8,\n\tfontSize: 14,\n\tresize: \"vertical\",\n\tfontFamily: \"inherit\",\n\tcolor: \"#111\",\n\tbackground: \"white\",\n\tboxSizing: \"border-box\",\n};\n\nconst popoverActionsStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\talignItems: \"center\",\n\tgap: 6,\n};\n\nconst baseButton: CSSProperties = {\n\tborder: \"1px solid transparent\",\n\tborderRadius: 6,\n\tpadding: \"6px 10px\",\n\tfontSize: 13,\n\tcursor: \"pointer\",\n\tfontWeight: 500,\n};\n\nconst primaryButtonStyle: CSSProperties = {\n\t...baseButton,\n\tbackground: \"#111\",\n\tcolor: \"white\",\n};\n\nconst ghostButtonStyle: CSSProperties = {\n\t...baseButton,\n\tbackground: \"transparent\",\n\tcolor: \"#374151\",\n\tborderColor: \"#d1d5db\",\n};\n\nconst dangerButtonStyle: CSSProperties = {\n\t...baseButton,\n\tbackground: \"transparent\",\n\tcolor: \"#b91c1c\",\n\tborderColor: \"#fecaca\",\n};\n\nconst attachmentRowStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\tflexWrap: \"wrap\",\n\tgap: 6,\n};\n\nconst attachmentThumbWrapStyle: CSSProperties = {\n\tposition: \"relative\",\n\twidth: 56,\n\theight: 56,\n\tborderRadius: 6,\n\toverflow: \"hidden\",\n\tborder: \"1px solid #e5e7eb\",\n\tbackground: \"#f9fafb\",\n};\n\nconst attachmentThumbStyle: CSSProperties = {\n\twidth: \"100%\",\n\theight: \"100%\",\n\tobjectFit: \"cover\",\n\tdisplay: \"block\",\n};\n\nconst attachmentRemoveStyle: CSSProperties = {\n\tposition: \"absolute\",\n\ttop: 2,\n\tright: 2,\n\twidth: 18,\n\theight: 18,\n\tborderRadius: 999,\n\tborder: \"none\",\n\tbackground: \"rgba(17, 17, 17, 0.85)\",\n\tcolor: \"white\",\n\tfontSize: 13,\n\tlineHeight: \"16px\",\n\tcursor: \"pointer\",\n\tpadding: 0,\n\tdisplay: \"inline-flex\",\n\talignItems: \"center\",\n\tjustifyContent: \"center\",\n};\n\nconst errorTextStyle: CSSProperties = {\n\tcolor: \"#b91c1c\",\n\tfontSize: 12,\n\tmargin: 0,\n};\n\nconst attachIconButtonStyle: CSSProperties = {\n\t...baseButton,\n\tbackground: \"transparent\",\n\tcolor: \"#374151\",\n\tborderColor: \"#d1d5db\",\n\twidth: 30,\n\theight: 30,\n\tpadding: 0,\n\tdisplay: \"inline-flex\",\n\talignItems: \"center\",\n\tjustifyContent: \"center\",\n\tflexShrink: 0,\n};\n\nfunction PaperclipGlyph() {\n\treturn (\n\t\t<svg\n\t\t\trole=\"img\"\n\t\t\taria-label=\"Attach image\"\n\t\t\twidth=\"14\"\n\t\t\theight=\"14\"\n\t\t\tviewBox=\"0 0 24 24\"\n\t\t\tfill=\"none\"\n\t\t\tstroke=\"currentColor\"\n\t\t\tstrokeWidth=\"2\"\n\t\t\tstrokeLinecap=\"round\"\n\t\t\tstrokeLinejoin=\"round\"\n\t\t>\n\t\t\t<title>Attach image</title>\n\t\t\t<path d=\"m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 17.93 8.83l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48\" />\n\t\t</svg>\n\t);\n}\n\nfunction SpinnerGlyph() {\n\treturn (\n\t\t<>\n\t\t\t<style>{`@keyframes yns-attach-spin { to { transform: rotate(360deg); } }`}</style>\n\t\t\t<span\n\t\t\t\taria-hidden\n\t\t\t\tstyle={{\n\t\t\t\t\tdisplay: \"inline-block\",\n\t\t\t\t\twidth: 12,\n\t\t\t\t\theight: 12,\n\t\t\t\t\tborder: \"2px solid rgba(55, 65, 81, 0.25)\",\n\t\t\t\t\tborderTopColor: \"#374151\",\n\t\t\t\t\tborderRadius: 999,\n\t\t\t\t\tanimation: \"yns-attach-spin 0.9s linear infinite\",\n\t\t\t\t}}\n\t\t\t/>\n\t\t</>\n\t);\n}\n\nconst NARROW_BREAKPOINT_PX = 480;\n\nfunction useIsNarrow(): boolean {\n\tconst [narrow, setNarrow] = useState(false);\n\tuseEffect(() => {\n\t\tconst mq = window.matchMedia(`(max-width: ${NARROW_BREAKPOINT_PX}px)`);\n\t\tconst update = () => setNarrow(mq.matches);\n\t\tupdate();\n\t\tmq.addEventListener(\"change\", update);\n\t\treturn () => mq.removeEventListener(\"change\", update);\n\t}, []);\n\treturn narrow;\n}\n\nconst getInitials = (user: User): string => {\n\tconst name = user.name?.trim();\n\tif (name) {\n\t\tconst parts = name.split(/\\s+/).filter(Boolean).slice(0, 2);\n\t\treturn parts.map((p) => p[0]?.toUpperCase() ?? \"\").join(\"\");\n\t}\n\treturn user.email[0]?.toUpperCase() ?? \"?\";\n};\n\nfunction Avatar({ user, size = 22 }: { user: User; size?: number }) {\n\tif (user.image) {\n\t\treturn (\n\t\t\t<img\n\t\t\t\tsrc={user.image}\n\t\t\t\talt=\"\"\n\t\t\t\twidth={size}\n\t\t\t\theight={size}\n\t\t\t\tstyle={{\n\t\t\t\t\twidth: size,\n\t\t\t\t\theight: size,\n\t\t\t\t\tborderRadius: 999,\n\t\t\t\t\tobjectFit: \"cover\",\n\t\t\t\t\tdisplay: \"block\",\n\t\t\t\t\tflexShrink: 0,\n\t\t\t\t}}\n\t\t\t/>\n\t\t);\n\t}\n\treturn (\n\t\t<span\n\t\t\taria-hidden\n\t\t\tstyle={{\n\t\t\t\twidth: size,\n\t\t\t\theight: size,\n\t\t\t\tborderRadius: 999,\n\t\t\t\tbackground: \"rgba(255,255,255,0.18)\",\n\t\t\t\tcolor: \"white\",\n\t\t\t\tdisplay: \"inline-flex\",\n\t\t\t\talignItems: \"center\",\n\t\t\t\tjustifyContent: \"center\",\n\t\t\t\tfontSize: Math.max(10, Math.round(size * 0.45)),\n\t\t\t\tfontWeight: 600,\n\t\t\t\tflexShrink: 0,\n\t\t\t}}\n\t\t>\n\t\t\t{getInitials(user)}\n\t\t</span>\n\t);\n}\n\n/**\n * Compose the confirm-dialog copy for the finalize action so reviewers see\n * exactly what they're submitting and *whose* comments they're submitting.\n * Falls back to the simple legacy message when the yns-app API hasn't yet\n * deployed the authorship fields (older preview deploys).\n */\nfunction buildFinalizeConfirmMessage(session: ActiveSession): string {\n\tconst authors = session.authors ?? [];\n\tconst anon = session.anonymousCount ?? 0;\n\tconst total = session.commentTotal;\n\n\tif (total === 0) {\n\t\treturn \"Finalize this feedback session with zero comments? It will be canceled instead of submitted.\";\n\t}\n\n\tif (authors.length === 0 && anon === 0) {\n\t\treturn `Finalize this feedback session with ${total} comment${\n\t\t\ttotal === 1 ? \"\" : \"s\"\n\t\t}? You won't be able to add more.`;\n\t}\n\n\tconst peopleCount = authors.length + (anon > 0 ? 1 : 0);\n\tconst names = authors.map((a) => a.name || a.email);\n\tif (anon > 0) names.push(`${anon} anonymous`);\n\tconst nameList = names.length === 1 ? names[0] : `${names.slice(0, -1).join(\", \")} and ${names.at(-1)}`;\n\n\treturn (\n\t\t`Submit ${total} comment${total === 1 ? \"\" : \"s\"} from ${peopleCount} ` +\n\t\t`reviewer${peopleCount === 1 ? \"\" : \"s\"} (${nameList})?\\n\\n` +\n\t\t`This sends ALL comments — including those from other reviewers — for AI processing. ` +\n\t\t`You won't be able to add more comments after finalizing.`\n\t);\n}\n\n/**\n * Compact avatar row showing every reviewer who's contributed at least one\n * comment in this session. Borrows the Figma/Linear pattern: presence is\n * *implicit* from authorship, not from real-time websockets. Avoids surprising\n * a reviewer who thought they were alone when they hit Finalize.\n */\nfunction ContributorsRow({ authors, viewerId }: { authors: AuthorSummary[] | undefined; viewerId: string }) {\n\tif (!authors || authors.length === 0) return null;\n\t// Demote the viewer themselves — they're already represented by the\n\t// IdentityChip on the right; doubling up adds visual noise. Keep them in\n\t// the count tooltip though, since their comments still count.\n\tconst others = authors.filter((a) => a.id !== viewerId);\n\tif (others.length === 0) return null;\n\n\tconst visible = others.slice(0, 3);\n\tconst overflow = others.length - visible.length;\n\n\treturn (\n\t\t<div\n\t\t\tstyle={contributorsRowStyle}\n\t\t\ttitle={`Also commenting: ${others.map((a) => `${a.name || a.email} (${a.commentCount})`).join(\", \")}`}\n\t\t>\n\t\t\t{visible.map((a) => (\n\t\t\t\t<span key={a.id} style={contributorsAvatarWrapStyle}>\n\t\t\t\t\t<Avatar user={a} size={18} />\n\t\t\t\t</span>\n\t\t\t))}\n\t\t\t{overflow > 0 && <span style={contributorsOverflowStyle}>+{overflow}</span>}\n\t\t</div>\n\t);\n}\n\nfunction AnonymousPill({ loginUrl }: { loginUrl: string }) {\n\treturn (\n\t\t<div data-yns-feedback-ui=\"true\">\n\t\t\t<div style={toolbarStyle}>\n\t\t\t\t<span style={toolbarHintStyle}>Log in to provide feedback</span>\n\t\t\t\t<button type=\"button\" onClick={() => window.location.assign(loginUrl)} style={toolbarButtonStyle}>\n\t\t\t\t\tLog in\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nfunction NonMemberPill({ user, onSignOut }: { user: User; onSignOut: () => void }) {\n\tconst narrow = useIsNarrow();\n\treturn (\n\t\t<div data-yns-feedback-ui=\"true\">\n\t\t\t<div style={toolbarStyle}>\n\t\t\t\t<Avatar user={user} />\n\t\t\t\t{!narrow && <span style={identityTextStyle}>{user.email}</span>}\n\t\t\t\t<span style={toolbarHintStyle}>You don't have access to this store</span>\n\t\t\t\t<button type=\"button\" onClick={onSignOut} style={toolbarButtonStyle}>\n\t\t\t\t\tSign out\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nfunction IdentityChip({ user, onSignOut }: { user: User; onSignOut: () => void }) {\n\tconst [open, setOpen] = useState(false);\n\tconst wrapRef = useRef<HTMLDivElement | null>(null);\n\tconst narrow = useIsNarrow();\n\n\tuseEffect(() => {\n\t\tif (!open) return;\n\t\tconst onDown = (e: MouseEvent) => {\n\t\t\tif (!wrapRef.current) return;\n\t\t\tif (e.target instanceof Node && wrapRef.current.contains(e.target)) return;\n\t\t\tsetOpen(false);\n\t\t};\n\t\tdocument.addEventListener(\"mousedown\", onDown);\n\t\treturn () => document.removeEventListener(\"mousedown\", onDown);\n\t}, [open]);\n\n\tconst displayName = user.name?.trim() || user.email;\n\tconst truncated = displayName.length > 16 ? `${displayName.slice(0, 16)}…` : displayName;\n\n\treturn (\n\t\t<div ref={wrapRef} style={{ position: \"relative\" }}>\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={() => setOpen((v) => !v)}\n\t\t\t\tstyle={identityChipStyle}\n\t\t\t\taria-haspopup=\"menu\"\n\t\t\t\taria-expanded={open}\n\t\t\t\ttitle={displayName}\n\t\t\t>\n\t\t\t\t<Avatar user={user} size={20} />\n\t\t\t\t{!narrow && <span style={identityChipNameStyle}>{truncated}</span>}\n\t\t\t\t<span aria-hidden style={identityChipCaretStyle}>\n\t\t\t\t\t▾\n\t\t\t\t</span>\n\t\t\t</button>\n\t\t\t{open && (\n\t\t\t\t<div role=\"menu\" style={identityMenuStyle}>\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\trole=\"menuitem\"\n\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\tsetOpen(false);\n\t\t\t\t\t\t\tonSignOut();\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tstyle={identityMenuItemStyle}\n\t\t\t\t\t>\n\t\t\t\t\t\tSign out\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t)}\n\t\t</div>\n\t);\n}\n\nconst identityTextStyle: CSSProperties = {\n\tfontSize: 13,\n\tcolor: \"white\",\n\twhiteSpace: \"nowrap\",\n\toverflow: \"hidden\",\n\ttextOverflow: \"ellipsis\",\n\tmaxWidth: 200,\n};\n\nconst identityChipStyle: CSSProperties = {\n\tdisplay: \"inline-flex\",\n\talignItems: \"center\",\n\tgap: 6,\n\tbackground: \"rgba(255,255,255,0.08)\",\n\tborder: \"1px solid rgba(255,255,255,0.16)\",\n\tcolor: \"white\",\n\tpadding: \"4px 8px 4px 4px\",\n\tborderRadius: 999,\n\tcursor: \"pointer\",\n\tfontSize: 12,\n\tfontWeight: 500,\n\tfontFamily: \"inherit\",\n};\n\nconst identityChipNameStyle: CSSProperties = {\n\tmaxWidth: \"16ch\",\n\toverflow: \"hidden\",\n\ttextOverflow: \"ellipsis\",\n\twhiteSpace: \"nowrap\",\n};\n\nconst identityChipCaretStyle: CSSProperties = {\n\topacity: 0.7,\n\tfontSize: 10,\n};\n\nconst identityMenuStyle: CSSProperties = {\n\tposition: \"absolute\",\n\tright: 0,\n\tbottom: \"calc(100% + 8px)\",\n\tbackground: \"white\",\n\tcolor: \"#111\",\n\tborder: \"1px solid #e5e7eb\",\n\tborderRadius: 8,\n\tboxShadow: \"0 12px 32px rgba(0,0,0,0.18)\",\n\tminWidth: 140,\n\tpadding: 4,\n\tzIndex: 2147483647,\n};\n\nconst identityMenuItemStyle: CSSProperties = {\n\tdisplay: \"block\",\n\twidth: \"100%\",\n\ttextAlign: \"left\",\n\tbackground: \"transparent\",\n\tborder: \"none\",\n\tpadding: \"6px 10px\",\n\tfontSize: 13,\n\tcolor: \"#111\",\n\tcursor: \"pointer\",\n\tborderRadius: 6,\n};\n\nconst sidebarStyle: CSSProperties = {\n\tposition: \"fixed\",\n\ttop: 0,\n\tright: 0,\n\tbottom: 0,\n\twidth: 360,\n\tmaxWidth: \"92vw\",\n\tbackground: \"white\",\n\tborderLeft: \"1px solid #e5e7eb\",\n\tboxShadow: \"-12px 0 32px rgba(0,0,0,0.12)\",\n\tzIndex: 2147483646,\n\tdisplay: \"flex\",\n\tflexDirection: \"column\",\n\tfontFamily:\n\t\t'-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\"',\n\tcolor: \"#111\",\n};\n\nconst sidebarHeaderStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\talignItems: \"center\",\n\tjustifyContent: \"space-between\",\n\tpadding: \"12px 16px\",\n\tborderBottom: \"1px solid #e5e7eb\",\n};\n\nconst sidebarScrollStyle: CSSProperties = {\n\tflex: 1,\n\toverflow: \"auto\",\n\tpadding: \"12px 12px 24px\",\n};\n\nconst sidebarEmptyStyle: CSSProperties = {\n\tcolor: \"#6b7280\",\n\tfontSize: 13,\n\tpadding: \"24px 8px\",\n\ttextAlign: \"center\",\n};\n\nconst sidebarPathStyle: CSSProperties = {\n\tfontSize: 11,\n\ttextTransform: \"uppercase\",\n\tletterSpacing: 0.5,\n\tcolor: \"#6b7280\",\n\tpadding: \"4px 4px 6px\",\n\twordBreak: \"break-all\",\n};\n\nconst sidebarItemStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\talignItems: \"flex-start\",\n\tgap: 8,\n\twidth: \"100%\",\n\ttextAlign: \"left\",\n\tbackground: \"white\",\n\tborder: \"1px solid #e5e7eb\",\n\tborderRadius: 6,\n\tpadding: \"8px 10px\",\n\tcursor: \"pointer\",\n\tfontSize: 13,\n\tmarginBottom: 6,\n\tcolor: \"#111\",\n};\n\nconst sidebarItemIndexStyle: CSSProperties = {\n\tflexShrink: 0,\n\twidth: 22,\n\theight: 22,\n\tborderRadius: 999,\n\tbackground: \"#10b981\",\n\tcolor: \"white\",\n\tfontWeight: 700,\n\tfontSize: 11,\n\tdisplay: \"inline-flex\",\n\talignItems: \"center\",\n\tjustifyContent: \"center\",\n};\n\nconst sidebarItemTextStyle: CSSProperties = {\n\tflex: 1,\n\twhiteSpace: \"pre-wrap\",\n\twordBreak: \"break-word\",\n};\n\nconst sidebarItemAuthorStyle: CSSProperties = {\n\tdisplay: \"block\",\n\tfontSize: 11,\n\tfontWeight: 600,\n\tcolor: \"#6b7280\",\n\tmarginBottom: 2,\n};\n\nconst sidebarItemDoneStyle: CSSProperties = {\n\tflexShrink: 0,\n\tbackground: \"#d1fae5\",\n\tcolor: \"#065f46\",\n\tfontSize: 11,\n\tfontWeight: 600,\n\tpadding: \"2px 6px\",\n\tborderRadius: 4,\n\talignSelf: \"center\",\n};\n\nconst submittedPanelStyle: CSSProperties = {\n\tposition: \"fixed\",\n\tbottom: 16,\n\tleft: \"50%\",\n\ttransform: \"translateX(-50%)\",\n\tzIndex: 2147483646,\n\tdisplay: \"flex\",\n\tflexDirection: \"column\",\n\tgap: 8,\n\tpadding: \"12px 16px\",\n\twidth: \"min(420px, calc(100vw - 32px))\",\n\tbackground: \"white\",\n\tborder: \"1px solid #e5e7eb\",\n\tborderRadius: 12,\n\tboxShadow: \"0 12px 32px rgba(0,0,0,0.18)\",\n\tfontFamily:\n\t\t'-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\"',\n\tcolor: \"#111\",\n\tpointerEvents: \"auto\",\n};\n\nconst submittedHeaderStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\talignItems: \"center\",\n\tgap: 8,\n};\n\nconst submittedBadgeStyle: CSSProperties = {\n\tbackground: \"#d1fae5\",\n\tcolor: \"#065f46\",\n\tfontSize: 11,\n\tfontWeight: 700,\n\tpadding: \"2px 8px\",\n\tborderRadius: 999,\n\ttextTransform: \"uppercase\",\n\tletterSpacing: 0.5,\n};\n\nconst submittedProgressLabelStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\tjustifyContent: \"space-between\",\n\tfontSize: 12,\n};\n\nconst submittedProgressTrackStyle: CSSProperties = {\n\twidth: \"100%\",\n\theight: 6,\n\tbackground: \"#f3f4f6\",\n\tborderRadius: 999,\n\toverflow: \"hidden\",\n};\n\nconst submittedProgressFillStyle: CSSProperties = {\n\theight: \"100%\",\n\tbackground: \"#10b981\",\n\tborderRadius: 999,\n\ttransition: \"width 500ms\",\n};\n\nconst submittedEtaStyle: CSSProperties = {\n\tmargin: 0,\n\tfontSize: 12,\n\tcolor: \"#6b7280\",\n};\n\nexport function mountFeedbackToolbar(): { unmount: () => void } | null {\n\tconsole.log(\"[YNS Feedback Toolbar] mountFeedbackToolbar() called\", {\n\t\tisWindow: typeof window !== \"undefined\",\n\t\tvercelEnv: process.env.NEXT_PUBLIC_VERCEL_ENV,\n\t\talreadyMounted: typeof document !== \"undefined\" && Boolean(document.getElementById(MOUNT_NODE_ID)),\n\t});\n\tif (typeof window === \"undefined\") return null;\n\tif (process.env.NEXT_PUBLIC_VERCEL_ENV !== \"preview\") {\n\t\tconsole.log(\n\t\t\t\"[YNS Feedback Toolbar] gate failed — NEXT_PUBLIC_VERCEL_ENV is\",\n\t\t\tprocess.env.NEXT_PUBLIC_VERCEL_ENV,\n\t\t);\n\t\treturn null;\n\t}\n\tif (document.getElementById(MOUNT_NODE_ID)) return null;\n\n\tconst container = document.createElement(\"div\");\n\tcontainer.id = MOUNT_NODE_ID;\n\tcontainer.dataset.ynsFeedbackUi = \"true\";\n\tdocument.body.appendChild(container);\n\n\tconst root = createRoot(container);\n\troot.render(<FeedbackToolbar />);\n\n\treturn {\n\t\tunmount: () => {\n\t\t\troot.unmount();\n\t\t\tcontainer.remove();\n\t\t},\n\t};\n}\n\n// Auto-mount on import. Wait for DOM ready.\nif (typeof window !== \"undefined\") {\n\tconsole.log(\"[YNS Feedback Toolbar] module evaluated\", {\n\t\tvercelEnv: process.env.NEXT_PUBLIC_VERCEL_ENV,\n\t\treadyState: document.readyState,\n\t\twillAutoMount: process.env.NEXT_PUBLIC_VERCEL_ENV === \"preview\",\n\t});\n}\nif (typeof window !== \"undefined\" && process.env.NEXT_PUBLIC_VERCEL_ENV === \"preview\") {\n\tif (document.readyState === \"loading\") {\n\t\tdocument.addEventListener(\"DOMContentLoaded\", () => {\n\t\t\tmountFeedbackToolbar();\n\t\t});\n\t} else {\n\t\tmountFeedbackToolbar();\n\t}\n}\n"],"mappings":"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","names":["useEffect","useRef","useState","createRoot","Fragment","jsx","jsxs","MOUNT_NODE_ID","buildApiBase","override","host","protocol","computeCssSelector","el","path","node","part","className","classes","c","parent","tag","siblings","idx","ATTRS_OF_INTEREST","formatAttrs","parts","attr","v","trimmed","formatTextContent","text","buildSurroundingHtml","target","ancestors","cur","lines","depth","ancestor","indent","targetIndent","targetTag","targetText","childIndent","children","child","childText","i","isInsideToolbar","IGNORED_TAGS","POLL_INTERVAL_MEMBER_MS","POLL_INTERVAL_LOBBY_MS","isFeedbackResponse","data","formatEta","etaIso","eta","remainingMs","dateLabel","remainingMin","remainingHours","remainingDays","signOutAndPoll","apiBase","pollNow","res","err","FeedbackToolbar","response","setResponse","loading","setLoading","pinMode","setPinMode","pending","setPending","editingId","setEditingId","sidebarOpen","setSidebarOpen","finalizing","setFinalizing","pollNowRef","cancelled","abortController","latestApplyId","pollTimeout","latestViewer","clearTimer","applyResponse","next","myId","scheduleNext","cadence","fetchOnce","controller","onVisibility","session","overlay","label","hovered","handleMove","e","rect","handleLeave","handleClick","event","offsetXRatio","offsetYRatio","refreshComments","submitNewComment","content","attachments","updateComment","id","removeComment","finalizeSession","buildFinalizeConfirmMessage","nextStatus","prev","scrollToComment","comment","AnonymousPill","NonMemberPill","SubmittedPanel","visiblePins","pin","PinOverlay","PendingCommentPopover","CommentsSidebar","toolbarStyle","toolbarButtonActiveStyle","toolbarButtonStyle","toolbarButtonGhostStyle","toolbarButtonFinalizeStyle","toolbarHintStyle","ContributorsRow","IdentityChip","progress","status","setTick","t","submittedPanelStyle","spinnerKeyframes","submittedHeaderStyle","Spinner","submittedBadgeStyle","submittedProgressLabelStyle","submittedProgressTrackStyle","submittedProgressFillStyle","submittedEtaStyle","size","number","editing","feedbackSessionId","onStartEdit","onCancelEdit","onSave","onRemove","useTargetPosition","isAuthorMutable","pinDotStyle","pinAuthorBadgeStyle","Avatar","EditPopover","ReadOnlyCommentPopover","onClose","popoverStyle","readOnlyAuthorRowStyle","readOnlyAuthorNameStyle","readOnlyContentStyle","attachmentRowStyle","att","attachmentThumbWrapStyle","attachmentThumbStyle","popoverActionsStyle","ghostButtonStyle","onCancel","comments","currentPath","onSelect","groups","list","paths","a","b","sidebarStyle","sidebarHeaderStyle","sidebarScrollStyle","sidebarEmptyStyle","sidebarPathStyle","sidebarItemStyle","sidebarItemIndexStyle","sidebarItemTextStyle","sidebarItemAuthorStyle","sidebarItemDoneStyle","MAX_ATTACHMENTS","MAX_ATTACHMENT_BYTES","readImageDimensions","file","resolve","url","img","initial","initialAttachments","value","setValue","setAttachments","saving","setSaving","uploading","setUploading","error","setError","textareaRef","fileInputRef","len","handleSubmit","handleFiles","files","slots","picked","f","dims","fd","body","removeAttachment","textareaStyle","attachmentRemoveStyle","errorTextStyle","dangerButtonStyle","attachIconButtonStyle","SpinnerGlyph","PaperclipGlyph","primaryButtonStyle","selector","pos","setPos","update","r","ro","contributorsRowStyle","contributorsAvatarWrapStyle","contributorsOverflowStyle","baseButton","NARROW_BREAKPOINT_PX","useIsNarrow","narrow","setNarrow","mq","getInitials","user","name","p","authors","anon","total","peopleCount","names","nameList","viewerId","others","visible","overflow","loginUrl","onSignOut","identityTextStyle","open","setOpen","wrapRef","onDown","displayName","truncated","identityChipStyle","identityChipNameStyle","identityChipCaretStyle","identityMenuStyle","identityMenuItemStyle","mountFeedbackToolbar","container","root"]}
|