@solhun/feedback-kit-web 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/react-tree.ts","../src/element-info.ts","../src/marker-store.ts","../src/picking.ts","../src/route-watcher.ts","../src/screenshot.ts","../src/widget.ts","../src/storage.ts","../src/feedback-kit.tsx","../src/version.ts","../src/providers.ts"],"sourcesContent":["// @solhun/feedback-kit-web\n//\n// 웹용 피드백 위젯 엔트리.\n//\n// 화면 지도·리포트 모달·핀 좌표 계산은 전부 코어에 있다. 여기 있는 건 웹에서만 뜻이 있는 것들 —\n// DOM 요소를 제보에 실을 형태로 옮기고(`element-info`), 지목 모드와 마커를 브라우저 저장소에\n// 남기고(`marker-store`), 페이지 클릭을 가로채 주석을 다는 것(`picking`) — 뿐이다.\n\nexport type {\n FeedbackReport,\n SubmitResult,\n FeedbackContext,\n FeedbackUser,\n FeedbackStorage,\n FeedbackAdapter,\n ElementInfo,\n FeedbackPin,\n} from \"@solhun/feedback-kit-core\";\n\n// 설정은 코어가 정의한다. 웹/앱이 각자 다른 설정 타입을 갖지 않는 게 중요해서\n// (같은 설정을 두 곳에 적으면 갈라진다) 여기서는 다시 내보내기만 한다.\nexport type {\n FeedbackConfig,\n ResolvedConfig,\n Visibility,\n VisibilityEnv,\n VisibilityFn,\n WidgetPosition,\n} from \"@solhun/feedback-kit-core\";\nexport { resolveConfig, shouldShowWidget } from \"@solhun/feedback-kit-core\";\n\n// 빌드 플러그인이 심은 소스 경로를 읽는 쪽. 플러그인이 없으면 조용히 null 이다.\nexport { parseSourceAttr, SOURCE_ATTR, sourceFromElement } from \"@solhun/feedback-kit-core\";\n\n// 코어의 헤드리스 위젯 계층. 웹 컴포넌트가 이 상태기계를 그대로 그린다.\nexport type {\n ModalSubmitStatus,\n ReportModalState,\n ScreenshotStatus,\n WidgetScreen,\n WidgetState,\n} from \"@solhun/feedback-kit-core\";\nexport {\n COMMENT_MAX_CHARS,\n COMMENT_REQUIRED_MESSAGE,\n COMMENT_TOO_LONG_MESSAGE,\n denormalizePin,\n FLOATING_BUTTON_ID,\n MODAL_ACTION_HINT_TOGGLE,\n MODAL_ACTION_PICK,\n normalizePin,\n ReportModalController,\n SCREENSHOT_FAILED_MESSAGE,\n SUBMIT_DONE_MESSAGE,\n SUBMIT_PENDING_MESSAGE,\n WidgetController,\n} from \"@solhun/feedback-kit-core\";\n\n// 힌트(자주 나오는 제보 제안). `WebWidgetOpts.hints`/`hintProvider` 를 타입 지정하거나\n// 테스트에서 직접 주입하려는 소비처를 위해 다시 내보낸다.\nexport type {\n ElementLike,\n FeedbackHint,\n HintCatalog,\n HintElementRule,\n HintPlatform,\n HintRankInput,\n HintScope,\n} from \"@solhun/feedback-kit-core\";\nexport { HintProvider, rankHints } from \"@solhun/feedback-kit-core\";\n\n// ── 웹 전용 ────────────────────────────────────────────────────────────────\n\nexport {\n cssSelectorPath,\n describeElement,\n ELEMENT_TEXT_MAX_CHARS,\n SELECTOR_MAX_DEPTH,\n visibleText,\n} from \"./element-info.js\";\n\nexport type { MarkerStatus, StorageLike, StoredMarker } from \"./marker-store.js\";\nexport {\n MARKER_KEY_PREFIX,\n MarkerStore,\n MAX_MARKERS_PER_PATH,\n PICKING_MODE_KEY,\n} from \"./marker-store.js\";\n\nexport type {\n AnnotationPopupState,\n ElementPickingOpts,\n PickingListener,\n PickingQueueLike,\n PickingState,\n} from \"./picking.js\";\nexport { ElementPickingController, OWN_UI_ATTR } from \"./picking.js\";\n\nexport { captureWebScreenshot, isOwnUi, reencodeWebScreenshot } from \"./screenshot.js\";\n\n// 지목한 요소의 React 컴포넌트 경로. 제보에서 코드를 찾는 시간을 가장 크게 줄인다.\nexport { reactComponentPath, reactComponentSummary } from \"./react-tree.js\";\n\nexport type { WebWidget, WebWidgetOpts } from \"./widget.js\";\nexport { createWebWidget } from \"./widget.js\";\n\nexport type { FeedbackKitProps } from \"./feedback-kit.js\";\nexport { FeedbackKit } from \"./feedback-kit.js\";\n\n// 웹 기본 저장소(localStorage). 앱마다 다시 쓰지 않게 패키지가 제공한다.\nexport { createWebStorage } from \"./storage.js\";\n\n// 웹 기본 컨텍스트 제공자(getUrl 등). 붙이는 쪽이 잊으면 제보가 버려진다.\nexport { webContextProviders, webDiagnosticsOptions } from \"./providers.js\";\nexport {\n createRouteWatcher,\n webRouteStorage,\n webRouteWatcher,\n type RouteWatchTarget,\n} from \"./route-watcher.js\";\n","// 지목한 요소가 **어느 React 컴포넌트에 속하는지** 알아낸다.\n//\n// 왜 필요한가: `div.flex.items-center` 같은 선택자만 오면 고치는 사람이 그걸\n// 코드에서 다시 찾아야 한다. `LoginForm > SubmitButton` 이 오면 바로 파일이 열린다.\n// 제보 하나당 이 한 줄이 탐색 시간을 가장 크게 줄인다.\n//\n// 방법: React 는 DOM 노드에 `__reactFiber$<랜덤>` 프로퍼티를 심는다(공개 API 가 아니다).\n// 거기서 fiber 를 잡아 부모로 거슬러 올라가며 컴포넌트 이름을 모은다.\n//\n// 한계를 미리 적어둔다:\n// - **프로덕션 빌드에서는 이름이 minify 되어 `t`·`Xe` 처럼 나올 수 있다.** 그래도 트리\n// 모양은 단서가 되므로 버리지 않는다. 이름을 살리려면 빌드에서 displayName 을 남긴다.\n// - React 19 도 같은 프로퍼티를 쓰지만 내부 구조라 언제든 바뀔 수 있다. 못 읽으면\n// **조용히 빈 배열**을 돌려준다 — 제보 자체를 막지 않는다.\n\n/**\n * 프레임워크가 만든 이름. 사람이 지은 게 아니라서 제보에 실어도 단서가 안 된다.\n *\n * 왜 필요한가 (2026-08-09 실측, cardnews 랜딩 = Next.js App Router):\n * 지목 제보의 컴포넌트 경로가 **전부 프레임워크 내부로만** 채워져 왔다 —\n * `HotReload > AppDevOverlayErrorBoundary > ... > InnerLayoutRouter > LayoutRouterContext`.\n * 페이지가 서버 컴포넌트면 클라이언트 fiber 에 그 이름이 안 남고, 위로는 전부 경계\n * 컴포넌트다. 원래 목적(\"`LoginForm > SubmitButton` 이 오면 바로 파일이 열린다\")이\n * 무너지는데다 300자 넘는 잡음이 제보마다 들어갔다. Vite + 클라이언트 컴포넌트인\n * KITS 웹에서는 정상이라 안 드러났다.\n *\n * **접미사로 거르지 않는다.** `*Provider`·`*Context`·`*Boundary` 를 통째로 지우면\n * 사람이 지은 `AuthProvider`·`ThemeContext` 까지 사라진다. 그래서 실제로 관측된\n * 이름만 정확히 적는다 — 새 프레임워크 잡음이 보이면 여기 추가한다.\n */\nconst FRAMEWORK_INTERNALS = new Set([\n // Next.js App Router\n \"AppRouter\",\n \"InnerLayoutRouter\",\n \"OuterLayoutRouter\",\n \"LayoutRouterContext\",\n \"RenderFromTemplateContext\",\n \"TemplateContext\",\n \"SegmentStateProvider\",\n \"SegmentViewNode\",\n \"ScrollAndMaybeFocusHandler\",\n \"InnerScrollAndFocusHandler\",\n \"InnerScrollAndFocusHandlerOld\",\n \"RedirectBoundary\",\n \"RedirectErrorBoundary\",\n \"NotFoundBoundary\",\n \"NotFoundErrorBoundary\",\n \"HTTPAccessFallbackBoundary\",\n \"HTTPAccessFallbackErrorBoundary\",\n \"DevRootHTTPAccessFallbackBoundary\",\n \"LoadingBoundary\",\n \"ErrorBoundaryHandler\",\n \"MetadataBoundary\",\n \"ViewportBoundary\",\n \"OutletBoundary\",\n // 개발 전용 오버레이 — 프로덕션엔 없지만 dev 제보에는 항상 낀다\n \"HotReload\",\n \"AppDevOverlay\",\n \"AppDevOverlayErrorBoundary\",\n \"ReactDevOverlay\",\n \"DevOverlay\",\n \"Router\",\n \"ServerRoot\",\n \"RSCComponent\",\n]);\n\n/** `__next_root_layout_boundary__` 처럼 규칙이 이름에 드러나는 것들. */\nfunction isFrameworkName(name: string): boolean {\n return FRAMEWORK_INTERNALS.has(name) || name.startsWith(\"__next\");\n}\n\n/** 호스트 요소(div, span…)는 건너뛴다. 우리가 원하는 건 사람이 지은 컴포넌트 이름이다. */\nfunction componentName(type: unknown): string | null {\n if (typeof type === \"function\") {\n const fn = type as { displayName?: string; name?: string };\n return fn.displayName || fn.name || null;\n }\n if (type !== null && typeof type === \"object\") {\n // memo/forwardRef 등 래퍼. 한 겹 안쪽을 본다.\n const wrapped = type as { displayName?: string; render?: unknown; type?: unknown };\n if (wrapped.displayName) return wrapped.displayName;\n if (wrapped.render) return componentName(wrapped.render);\n if (wrapped.type) return componentName(wrapped.type);\n }\n return null;\n}\n\nfunction fiberOf(node: Element): Record<string, unknown> | null {\n // Object.keys 가 아니라 getOwnPropertyNames — React 는 열거 가능하게 심지만,\n // 그렇지 않은 환경/버전에서도 찾을 수 있어야 한다.\n for (const key of Object.getOwnPropertyNames(node)) {\n if (key.startsWith(\"__reactFiber$\") || key.startsWith(\"__reactInternalInstance$\")) {\n const value = (node as unknown as Record<string, unknown>)[key];\n if (value !== null && typeof value === \"object\") return value as Record<string, unknown>;\n }\n }\n return null;\n}\n\n/**\n * 바깥 → 안쪽 순서의 **앱** 컴포넌트 이름 목록.\n * 예: `[\"App\", \"LoginPage\", \"LoginForm\", \"SubmitButton\"]`\n *\n * 프레임워크가 만든 이름은 뺀다. 다 빼고 나면 빈 배열이고, 그게 정직한 답이다 —\n * 서버 컴포넌트만 있는 화면에는 클라이언트 fiber 에 앱 이름이 애초에 없다.\n *\n * @param limit 안쪽에서부터 이만큼만 남긴다. 요소에 **가까운 쪽이 단서**라, 넘치면\n * 바깥(앱 루트 쪽)을 버린다. 예전엔 20 이었는데 그 길이가 그대로 제보에 실려서\n * 한 줄이 300자를 넘었다.\n */\nexport function reactComponentPath(node: Element, limit = 5): string[] {\n try {\n let fiber = fiberOf(node);\n const names: string[] = [];\n // 안쪽 → 바깥 순으로 훑는다. 프레임워크 이름을 거른 뒤에 세야 limit 이\n // 잡음으로 먼저 차지 않는다.\n let depth = 0;\n while (fiber && names.length < limit && depth < 200) {\n depth += 1;\n const name = componentName(fiber.elementType ?? fiber.type);\n // 같은 이름이 연속으로 나오는 건(래퍼) 한 번만 남긴다.\n if (name && !isFrameworkName(name) && names[0] !== name) names.unshift(name);\n fiber = (fiber.return ?? null) as Record<string, unknown> | null;\n }\n return names;\n } catch {\n return [];\n }\n}\n\n/** 제보 본문에 실을 한 줄. 컴포넌트를 못 찾으면 null(빈 줄을 만들지 않는다). */\nexport function reactComponentSummary(node: Element): string | null {\n const path = reactComponentPath(node);\n return path.length > 0 ? path.join(\" > \") : null;\n}\n","import { reactComponentSummary } from \"./react-tree.js\";\n// 지목한 DOM 요소를 제보에 실을 수 있는 형태로 옮긴다.\n//\n// 목표는 \"나중에 이 요소를 다시 찾을 수 있게\" 하는 것이다. 그래서 태그·id·클래스만이 아니라\n// 형제 순서까지 담은 CSS 선택자 경로를 함께 만든다. 못 만들어도 제보는 성립해야 하므로\n// 이 파일의 함수들은 절대 던지지 않는다.\n\nimport type { ElementInfo } from \"@solhun/feedback-kit-core\";\n\n/** 요소 텍스트를 담을 때의 상한. 본문 전체가 실려 페이로드가 붓는 걸 막는다. */\nexport const ELEMENT_TEXT_MAX_CHARS = 200;\n\n/** 선택자 경로를 몇 단계까지 거슬러 올라갈지. 너무 깊으면 오히려 잘 깨진다. */\nexport const SELECTOR_MAX_DEPTH = 8;\n\n/**\n * 빈 여백을 눌렀을 때 가까운 실제 콘텐츠로 붙여 주는 거리.\n *\n * 레이아웃용 `div` 의 padding 을 누르면 브라우저의 event.target 은 그 큰 `div` 다.\n * 그 상태로 박스를 그리면 사용자가 보고 누른 카드 바로 옆인데도 패널 전체를 가리킨다.\n * 다만 멀리 있는 자식까지 임의로 고르면 빈 영역을 지목할 수 없으므로, 한 번의 클릭 오차로\n * 볼 수 있는 24px 안에서만 더 작은 자식으로 붙인다.\n */\nexport const PICK_TARGET_SNAP_PX = 24;\n\n/** 담아 둘 속성. 전부 담으면 인라인 style 같은 게 페이로드를 채운다. */\nconst KEPT_ATTRS = [\n \"id\",\n \"class\",\n \"name\",\n \"type\",\n \"role\",\n \"href\",\n \"aria-label\",\n \"data-testid\",\n \"data-fk-source\",\n];\n\nfunction cssEscape(value: string): string {\n const fn = (globalThis as { CSS?: { escape?: (v: string) => string } }).CSS?.escape;\n if (typeof fn === \"function\") return fn(value);\n // CSS.escape 가 없는 환경 대비 최소 폴백. 식별자에 쓰이면 곤란한 문자만 막는다.\n return value.replace(/[^a-zA-Z0-9_-]/g, (ch) => `\\\\${ch}`);\n}\n\n/** 요소가 형제들 사이에서 몇 번째 같은 태그인지. 1부터 센다. */\nfunction nthOfType(el: Element): number {\n const parent = el.parentElement;\n if (!parent) return 1;\n let index = 0;\n for (const child of Array.from(parent.children)) {\n if (child.tagName === el.tagName) {\n index += 1;\n if (child === el) return index;\n }\n }\n return 1;\n}\n\n/** 같은 태그 형제가 둘 이상인가. 하나뿐이면 `:nth-of-type` 을 붙일 이유가 없다. */\nfunction hasTypeSiblings(el: Element): boolean {\n const parent = el.parentElement;\n if (!parent) return false;\n let count = 0;\n for (const child of Array.from(parent.children)) {\n if (child.tagName === el.tagName) {\n count += 1;\n if (count > 1) return true;\n }\n }\n return false;\n}\n\n/**\n * 요소까지의 CSS 선택자 경로.\n *\n * id 를 만나면 거기서 멈춘다 — 문서에서 유일하다고 가정할 수 있는 지점이라 그 위는 군더더기다.\n * 클래스는 쓰지 않는다: 유틸리티 클래스(`px-4` 같은 것)가 섞이면 선택자가 길기만 하고\n * 재현성은 오히려 떨어진다.\n */\nexport function cssSelectorPath(el: Element | null | undefined): string | null {\n if (!el || typeof el.tagName !== \"string\") return null;\n try {\n const parts: string[] = [];\n let node: Element | null = el;\n let depth = 0;\n\n while (node && depth < SELECTOR_MAX_DEPTH) {\n const tag = node.tagName.toLowerCase();\n if (tag === \"html\" || tag === \"body\") {\n parts.unshift(tag);\n break;\n }\n\n const id = node.getAttribute(\"id\");\n if (id) {\n parts.unshift(`#${cssEscape(id)}`);\n break; // id 위로는 올라갈 필요가 없다.\n }\n\n parts.unshift(hasTypeSiblings(node) ? `${tag}:nth-of-type(${nthOfType(node)})` : tag);\n node = node.parentElement;\n depth += 1;\n }\n\n return parts.length > 0 ? parts.join(\" > \") : null;\n } catch {\n return null;\n }\n}\n\n/** 눈에 보이는 텍스트를 한 줄로 눌러 담는다. */\nexport function visibleText(el: Element): string | null {\n const raw = (el as HTMLElement).innerText ?? el.textContent ?? \"\";\n const collapsed = raw.replace(/\\s+/g, \" \").trim();\n if (collapsed === \"\") return null;\n return collapsed.length > ELEMENT_TEXT_MAX_CHARS\n ? collapsed.slice(0, ELEMENT_TEXT_MAX_CHARS)\n : collapsed;\n}\n\ntype ClientPoint = { x: number; y: number };\n\ntype ElementRect = {\n element: Element;\n left: number;\n top: number;\n right: number;\n bottom: number;\n width: number;\n height: number;\n};\n\nfunction elementRect(element: Element): ElementRect | null {\n try {\n const rect = element.getBoundingClientRect();\n const left = rect.left;\n const top = rect.top;\n const width = rect.width;\n const height = rect.height;\n if (![left, top, width, height].every(Number.isFinite) || width <= 0 || height <= 0) {\n return null;\n }\n return {\n element,\n left,\n top,\n right: left + width,\n bottom: top + height,\n width,\n height,\n };\n } catch {\n return null;\n }\n}\n\nfunction distanceFromPoint(rect: ElementRect, point: ClientPoint): number {\n const dx = Math.max(rect.left - point.x, 0, point.x - rect.right);\n const dy = Math.max(rect.top - point.y, 0, point.y - rect.bottom);\n return Math.hypot(dx, dy);\n}\n\n/**\n * 브라우저가 돌려준 event.target 을 사용자가 보고 누른 더 정확한 요소로 좁힌다.\n *\n * 1. 클릭을 포함하는 자식이 있으면 가장 작은 자식으로 계속 내려간다. `pointer-events: none`\n * 장식 아래의 실제 시각 요소도 고를 수 있어야 해서 hit-test 결과만 다시 묻지 않는다.\n * 2. 클릭이 padding 에 떨어졌다면 24px 안의 더 작은 직계 자식까지만 한 번 붙인다.\n * 그보다 멀면 사용자가 빈 레이아웃 자체를 고른 것으로 보고 원래 target 을 보존한다.\n *\n * DOM/레이아웃 접근이 실패해도 원래 target 을 돌려 제보 자체는 성립하게 한다.\n */\nexport function resolvePickTarget(\n target: Element | null | undefined,\n point: ClientPoint,\n): Element | null {\n if (!target || typeof target.tagName !== \"string\") return null;\n if (!Number.isFinite(point.x) || !Number.isFinite(point.y)) return target;\n\n let current = target;\n while (true) {\n const currentRect = elementRect(current);\n const children = Array.from(current.children)\n .map(elementRect)\n .filter((rect): rect is ElementRect => rect !== null)\n .map((rect) => ({ rect, distance: distanceFromPoint(rect, point) }));\n\n if (children.length === 0) return current;\n\n const containing = children\n .filter(({ distance }) => distance === 0)\n .sort((a, b) => a.rect.width * a.rect.height - b.rect.width * b.rect.height);\n if (containing[0]) {\n current = containing[0].rect.element;\n continue;\n }\n\n const nearest = children.sort((a, b) => a.distance - b.distance)[0];\n const isMorePrecise = currentRect\n ? nearest.rect.width * nearest.rect.height < currentRect.width * currentRect.height\n : true;\n if (nearest.distance <= PICK_TARGET_SNAP_PX && isMorePrecise) {\n return nearest.rect.element;\n }\n return current;\n }\n}\n\n/** 지목된 요소를 `ElementInfo` 로 옮긴다. 실패해도 던지지 않는다. */\nexport function describeElement(el: Element | null | undefined): ElementInfo | null {\n if (!el || typeof el.tagName !== \"string\") return null;\n\n const attributes: Record<string, string> = {};\n for (const name of KEPT_ATTRS) {\n const value = el.getAttribute(name);\n if (value !== null) attributes[name] = value;\n }\n\n let boundingBox: ElementInfo[\"boundingBox\"] = null;\n try {\n const rect = el.getBoundingClientRect();\n boundingBox = { x: rect.left, y: rect.top, width: rect.width, height: rect.height };\n } catch {\n boundingBox = null;\n }\n\n const react = reactComponentSummary(el);\n\n return {\n tag: el.tagName.toLowerCase(),\n id: el.getAttribute(\"id\"),\n className: el.getAttribute(\"class\"),\n text: visibleText(el),\n boundingBox,\n attributes: {\n ...attributes,\n // 지목한 요소가 어느 컴포넌트에 속하는지. `div.flex.items-center` 만 오면\n // 고치는 사람이 코드에서 다시 찾아야 하지만, `LoginForm > SubmitButton` 이\n // 오면 바로 파일이 열린다. React 가 아니면 이 키 자체가 없다.\n ...(react ? { \"react-components\": react } : {}),\n },\n selector: cssSelectorPath(el),\n };\n}\n","// 요소 지목 모드와 마커를 브라우저 저장소에 남긴다.\n//\n// 왜 저장하나: 지목 모드는 페이지를 옮겨 다니며 쓰는 기능이라 새로고침·이동에 살아남아야 하고,\n// 찍은 마커는 같은 경로로 돌아왔을 때 원래 자리에 다시 보여야 한다.\n//\n// 키 이름은 `feedback-kit:` 로 시작하는 우리 고유 네임스페이스를 쓴다. 남의 위젯과 키가 겹치면\n// 서로의 상태를 지우게 된다.\n\n/** 지목 모드 on/off 플래그. */\nexport const PICKING_MODE_KEY = \"feedback-kit:picking-mode\";\n\n/** 경로별 마커 목록. 실제 키는 여기에 pathname 을 이어 붙인다. */\nexport const MARKER_KEY_PREFIX = \"feedback-kit:markers:\";\n\n/** 한 경로에 남겨 둘 마커 상한. 넘으면 오래된 것부터 버린다. */\nexport const MAX_MARKERS_PER_PATH = 50;\n\n/** 마커에 보이는 전송 상태. `sending` → `done`, 실패하면 `pending`. */\nexport type MarkerStatus = \"sending\" | \"done\" | \"pending\";\n\nexport interface StoredMarker {\n /** 제보의 `clientSubmissionId` 와 같다 — 전송 상태를 되짚어 갱신하려고 맞춰 둔다. */\n id: string;\n /** 뷰포트 기준 상대 좌표(0~1). 해상도가 달라도 같은 지점을 가리킨다. */\n x: number;\n y: number;\n selector: string | null;\n comment: string;\n status: MarkerStatus;\n /** 생성 시각(ISO). 상한을 넘겼을 때 무엇을 버릴지 정하는 기준. */\n at: string;\n}\n\n/** `localStorage` 가 만족하는 최소 계약. 테스트에서 갈아 끼운다. */\nexport interface StorageLike {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n}\n\nfunction defaultStorage(): StorageLike | null {\n try {\n const ls = (globalThis as { localStorage?: StorageLike }).localStorage;\n return ls ?? null;\n } catch {\n // 사파리 프라이빗 모드처럼 접근 자체가 던지는 환경이 있다.\n return null;\n }\n}\n\nfunction markerKey(pathname: string): string {\n return `${MARKER_KEY_PREFIX}${pathname}`;\n}\n\n/**\n * 저장소 접근을 한 곳으로 모은다.\n *\n * 저장소가 없거나(SSR) 던지는 환경(프라이빗 모드·용량 초과)에서도 위젯이 죽으면 안 되므로\n * 모든 접근을 try/catch 로 감싸고 실패는 조용히 흘린다. 저장 실패는 \"마커가 안 남는다\" 정도의\n * 불편이지 제보 자체를 막을 이유가 아니다.\n */\nexport class MarkerStore {\n private readonly storage: StorageLike | null;\n\n constructor(storage?: StorageLike | null) {\n this.storage = storage === undefined ? defaultStorage() : storage;\n }\n\n private read(key: string): string | null {\n try {\n return this.storage?.getItem(key) ?? null;\n } catch {\n return null;\n }\n }\n\n private write(key: string, value: string): void {\n try {\n this.storage?.setItem(key, value);\n } catch {\n /* 저장 실패는 흘린다 */\n }\n }\n\n private drop(key: string): void {\n try {\n this.storage?.removeItem(key);\n } catch {\n /* 삭제 실패도 흘린다 */\n }\n }\n\n // ── 지목 모드 ─────────────────────────────────────────────────────────────\n\n isPickingActive(): boolean {\n return this.read(PICKING_MODE_KEY) === \"1\";\n }\n\n setPickingActive(active: boolean): void {\n if (active) this.write(PICKING_MODE_KEY, \"1\");\n else this.drop(PICKING_MODE_KEY);\n }\n\n // ── 마커 ──────────────────────────────────────────────────────────────────\n\n list(pathname: string): StoredMarker[] {\n const raw = this.read(markerKey(pathname));\n if (!raw) return [];\n try {\n const parsed: unknown = JSON.parse(raw);\n if (!Array.isArray(parsed)) return [];\n // 남이 쓴 값이나 옛 포맷이 섞여 있어도 위젯이 깨지지 않게 형태를 확인하고 거른다.\n return parsed.filter((item): item is StoredMarker => {\n if (typeof item !== \"object\" || item === null) return false;\n const m = item as Partial<StoredMarker>;\n return (\n typeof m.id === \"string\" && typeof m.x === \"number\" && typeof m.y === \"number\"\n );\n });\n } catch {\n return [];\n }\n }\n\n /** 같은 id 가 있으면 덮어쓰고, 없으면 뒤에 붙인다. */\n upsert(pathname: string, marker: StoredMarker): StoredMarker[] {\n const current = this.list(pathname);\n const index = current.findIndex((m) => m.id === marker.id);\n if (index >= 0) current[index] = marker;\n else current.push(marker);\n\n const trimmed =\n current.length > MAX_MARKERS_PER_PATH\n ? current.slice(current.length - MAX_MARKERS_PER_PATH)\n : current;\n this.write(markerKey(pathname), JSON.stringify(trimmed));\n return trimmed;\n }\n\n /** 전송 상태만 바꾼다. 없는 id 면 아무것도 하지 않는다. */\n updateStatus(pathname: string, id: string, status: MarkerStatus): StoredMarker[] {\n const current = this.list(pathname);\n const index = current.findIndex((m) => m.id === id);\n if (index < 0) return current;\n current[index] = { ...current[index], status };\n this.write(markerKey(pathname), JSON.stringify(current));\n return current;\n }\n\n clear(pathname: string): void {\n this.drop(markerKey(pathname));\n }\n}\n","// 요소 지목 모드 (웹 전용).\n//\n// 켜져 있는 동안 페이지의 아무 요소나 클릭하면 그 자리에 주석을 남긴다. 리포트 모달과 달리\n// \"열고 → 쓰고 → 닫는\" 절차가 없다 — 한 번 켜 두고 여러 곳을 연달아 지목하는 게 이 모드의 전부다.\n// 그래서 주석을 저장해도 모드는 꺼지지 않고, 페이지를 옮겨도 살아남는다.\n//\n// 페이지의 원래 클릭(링크 이동·버튼 동작)은 막아야 한다. 지목하려고 누른 링크가 실제로 이동해\n// 버리면 주석을 달 화면이 사라진다.\n\nimport {\n COMMENT_MAX_CHARS,\n captureWithinLimit,\n normalizePin,\n type ElementInfo,\n type FeedbackHint,\n type FeedbackPin,\n type FeedbackReport,\n type FeedbackScreenshot,\n type ReportParts,\n type QueueStatus,\n type ScreenshotCapture,\n type ScreenshotReencode,\n type RouteWatcher,\n type SubmitOutcome,\n} from \"@solhun/feedback-kit-core\";\nimport { describeElement, resolvePickTarget } from \"./element-info.js\";\nimport { MarkerStore, type StoredMarker } from \"./marker-store.js\";\nimport { webRouteWatcher } from \"./route-watcher.js\";\n\n/** 위젯 자신의 UI 를 표시하는 속성. 이 안쪽 클릭은 가로채지 않는다. */\nexport const OWN_UI_ATTR = \"data-feedback-kit\";\n\n/** 지목 컨트롤러가 큐에게 요구하는 최소 계약. */\nexport interface PickingQueueLike {\n submit(report: FeedbackReport): Promise<SubmitOutcome>;\n subscribe?(listener: (status: QueueStatus) => void): () => void;\n getOutcome?(clientSubmissionId: string): Pick<SubmitOutcome, \"delivered\" | \"id\"> | null;\n}\n\n/** 퀵 주석의 스크린샷 상태. 모달과 같은 어휘를 쓴다(렌더러가 둘을 같은 방식으로 그린다). */\nexport type AnnotationShotStatus = \"none\" | \"capturing\" | \"ready\" | \"failed\";\n\nexport interface AnnotationPopupState {\n element: ElementInfo | null;\n /** 클릭한 지점의 뷰포트 상대 좌표(0~1). */\n point: FeedbackPin;\n comment: string;\n canSave: boolean;\n /**\n * 이 주석에 실려 갈 그림. 클릭 직후 자동 캡처가 채우고, 사용자가 붙여넣기(Cmd+V)나\n * 드래그로 덮을 수 있다.\n *\n * 왜 퀵 주석에도 그림을 싣나: 이게 없으면 **들어온 문에 따라 정보량이 달라진다.**\n * 지목으로 남긴 제보만 화면 증거가 비는 일이 실제로 있었고(0.2.0), 그때 얻은 규칙이\n * \"같은 제보는 어느 문으로 들어와도 같은 것을 담는다\" 다.\n */\n screenshot: FeedbackScreenshot | null;\n screenshotStatus: AnnotationShotStatus;\n /** 저장 중인가. 두 번 눌러 같은 주석이 두 번 가는 것을 막는다. */\n saving: boolean;\n /**\n * 자주 나오는 제보 제안 칩. 리포트 모달과 같은 계약(같은 `rankHints` 결과, 같은 접힘 규칙,\n * 같은 `hintIds` 흐름)이다 — \"들어온 문이 달라도 담기는 정보는 같다\"가 이 위젯의 약속이고,\n * 제안 칩도 예외가 아니다. `widget.ts` 가 만든 `HintProvider` 를 그대로 물려 쓴다\n * (팝업이 자기 카탈로그를 따로 들고 있지 않는다).\n */\n hints: readonly FeedbackHint[];\n hintsExpanded: boolean;\n hintsAvailable: boolean;\n usedHintIds: readonly string[];\n}\n\nexport interface PickingState {\n active: boolean;\n /**\n * 켜져 있지만 **클릭 가로채기만 잠시 끈 상태**.\n *\n * 왜 필요한가: 지목 모드는 페이지의 모든 클릭을 먹는다(그래야 지목하려고 누른 링크가\n * 실제로 이동해 버리지 않는다). 그 대가로 **제보할 화면을 만들 수가 없다** — 드롭다운을\n * 열고 그 안의 항목을 지목하고 싶어도 여는 클릭부터 막힌다. 그때 모드를 끄면 다시\n * 켜야 하고, 사용자에겐 그게 \"모드가 유지되지 않는다\"로 읽힌다.\n *\n * 그래서 끄는 것과 멈추는 것을 나눈다. 멈춘 동안 페이지는 원래대로 동작하고, 모드는\n * 켜진 채로 남는다.\n */\n paused: boolean;\n /** 지금 마우스가 올라가 있는 요소. 하이라이트를 그리는 근거. */\n hovered: ElementInfo | null;\n popup: AnnotationPopupState | null;\n /** 현재 경로에 남아 있는 마커들. */\n markers: readonly StoredMarker[];\n}\n\nexport type PickingListener = (state: PickingState) => void;\n\nexport interface ElementPickingOpts {\n queue: PickingQueueLike;\n createReport: (parts: ReportParts) => Promise<FeedbackReport>;\n store?: MarkerStore;\n /** 현재 경로. 마커는 경로별로 나눠 저장한다. */\n getPathname?: () => string;\n /** 이벤트를 들을 문서. 기본은 전역 `document`. */\n doc?: Document | null;\n /** 좌표를 정규화할 기준 크기. 기본은 현재 뷰포트. */\n getViewport?: () => { width: number; height: number };\n /**\n * 퀵 주석에 붙일 자동 캡처. 없으면 그림 없이 코멘트만 받는다.\n * 웹 기본 캡처는 위젯 자신의 UI 를 결과에서 빼므로(`ignoreElements`) 팝업이 찍히지 않는다.\n */\n capture?: ScreenshotCapture | null;\n reencode?: ScreenshotReencode | null;\n screenshotLimitBytes?: number;\n /**\n * 요소를 고르면 호출된다. 주면 **퀵 주석 팝업 대신 이걸 부른다** —\n * 호스트가 전체 리포트 모달을 열어 우선순위까지 받고 싶을 때의 탈출구다.\n * 기본값은 팝업이다: 지목은 \"클릭 → 한 줄 → Enter\" 로 끝나는 게 목적이라,\n * 매번 중앙 모달이 뜨면 흐름이 끊긴다.\n *\n * 모드는 켜진 채로 둔다. 한 번 켜고 여러 요소를 연달아 지목하는 게 이 모드의 목적이라,\n * 요소 하나 고를 때마다 모드가 꺼지면 매번 다시 켜야 한다.\n */\n onPick?: (element: ElementInfo | null, point: FeedbackPin) => void;\n /**\n * 힌트 랭킹 계산. `widget.ts` 가 만든 `HintProvider` 의 카탈로그를 그 시점의 경로·요소로\n * `rankHints` 한 결과를 돌려준다. 없으면(힌트 기능이 꺼져 있으면) 팝업에 칩이 안 뜬다.\n */\n rankHintsFor?: ((element: ElementInfo | null) => readonly FeedbackHint[]) | null;\n /**\n * 화면 이동 감시자. 기본은 웹 공용 감시자(`webRouteWatcher`) — **진단의 화면 이동 궤적과\n * 같은 것**이다. 여기만 따로 구현하면 한쪽이 이동을 놓쳤을 때 원인을 가릴 수 없다.\n * `null` 을 주면 감시하지 않는다(테스트에서 `syncPath()` 를 직접 부를 때).\n */\n watchRoutes?: RouteWatcher | null;\n}\n\nfunction defaultDocument(): Document | null {\n return (globalThis as { document?: Document }).document ?? null;\n}\n\nfunction defaultPathname(): string {\n const loc = (globalThis as { location?: { pathname?: string } }).location;\n return loc?.pathname ?? \"/\";\n}\n\nfunction defaultViewport(): { width: number; height: number } {\n const w = globalThis as { innerWidth?: number; innerHeight?: number };\n return { width: w.innerWidth ?? 0, height: w.innerHeight ?? 0 };\n}\n\n/** 위젯 자신의 UI 안에서 일어난 일인가. */\nfunction isOwnUi(target: EventTarget | null): boolean {\n const el = target as Element | null;\n if (!el || typeof el.closest !== \"function\") return false;\n return el.closest(`[${OWN_UI_ATTR}]`) !== null;\n}\n\nexport class ElementPickingController {\n private readonly queue: PickingQueueLike;\n private readonly createReport: (parts: ReportParts) => Promise<FeedbackReport>;\n private readonly onPick:\n | ((element: ElementInfo | null, point: FeedbackPin) => void)\n | null;\n private readonly store: MarkerStore;\n private readonly getPathname: () => string;\n private readonly doc: Document | null;\n private readonly getViewport: () => { width: number; height: number };\n private readonly capture: ScreenshotCapture | null;\n private readonly reencode: ScreenshotReencode | null;\n private readonly screenshotLimitBytes: number | undefined;\n private readonly unsubscribeQueue: () => void;\n private readonly rankHintsFor: ((element: ElementInfo | null) => readonly FeedbackHint[]) | null;\n\n private readonly listeners = new Set<PickingListener>();\n\n private active = false;\n /**\n * 일시중지. **저장하지 않는다** — 멈추는 목적이 대개 \"링크를 눌러 다른 화면으로 가는 것\"\n * 이라, 그 이동이 끝나면 지목이 돌아와 있는 게 목적에 맞다. 같은 이유로 경로가 바뀌면\n * 스스로 풀린다(`syncPath`) — 그래야 SPA 이동과 새로고침이 같게 동작한다.\n */\n private paused = false;\n private attached = false;\n /** 같은 요소 위 mousemove 마다 React 경로·선택자를 다시 만들지 않기 위한 캐시. */\n private hoveredTarget: Element | null = null;\n private hovered: ElementInfo | null = null;\n private popup: AnnotationPopupState | null = null;\n private markers: StoredMarker[] = [];\n private readonly watchRoutes: RouteWatcher | null;\n /** 경로 감시 해제. 감시 중이 아니면 null. */\n private unwatchRoutes: (() => void) | null = null;\n private lastPathname: string;\n private saving = false;\n /** 늦게 끝난 캡처가 다음 주석의 그림을 덮지 못하게 하는 세대 번호. */\n private shotGeneration = 0;\n /** 지금 도는 자동 캡처. Enter 가 캡처보다 빨랐을 때 기다릴 대상. */\n private capturing: Promise<void> | null = null;\n /**\n * 사용자가 [이런 건가요? ▾] 를 직접 건드렸는가. 한 번이라도 건드리면 이후 타이핑에 의한 자동\n * 접힘/펼침이 멈춘다 — 모달의 `userToggledHints` 와 같은 규칙. 팝업을 새로 열 때마다 리셋된다.\n */\n private userToggledHints = false;\n\n private readonly onClick = (event: Event) => this.handleClick(event);\n private readonly onMouseOver = (event: Event) => this.handleMouseOver(event);\n private readonly onMouseMove = (event: Event) => this.handleMouseOver(event);\n\n constructor(opts: ElementPickingOpts) {\n this.queue = opts.queue;\n this.createReport = opts.createReport;\n this.store = opts.store ?? new MarkerStore();\n this.onPick = opts.onPick ?? null;\n this.getPathname = opts.getPathname ?? defaultPathname;\n this.doc = opts.doc === undefined ? defaultDocument() : opts.doc;\n this.getViewport = opts.getViewport ?? defaultViewport;\n this.capture = opts.capture ?? null;\n this.reencode = opts.reencode ?? null;\n this.screenshotLimitBytes = opts.screenshotLimitBytes;\n this.rankHintsFor = opts.rankHintsFor ?? null;\n this.watchRoutes = opts.watchRoutes === undefined ? webRouteWatcher : opts.watchRoutes;\n this.lastPathname = this.getPathname();\n this.markers = this.store.list(this.lastPathname);\n this.unsubscribeQueue =\n this.queue.subscribe?.(() => {\n if (this.reconcileMarkerOutcomes()) this.emit();\n }) ?? (() => undefined);\n }\n\n // ── 조회 ──────────────────────────────────────────────────────────────────\n\n getState(): PickingState {\n return {\n active: this.active,\n paused: this.paused,\n hovered: this.hovered,\n popup: this.popup,\n markers: this.markers,\n };\n }\n\n get isActive(): boolean {\n return this.active;\n }\n\n /** 켜져 있지만 클릭을 안 먹는 상태인가. */\n get isPaused(): boolean {\n return this.paused;\n }\n\n subscribe(listener: PickingListener): () => void {\n this.listeners.add(listener);\n listener(this.getState());\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n // ── 모드 on/off ───────────────────────────────────────────────────────────\n\n start(): void {\n if (this.active) return;\n this.active = true;\n this.paused = false;\n this.store.setPickingActive(true);\n this.attach();\n this.startPathWatch();\n this.markers = this.store.list(this.getPathname());\n this.emit();\n }\n\n /** [지목 종료]. 저장된 플래그까지 지워서 새로고침해도 다시 켜지지 않게 한다. */\n stop(): void {\n this.active = false;\n this.paused = false;\n this.store.setPickingActive(false);\n this.detach();\n this.stopPathWatch();\n this.hoveredTarget = null;\n this.hovered = null;\n this.popup = null;\n this.emit();\n }\n\n /**\n * 잠시 멈춘다 — 페이지 클릭이 원래대로 동작하고, 모드는 켜진 채로 남는다.\n *\n * 팝업이 열려 있으면 **그대로 둔다.** 쓰던 한 줄을 여기서 지우면 「지목 켜면 모달 초안이\n * 날아간다」와 같은 사고를 다른 자리에 만드는 셈이다. 팝업은 위젯 자신의 UI 라\n * 리스너를 떼도 계속 눌린다.\n */\n pause(): void {\n if (!this.active || this.paused) return;\n this.paused = true;\n this.detach();\n this.hoveredTarget = null;\n this.hovered = null; // 멈춘 동안 하이라이트가 남아 있으면 아직 먹는 줄로 읽힌다\n this.emit();\n }\n\n /** 다시 지목을 받는다. */\n resume(): void {\n if (!this.active || !this.paused) return;\n this.paused = false;\n this.attach();\n this.emit();\n }\n\n togglePause(): void {\n if (this.paused) this.resume();\n else this.pause();\n }\n\n /**\n * 저장돼 있던 모드를 되살린다. 새로고침·페이지 이동 직후에 한 번 부른다.\n * @returns 되살아났으면 true.\n */\n restore(): boolean {\n if (!this.store.isPickingActive()) {\n this.markers = this.store.list(this.getPathname());\n this.emit();\n return false;\n }\n this.start();\n return true;\n }\n\n /**\n * 이 화면에 쌓인 마커 표시를 지운다.\n *\n * 지워지는 건 **화면 표시뿐**이다. 제보는 이미 큐를 거쳐 수집처로 갔으므로 없어지지 않는다.\n * 마커는 `localStorage` 에 남아 새로고침에도 살아남는데(그게 원래 목적이다) 정작 치울\n * 수단이 없어서, 한 번 보낸 [완료] 배지가 그 화면을 볼 때마다 계속 따라다녔다.\n *\n * 화면에 보이는 목록의 기준은 `lastPathname` 이다(`reconcileMarkerOutcomes` 와 같다).\n * 이동 직후 아직 `syncPath` 가 안 돈 순간에도 \"지금 눈에 보이는 것\"이 지워져야 한다.\n */\n clearMarkers(): void {\n if (this.markers.length === 0) return;\n this.store.clear(this.lastPathname);\n this.markers = [];\n this.emit();\n }\n\n /** 경로가 바뀌었을 때 그 경로의 마커로 갈아 끼운다. */\n syncPath(): readonly StoredMarker[] {\n // 멈춘 이유가 대개 \"여길 눌러 다른 화면으로 가려고\" 다. 이동이 끝났으면 그 이유도\n // 끝났으므로 스스로 재개한다 — 안 그러면 이동할 때마다 사용자가 다시 켜야 한다.\n if (this.paused) this.resume();\n this.lastPathname = this.getPathname();\n this.markers = this.store.list(this.lastPathname);\n this.reconcileMarkerOutcomes();\n this.popup = null;\n this.hoveredTarget = null;\n this.hovered = null;\n this.emit();\n return this.markers;\n }\n\n dispose(): void {\n this.detach();\n this.stopPathWatch();\n this.unsubscribeQueue();\n this.listeners.clear();\n }\n\n // ── DOM 이벤트 ────────────────────────────────────────────────────────────\n\n private attach(): void {\n if (this.attached || !this.doc) return;\n // 캡처 단계에서 잡아야 페이지의 핸들러보다 먼저 막을 수 있다.\n this.doc.addEventListener(\"click\", this.onClick, true);\n this.doc.addEventListener(\"mouseover\", this.onMouseOver, true);\n // padding 안에서는 event.target 이 그대로라 mouseover 가 다시 오지 않는다. 포인터가\n // 가까운 콘텐츠의 24px 범위에 들어왔는지 갱신하려면 이동 좌표도 봐야 한다.\n this.doc.addEventListener(\"mousemove\", this.onMouseMove, true);\n this.attached = true;\n }\n\n private detach(): void {\n if (!this.attached || !this.doc) return;\n this.doc.removeEventListener(\"click\", this.onClick, true);\n this.doc.removeEventListener(\"mouseover\", this.onMouseOver, true);\n this.doc.removeEventListener(\"mousemove\", this.onMouseMove, true);\n this.attached = false;\n }\n\n private handleMouseOver(event: Event): void {\n if (!this.active || this.paused || isOwnUi(event.target)) return;\n const mouse = event as MouseEvent;\n const target = resolvePickTarget(event.target as Element | null, {\n x: mouse.clientX ?? 0,\n y: mouse.clientY ?? 0,\n });\n if (target === this.hoveredTarget) return;\n this.hoveredTarget = target;\n this.hovered = describeElement(target);\n this.emit();\n }\n\n private handleClick(event: Event): void {\n if (!this.active || this.paused) return;\n if (isOwnUi(event.target)) return;\n\n // 페이지가 이 클릭을 보지 못하게 한다 — 링크 이동·버튼 동작을 전부 막는다.\n event.preventDefault();\n event.stopPropagation();\n if (typeof event.stopImmediatePropagation === \"function\") {\n event.stopImmediatePropagation();\n }\n\n const mouse = event as MouseEvent;\n this.openAnnotation(event.target as Element | null, {\n x: mouse.clientX ?? 0,\n y: mouse.clientY ?? 0,\n });\n }\n\n // ── 주석 팝업 ─────────────────────────────────────────────────────────────\n\n /** 클릭 지점 기준으로 주석 팝업을 연다. 좌표는 해상도 무관한 상대값으로 접어 둔다. */\n openAnnotation(element: Element | null, clientPoint: { x: number; y: number }): void {\n const point = normalizePin(clientPoint, this.getViewport());\n const target = resolvePickTarget(element, clientPoint);\n if (this.onPick) {\n // 호스트가 모달을 연다. 팝업은 만들지 않는다(둘이 동시에 뜨면 안 된다).\n this.onPick(describeElement(target), point);\n return;\n }\n const info = describeElement(target);\n // 새로 여는 팝업이니 이전 판단은 잊는다 — 다음 판단은 이번 코멘트 값으로 한다.\n this.userToggledHints = false;\n const hints = this.rankHintsFor ? this.rankHintsFor(info) : [];\n this.popup = {\n element: info,\n point,\n comment: \"\",\n canSave: false,\n screenshot: null,\n screenshotStatus: this.capture ? \"capturing\" : \"none\",\n saving: false,\n hints,\n // 코멘트가 비어 있을 때만 펼친다 — 모달과 같은 규칙(계약 원문). 방금 연 팝업은 항상 빈다.\n hintsExpanded: true,\n hintsAvailable: hints.length > 0,\n usedHintIds: [],\n };\n this.emit();\n if (this.capture) this.capturing = this.runCapture();\n }\n\n setAnnotationComment(value: string): void {\n if (!this.popup) return;\n this.popup = {\n ...this.popup,\n comment: value,\n canSave: this.isSavable(value),\n ...this.hintsPatchFor(value),\n };\n this.emit();\n }\n\n /**\n * 코멘트가 비었는지에 따라 칩 줄 상태를 갱신하는 조각. `setAnnotationComment`·`applyHint`\n * 양쪽에서 쓴다 — 모달의 `hintsPatchFor` 와 같은 규칙이다.\n */\n private hintsPatchFor(nextComment: string): Partial<AnnotationPopupState> {\n if (nextComment.trim().length === 0) {\n return { hintsExpanded: true, usedHintIds: [] };\n }\n return this.userToggledHints ? {} : { hintsExpanded: false };\n }\n\n /**\n * 칩 누름. 이미 눌린 칩이면 아무 일도 하지 않는다. 코멘트가 비어 있으면 초안으로 치환,\n * 아니면 줄바꿈 뒤 이어붙인다 — 모달의 `applyHint` 와 동일한 규칙이다.\n */\n applyHint(hintId: string): void {\n if (!this.popup || this.popup.saving) return;\n if (this.popup.usedHintIds.includes(hintId)) return;\n const hint = this.popup.hints.find((h) => h.id === hintId);\n if (!hint) return;\n const comment = this.popup.comment.length === 0\n ? hint.draft\n : `${this.popup.comment}\\n${hint.draft}`;\n this.popup = {\n ...this.popup,\n comment,\n canSave: this.isSavable(comment),\n usedHintIds: [...this.popup.usedHintIds, hintId],\n ...this.hintsPatchFor(comment),\n };\n this.emit();\n }\n\n /** [이런 건가요? ▾] 토글. 방향과 무관하게 이후 자동 접힘/펼침을 멈춘다. */\n toggleHints(): void {\n if (!this.popup) return;\n this.userToggledHints = true;\n this.popup = { ...this.popup, hintsExpanded: !this.popup.hintsExpanded };\n this.emit();\n }\n\n /**\n * 사용자가 붙여넣기(Cmd+V)나 드래그로 넣은 그림. 자동 캡처 결과를 덮는다.\n *\n * 자동 캡처보다 이게 우선인 이유: 사용자가 굳이 그림을 붙였다면 그건 \"지금 화면\"이\n * 아니라 **보여주고 싶은 다른 것**이다(잘라낸 부분, 다른 탭, 기대하는 디자인).\n */\n async attachAnnotationImage(shot: FeedbackScreenshot): Promise<void> {\n if (!this.popup || this.popup.saving) return;\n // 붙여넣은 그림도 8 MiB 정책을 통과해야 한다. 통과 못 하면 실패로 표시하고 그림은 비운다.\n const generation = ++this.shotGeneration;\n const outcome = await captureWithinLimit({\n capture: () => shot,\n reencode: this.reencode,\n limitBytes: this.screenshotLimitBytes,\n });\n if (generation !== this.shotGeneration || !this.popup) return;\n this.popup = {\n ...this.popup,\n screenshot: outcome.failed ? null : outcome.screenshot,\n screenshotStatus: outcome.failed || !outcome.screenshot ? \"failed\" : \"ready\",\n };\n this.emit();\n }\n\n /** 그림만 뗀다. 코멘트는 그대로 — 그림이 없어도 주석은 성립한다. */\n removeAnnotationImage(): void {\n if (!this.popup || this.popup.saving) return;\n this.shotGeneration += 1; // 진행 중인 캡처가 되살리지 못하게\n this.popup = { ...this.popup, screenshot: null, screenshotStatus: \"none\" };\n this.emit();\n }\n\n /** 취소 — 마커도 제보도 남기지 않는다. 모드는 켜진 채로 둔다. */\n cancelAnnotation(): void {\n this.shotGeneration += 1;\n this.popup = null;\n this.emit();\n }\n\n private isSavable(comment: string): boolean {\n return comment.trim().length > 0 && comment.length <= COMMENT_MAX_CHARS;\n }\n\n /** 도는 캡처가 있으면 끝나기를 기다린다. 캡처가 던져도 저장을 막지 않는다. */\n private async settleCapture(): Promise<void> {\n try {\n await this.capturing;\n } catch {\n /* 캡처 실패는 그림 없음일 뿐이다 */\n }\n }\n\n private async runCapture(): Promise<void> {\n if (!this.capture) return;\n const generation = ++this.shotGeneration;\n const outcome = await captureWithinLimit({\n capture: this.capture,\n reencode: this.reencode,\n limitBytes: this.screenshotLimitBytes,\n });\n // 그 사이 팝업이 닫혔거나 다른 요소를 지목했으면 결과를 버린다.\n if (generation !== this.shotGeneration || !this.popup) return;\n this.popup = {\n ...this.popup,\n screenshot: outcome.failed ? null : outcome.screenshot,\n screenshotStatus: outcome.failed || !outcome.screenshot ? \"failed\" : \"ready\",\n };\n this.emit();\n }\n\n /**\n * 제출된 제보를 마커로 남긴다. 모달 경로에서 쓴다 — 마커 생성이 팝업 저장 안에만\n * 있으면, 리포트 모달로 보낸 지목 제보는 화면에 흔적이 남지 않는다(\"보냈는지 알 수\n * 없다\"가 이 모드가 없애려던 불편이다).\n */\n noteSubmitted(report: FeedbackReport, delivered: boolean): void {\n if (!report.element && !report.pin) return;\n const pathname = this.getPathname();\n const marker: StoredMarker = {\n id: report.clientSubmissionId,\n x: report.pin?.x ?? 0,\n y: report.pin?.y ?? 0,\n selector: report.element?.selector ?? null,\n comment: report.comment,\n status: delivered ? \"done\" : \"pending\",\n at: report.createdAt,\n };\n this.markers = this.store.upsert(pathname, marker);\n this.emit();\n }\n\n /**\n * 주석 저장. 마커를 먼저 \"전송 중\"으로 찍고 나서 보낸다 — 사용자는 결과를 기다리지 않고\n * 다음 요소로 넘어갈 수 있어야 한다.\n *\n * @returns 검증에 걸려 아무것도 하지 않았으면 `null`.\n */\n async saveAnnotation(): Promise<SubmitOutcome | null> {\n const popup = this.popup;\n if (!popup || !popup.canSave || this.saving) return null;\n this.saving = true;\n // 캡처가 아직 도는 중일 수 있다. Enter 를 눌렀다고 그림을 잃지 않게 여기서 기다린다 —\n // 사용자가 한 줄 쓰는 동안 대개 끝나므로 실제로 기다리는 시간은 거의 없다.\n if (popup.screenshotStatus === \"capturing\") await this.settleCapture();\n // 기다리는 사이 취소됐으면 아무것도 보내지 않는다.\n const settled = this.popup;\n if (!settled) {\n this.saving = false;\n return null;\n }\n this.popup = { ...settled, saving: true };\n this.emit();\n\n const pathname = this.getPathname();\n const parts: ReportParts = {\n kind: \"annotation\",\n comment: settled.comment,\n priority: \"unset\",\n screenshot: settled.screenshot,\n pin: settled.point,\n element: settled.element,\n // 눌린 칩 기록. 리포트 모달과 같은 필드 — 들어온 문이 달라도 채택 신호는 같은 값으로 쌓인다.\n hintIds: settled.usedHintIds,\n };\n\n let report: FeedbackReport;\n try {\n report = await this.createReport(parts);\n } catch {\n // 조립이 실패하면 남길 마커의 id 도 없다. 팝업만 닫고 조용히 물러난다.\n this.popup = null;\n this.emit();\n this.saving = false;\n return null;\n }\n\n const marker: StoredMarker = {\n id: report.clientSubmissionId,\n x: settled.point.x,\n y: settled.point.y,\n selector: settled.element?.selector ?? null,\n comment: settled.comment,\n status: \"sending\",\n at: report.createdAt,\n };\n this.markers = this.store.upsert(pathname, marker);\n this.popup = null; // 모드는 그대로 — 다음 요소를 바로 지목할 수 있다.\n this.emit();\n\n let outcome: SubmitOutcome;\n try {\n outcome = await this.queue.submit(report);\n } catch {\n outcome = { delivered: false, id: null, queued: true };\n }\n\n const next = this.store.updateStatus(\n pathname,\n marker.id,\n outcome.delivered ? \"done\" : \"pending\",\n );\n // 저장 중에 경로가 바뀌었으면 화면에 보이는 목록은 건드리지 않는다.\n if (pathname === this.getPathname()) this.markers = next;\n this.emit();\n\n this.saving = false;\n return outcome;\n }\n\n private startPathWatch(): void {\n if (this.unwatchRoutes !== null || !this.watchRoutes) return;\n this.lastPathname = this.getPathname();\n // 감시자가 흘리는 경로를 그대로 쓰지 않고 `getPathname()` 을 다시 묻는다 — 호스트가\n // 경로 기준을 바꿔 끼웠을 수 있고(마커는 그 기준으로 저장돼 있다), 구독 즉시 오는\n // 첫 통지가 지금 경로라 이 비교 덕에 아무 일도 일어나지 않는다.\n this.unwatchRoutes = this.watchRoutes(() => {\n const pathname = this.getPathname();\n if (pathname !== this.lastPathname) this.syncPath();\n });\n }\n\n private stopPathWatch(): void {\n if (this.unwatchRoutes === null) return;\n this.unwatchRoutes();\n this.unwatchRoutes = null;\n }\n\n /** 전역 pending 수가 아니라 마커와 같은 clientSubmissionId의 확정 성공만 완료로 바꾼다. */\n private reconcileMarkerOutcomes(): boolean {\n if (!this.queue.getOutcome) return false;\n let changed = false;\n for (const marker of this.markers) {\n const outcome = this.queue.getOutcome(marker.id);\n if (marker.status !== \"done\" && outcome?.delivered) {\n this.markers = this.store.updateStatus(this.lastPathname, marker.id, \"done\");\n changed = true;\n }\n }\n return changed;\n }\n\n private emit(): void {\n const state = this.getState();\n for (const listener of this.listeners) listener(state);\n }\n}\n","// 웹 경로 감시자.\n//\n// SPA 는 페이지를 다시 로드하지 않고 화면을 바꾼다. 그래서 \"지금 어느 화면인가\"를 알려면\n// `history.pushState`/`replaceState` 와 `popstate`(뒤로/앞으로) 를 **함께** 봐야 한다.\n// 셋 중 하나라도 빠지면 그 방식으로 이동한 화면을 통째로 놓친다.\n//\n// **이 감시자는 한 곳이다.** 화면 이동 궤적(진단)과 요소 지목 모드의 경로 추적이 같은 것을 쓴다.\n// 따로 구현하면 한쪽이 이동을 놓쳤을 때 \"감시가 안 붙었나 / 이 경로만 다르게 도나\"를 가릴 수\n// 없다. 실제로 지목 쪽은 200ms 폴링이었어서, 이동 직후 200ms 안에 벌어진 일은 옛 경로로 찍혔다.\n//\n// 코어는 이 파일을 모른다(`window` 를 보므로 웹 전용이다). 코어에는 구독 함수 하나\n// (`RouteWatcher`)만 주입된다.\n\nimport type { RouteTrailStore, RouteWatcher } from \"@solhun/feedback-kit-core\";\n\n/** 감시자가 들여다보는 전역. 테스트에서는 가짜를 넣는다. */\nexport interface RouteWatchTarget {\n location?: { pathname?: string } | null;\n history?: {\n pushState?: (...args: unknown[]) => unknown;\n replaceState?: (...args: unknown[]) => unknown;\n } | null;\n addEventListener?: (type: string, listener: () => void) => void;\n removeEventListener?: (type: string, listener: () => void) => void;\n}\n\nfunction defaultTarget(): RouteWatchTarget | null {\n const g = globalThis as RouteWatchTarget;\n // SSR·비브라우저에서는 감시할 게 없다. null 을 돌려주면 구독은 성립하되 아무것도 안 흐른다\n // (= 궤적만 비고 나머지 수집은 그대로) — 여기서 던지면 서버 렌더가 깨진다.\n return g.location ? g : null;\n}\n\n/**\n * 경로 감시자 하나를 만든다.\n *\n * 돌려주는 것은 **구독 함수**다. 구독하면 현재 경로를 한 번 흘리고(마운트 시점의 화면이\n * 궤적의 첫 줄이 된다), 이후 이동마다 새 경로를 흘린다. 해제 함수를 부르면 구독이 끊긴다.\n *\n * `history` 패치는 **첫 구독자가 생길 때 걸고 마지막이 나가면 푼다.** 구독자가 없는데 패치가\n * 남아 있으면, 위젯을 떼어낸 뒤에도 호스트의 모든 화면 이동이 우리 코드를 거친다.\n */\nexport function createRouteWatcher(target: RouteWatchTarget | null = defaultTarget()): RouteWatcher {\n const listeners = new Set<(path: string) => void>();\n let detach: (() => void) | null = null;\n\n const currentPath = (): string => {\n const p = target?.location?.pathname;\n return typeof p === \"string\" && p.length > 0 ? p : \"/\";\n };\n\n const emit = (): void => {\n const path = currentPath();\n // 사본을 돌린다 — 구독자가 콜백 안에서 해제하면 순회 중 집합이 바뀐다.\n for (const listener of [...listeners]) {\n try {\n listener(path);\n } catch {\n // 한 구독자가 던져도 나머지는 받아야 한다. 이동 기록이 남의 예외로 멈추면 안 된다.\n }\n }\n };\n\n const attach = (): void => {\n if (detach !== null || !target) return;\n\n const onPop = (): void => emit();\n target.addEventListener?.(\"popstate\", onPop);\n\n const history = target.history;\n const originalPush = typeof history?.pushState === \"function\" ? history.pushState : null;\n const originalReplace = typeof history?.replaceState === \"function\" ? history.replaceState : null;\n\n if (history && originalPush) {\n history.pushState = function patched(this: unknown, ...args: unknown[]): unknown {\n // 원본을 **먼저** 부른다. 그래야 emit 시점의 location.pathname 이 새 경로다.\n const out = originalPush.apply(this, args);\n emit();\n return out;\n };\n }\n if (history && originalReplace) {\n history.replaceState = function patched(this: unknown, ...args: unknown[]): unknown {\n const out = originalReplace.apply(this, args);\n emit();\n return out;\n };\n }\n\n detach = (): void => {\n target.removeEventListener?.(\"popstate\", onPop);\n if (history && originalPush) history.pushState = originalPush;\n if (history && originalReplace) history.replaceState = originalReplace;\n };\n };\n\n return (onPath: (path: string) => void): (() => void) => {\n listeners.add(onPath);\n if (listeners.size === 1) attach();\n\n // 구독 즉시 현재 경로를 흘린다 — 이게 없으면 궤적이 \"두 번째 화면\"부터 시작해서\n // 정작 어디서 출발했는지가 빈다.\n try {\n onPath(currentPath());\n } catch {\n // 첫 통지가 실패해도 구독 자체는 유지한다.\n }\n\n return (): void => {\n listeners.delete(onPath);\n if (listeners.size === 0 && detach) {\n detach();\n detach = null;\n }\n };\n };\n}\n\n/**\n * 웹 기본 감시자. **모듈 스코프에 하나**라서 궤적과 지목이 같은 인스턴스를 물려 쓴다.\n * 각자 `createRouteWatcher()` 를 부르면 `history` 가 두 겹으로 패치돼, 해제 순서가 엇갈릴 때\n * 원본이 복구되지 않는다.\n */\nexport const webRouteWatcher: RouteWatcher = createRouteWatcher();\n\n/**\n * 궤적을 남길 탭 단위 저장소(`sessionStorage`).\n *\n * `localStorage` 가 아닌 이유: 궤적은 **이 탭에서 지금 지나온 길**이다. 탭을 나눠 쓰면\n * 다른 탭의 이동이 섞여 재현 절차가 거짓이 된다.\n *\n * 프라이빗 모드·차단 설정에서는 접근 자체가 던진다. 그때는 `null` 을 돌려주고 궤적은\n * 메모리로 떨어진다 — 저장을 못 한다고 제보를 막지 않는다.\n */\nexport function webRouteStorage(): RouteTrailStore | null {\n try {\n const s = (globalThis as { sessionStorage?: RouteTrailStore | null }).sessionStorage;\n if (!s || typeof s.getItem !== \"function\" || typeof s.setItem !== \"function\") return null;\n // 있다고 쓸 수 있는 건 아니다(사파리 프라이빗 모드는 setItem 에서 던진다). 한 번 찔러 본다.\n s.getItem(\"feedback-kit:probe\");\n return s;\n } catch {\n return null;\n }\n}\n","import type { ScreenshotCapture, ScreenshotReencode } from \"@solhun/feedback-kit-core\";\nimport html2canvas from \"html2canvas-pro\";\n\nimport { OWN_UI_ATTR } from \"./picking.js\";\n\n/**\n * 왜 라이브러리를 쓰나 (직접 만든 SVG foreignObject 방식에서 갈아탄 이유):\n *\n * `<foreignObject>` 로 DOM 을 감싼 SVG 를 캔버스에 그리면 **캔버스가 tainted 되어**\n * `toDataURL()` 이 `SecurityError` 를 던진다. 즉 캡처가 항상 null 이 되고, 사용자는\n * 매번 \"캡처 실패 — 파일로 첨부할 수 있습니다\"만 본다. 실측으로 확인했다:\n * 같은 브라우저에서 일반 SVG(rect)는 오염되지 않고 `foreignObject` 만 오염된다.\n *\n * html2canvas-pro(MIT)는 foreignObject 를 쓰지 않는다. DOM 을 순회하며 캔버스에\n * 직접 그리므로 오염 자체가 생기지 않는다. 명세의 \"DOM 을 이미지로 변환하는 MIT\n * 라이브러리를 쓴다\"도 원래 이 뜻이었다.\n */\n\nfunction payload(dataUrl: string): string | null {\n const comma = dataUrl.indexOf(\",\");\n return comma >= 0 ? dataUrl.slice(comma + 1) : null;\n}\n\n/** 캡처에서 빼야 할 노드인가(위젯 자신의 UI). 결과에 자기 모달이 찍히면 안 된다. */\nexport function isOwnUi(node: Element): boolean {\n return node.hasAttribute?.(OWN_UI_ATTR) === true\n || node.closest?.(`[${OWN_UI_ATTR}]`) !== null;\n}\n\n/**\n * 웹 기본 캡처 — 현재 viewport 를 JPEG 로.\n *\n * 실패하면 **null 을 돌려준다**(던지지 않는다). 코어가 \"캡처 실패\" 상태로 바꾸고\n * 사용자는 파일로 첨부할 수 있다 — 그림 때문에 제보 자체를 잃지 않는다.\n */\n/**\n * 캔버스를 같은 크기의 <img> 로 바꾼다.\n *\n * html2canvas 는 <canvas> 의 픽셀은 가져오지만 **배치를 잘못 잡는 경우가 있다.**\n * 실측: KITS 홈의 Rive 캔버스(700×933, 왼쪽 x=0)가 캡처본에서는 가운데로 옮겨져\n * 본문 텍스트와 버튼을 덮었다. 원본 화면과 캡처를 나란히 놓고서야 보였다.\n *\n * 캔버스를 이미지로 바꾸면 일반 요소로 배치되므로 위치가 어긋나지 않는다.\n * 픽셀을 못 읽는 캔버스(WebGL 에서 preserveDrawingBuffer 미설정, 오염 등)는\n * 원본을 그대로 둔다 — 자리를 비우는 것보다 어긋나더라도 뭔가 보이는 편이 낫다.\n */\nfunction freezeCanvases(source: Document, clone: Document): void {\n const originals = source.querySelectorAll(\"canvas\");\n const clones = clone.querySelectorAll(\"canvas\");\n for (let index = 0; index < originals.length && index < clones.length; index++) {\n const from = originals[index];\n const to = clones[index];\n let dataUrl: string;\n try {\n dataUrl = from.toDataURL(\"image/png\");\n } catch {\n continue;\n }\n if (!dataUrl.startsWith(\"data:image\") || dataUrl.length < 64) continue;\n\n const box = from.getBoundingClientRect();\n if (box.width < 1 || box.height < 1) continue;\n\n const image = clone.createElement(\"img\");\n image.src = dataUrl;\n // 원래 캔버스가 받던 레이아웃을 그대로 물려받게 한다.\n image.setAttribute(\"style\", to.getAttribute(\"style\") ?? \"\");\n image.style.width = `${box.width}px`;\n image.style.height = `${box.height}px`;\n image.className = to.className;\n to.replaceWith(image);\n }\n}\n\nexport const captureWebScreenshot: ScreenshotCapture = async () => {\n const doc = globalThis.document;\n const view = doc?.defaultView;\n if (!doc?.documentElement || !view) return null;\n\n try {\n const canvas = await html2canvas(doc.documentElement, {\n // 현재 보이는 화면만. 문서 전체를 그리면 제보와 무관한 영역까지 커진다.\n width: view.innerWidth,\n height: view.innerHeight,\n x: view.scrollX,\n y: view.scrollY,\n windowWidth: view.innerWidth,\n windowHeight: view.innerHeight,\n // 배율은 2배까지만. 그 위는 용량만 커지고 8 MiB 한도에 먼저 걸린다.\n scale: Math.min(2, Math.max(1, view.devicePixelRatio || 1)),\n // 위젯 자신의 UI 는 결과에서 뺀다.\n ignoreElements: isOwnUi,\n // 캔버스(Rive·차트 등)는 이미지로 바꿔 넘긴다 — freezeCanvases 주석 참고.\n onclone: (cloned: Document) => freezeCanvases(doc, cloned),\n // 외부 이미지가 CORS 를 안 열어두면 그것 때문에 전체가 실패할 수 있다.\n // 못 가져오는 리소스는 건너뛰고 나머지를 그린다.\n useCORS: true,\n allowTaint: false,\n logging: false,\n backgroundColor: null,\n });\n return { base64: payload(canvas.toDataURL(\"image/jpeg\", 0.9)) ?? \"\", contentType: \"image/jpeg\" };\n } catch {\n return null;\n }\n};\n\n/** 8 MiB 정책이 요구할 때 기존 이미지를 JPEG 품질 단계로 다시 인코딩한다. */\nexport const reencodeWebScreenshot: ScreenshotReencode = async (shot, quality) => {\n const doc = globalThis.document;\n if (!doc) return null;\n try {\n const image = await new Promise<HTMLImageElement | null>((resolve) => {\n const img = new Image();\n img.onload = () => resolve(img);\n img.onerror = () => resolve(null);\n img.src = `data:${shot.contentType};base64,${shot.base64}`;\n });\n if (!image) return null;\n const canvas = doc.createElement(\"canvas\");\n canvas.width = image.naturalWidth || image.width;\n canvas.height = image.naturalHeight || image.height;\n const context = canvas.getContext(\"2d\");\n if (!context) return null;\n context.drawImage(image, 0, 0);\n const base64 = payload(canvas.toDataURL(\"image/jpeg\", quality));\n return base64 ? { base64, contentType: \"image/jpeg\" } : null;\n } catch {\n return null;\n }\n};\n","// 웹 위젯 조립.\n//\n// 코어의 화면 지도(`WidgetController`)와 웹 전용 지목 모드(`ElementPickingController`)를\n// 한 덩어리로 묶는다. 둘을 따로 두면 \"모달에서 [요소 지목]을 눌렀다\"와 \"지목 오버레이가 떴다\"가\n// 서로 모르는 상태가 되므로, 연결 지점을 여기 한 곳으로 모은다.\n//\n// 지목 모드 on/off 의 유일한 입구는 `widget.startPicking()` / `widget.stopPicking()` 이다.\n// 지목 컨트롤러를 직접 켜면 화면 지도가 그 사실을 모른다.\n\nimport {\n WidgetController,\n HintProvider,\n rankHints,\n type WidgetControllerOpts,\n type ElementInfo,\n type FeedbackAdapter,\n type FeedbackHint,\n type HintRankInput,\n type ModalQueueLike,\n} from \"@solhun/feedback-kit-core\";\nimport { MarkerStore } from \"./marker-store.js\";\nimport { ElementPickingController } from \"./picking.js\";\nimport { captureWebScreenshot, reencodeWebScreenshot } from \"./screenshot.js\";\nimport { createWebStorage } from \"./storage.js\";\n\n/**\n * 요소를 고른 뒤 무엇을 띄우는가.\n *\n * - `popup`(기본): 클릭한 자리에 **한 줄 입력창**. Enter 로 바로 보낸다.\n * - `modal`: 화면 중앙의 전체 리포트 모달(우선순위까지 고른다).\n *\n * 기본이 팝업인 이유: 지목은 \"여기 이거요\" 를 여러 곳에 연달아 남기는 작업이다.\n * 클릭마다 중앙 모달이 뜨면 시선이 클릭 지점을 떠나고, 어디를 가리켰는지 잊는다.\n */\nexport type PickTarget = \"popup\" | \"modal\";\n\nexport interface WebWidgetOpts\n extends Omit<WidgetControllerOpts, \"platform\" | \"initialPicking\" | \"onPickingChange\"> {\n /** 지목 모드 플래그와 마커를 담아 둘 저장소. 기본은 `localStorage`. */\n store?: MarkerStore;\n /** 현재 경로. 마커를 경로별로 나눠 담는 기준. */\n getPathname?: () => string;\n /** 지목 이벤트를 들을 문서. 기본은 전역 `document`. */\n doc?: Document | null;\n getViewport?: () => { width: number; height: number };\n /** 요소를 고른 뒤 띄울 UI. 기본은 `popup`. */\n pickTarget?: PickTarget;\n /**\n * 정적 힌트 목록(설정으로 직접 넘김). 주면 서버에서 받아오지 않는다 — 도입 초기·오프라인\n * 데모·테스트용이다. 서버 목록과 합치지 않는다(둘을 섞으면 무엇이 보이는지 설명이 안 된다).\n */\n hints?: readonly FeedbackHint[];\n /** 힌트 조회 컨트롤러 주입(테스트용). 주면 큐 어댑터의 fetchHints 를 보지 않는다. */\n hintProvider?: HintProvider;\n}\n\nexport interface WebWidget {\n widget: WidgetController;\n picking: ElementPickingController;\n store: MarkerStore;\n dispose(): void;\n}\n\n/** 경로에서 쿼리스트링·해시를 뺀다. 같은 화면이 쿼리 값 때문에 다른 화면으로 갈리지 않게 한다. */\nfunction stripQueryHash(path: string): string {\n const cut = [path.indexOf(\"?\"), path.indexOf(\"#\")].filter((i) => i >= 0);\n return cut.length > 0 ? path.slice(0, Math.min(...cut)) : path;\n}\n\nfunction defaultPathname(): string {\n const loc = (globalThis as { location?: { pathname?: string } }).location;\n return loc?.pathname ?? \"/\";\n}\n\n/**\n * 큐가 `adapter` 를 노출하면 그 힌트 조회 함수를 꺼낸다.\n *\n * `queue` 타입은 `ModalQueueLike`(제출 최소 계약)라 `adapter` 가 없다 — 실제로 오는 건\n * `FeedbackQueue` 인스턴스이고 그쪽엔 `readonly adapter` 가 공개돼 있어 좁혀서 읽는다.\n * 테스트 대역처럼 `adapter` 가 없는 큐가 와도 힌트 기능만 조용히 꺼질 뿐 제보 전송에는\n * 영향이 없다.\n */\nfunction hintSourceFrom(\n queue: ModalQueueLike\n): { fetchHints: NonNullable<FeedbackAdapter[\"fetchHints\"]>; cacheKey: string } | null {\n const adapter = (queue as ModalQueueLike & { adapter?: FeedbackAdapter }).adapter;\n if (!adapter || typeof adapter.fetchHints !== \"function\") return null;\n const cacheKey = adapter.hintsCacheKey ?? \"default\";\n return { fetchHints: adapter.fetchHints.bind(adapter), cacheKey };\n}\n\n/** 정적 목록 > 테스트 주입 > 큐 어댑터 순으로 힌트 공급자를 고른다. 셋 다 없으면 기능이 꺼진다. */\nfunction resolveHintProvider(opts: WebWidgetOpts): HintProvider | null {\n if (opts.hintProvider) return opts.hintProvider;\n if (opts.hints) {\n // 정적 목록은 cacheKey 가 뜻이 없다(저장소·네트워크를 아예 안 본다) — 값만 채운다.\n return new HintProvider({ source: null, staticHints: opts.hints, cacheKey: \"static\" });\n }\n const found = hintSourceFrom(opts.queue);\n if (!found) return null;\n return new HintProvider({\n source: { fetchHints: (etag) => found.fetchHints(etag) },\n storage: createWebStorage(),\n cacheKey: found.cacheKey,\n });\n}\n\n/**\n * 클릭 지점의 퀵 입력창에서 보여줄 칩 개수 상한. 리포트 모달(기본 4, 최대 6)보다 좁게 잡는다 —\n * 팝업은 클릭 지점 옆의 332px짜리 카드라서, 카탈로그의 maxDisplay 를 그대로 쓰면 줄바꿈이\n * 심해져 정작 코멘트란이 화면 밖으로 밀린다. 카탈로그 쪽 maxDisplay 가 이보다 작으면 그 값을\n * 따른다(rankFor 참고) — 여기서는 상한만 정한다.\n */\nconst POPUP_HINT_DISPLAY_MAX = 3;\n\n/**\n * 이 시점의 카탈로그로 랭킹한다. 모달을 열 때와 퀵 팝업을 열 때 **같은 계산**을 쓰기 위한\n * 한 곳 — 따로 구현하면 두 문이 다른 순위를 보여주는 사고가 난다.\n * @param limitOverride 팝업처럼 화면이 좁아 더 적게 보여줘야 할 때만 준다. 카탈로그의\n * `maxDisplay` 보다 크게는 못 넘는다(프로젝트가 정한 상한이 우선).\n */\nfunction rankFor(\n hintProvider: HintProvider,\n opts: WebWidgetOpts,\n element: ElementInfo | null,\n limitOverride?: number\n): readonly FeedbackHint[] {\n const catalog = hintProvider.catalog();\n if (!catalog) return [];\n const input: HintRankInput = {\n platform: \"web\",\n path: stripQueryHash(opts.getPathname ? opts.getPathname() : defaultPathname()),\n element,\n components: element ? (element.attributes[\"react-components\"] ?? null) : null,\n };\n const limit = limitOverride !== undefined ? Math.min(limitOverride, catalog.maxDisplay) : catalog.maxDisplay;\n return rankHints(catalog.hints, input, limit);\n}\n\nexport function createWebWidget(opts: WebWidgetOpts): WebWidget {\n const store = opts.store ?? new MarkerStore();\n // widget 이 아래에서 만들어지므로, 콜백이 불릴 때 참조할 수 있게 늦게 채운다.\n let widgetRef: WidgetController | null = null;\n const capture = opts.capture === undefined ? captureWebScreenshot : opts.capture;\n const reencode = opts.reencode === undefined ? reencodeWebScreenshot : opts.reencode;\n\n // 힌트(자주 나오는 제보 제안). 셋 다 없으면(정적 목록도, 테스트 주입도, 큐 어댑터도)\n // 기능이 조용히 꺼진다 — 제안은 거들 뿐이라 이것 때문에 위젯이 막히면 안 된다.\n // 리포트 모달과 퀵 팝업 **둘 다 이 인스턴스 하나를 물려 쓴다** — 같은 카탈로그, 같은\n // rankFor 계산. 따로 만들면 두 문이 다른 제안을 보여주는 사고가 난다.\n const hintProvider = resolveHintProvider(opts);\n if (hintProvider) {\n // 마운트 후 캐시 복원 → 필요하면 네트워크. await 로 붙잡지 않는다 — 모달 열기를 막지 않는다.\n void hintProvider.load().then(() => hintProvider.refresh());\n }\n\n const picking = new ElementPickingController({\n queue: opts.queue,\n createReport: opts.createReport,\n store,\n getPathname: opts.getPathname,\n doc: opts.doc,\n getViewport: opts.getViewport,\n // 퀵 주석도 같은 캡처를 쓴다. 캡처가 위젯 자신의 UI 를 빼주므로 팝업이 찍히지 않는다.\n capture,\n reencode,\n screenshotLimitBytes: opts.screenshotLimitBytes,\n // 기본은 클릭 자리의 퀵 입력창이라 onPick 을 주지 않는다(주면 팝업 대신 그걸 부른다).\n // 모드는 어느 쪽이든 켜진 채로 둬서 한 번 켜고 여러 요소를 연달아 지목할 수 있다.\n onPick: opts.pickTarget === \"modal\"\n ? (element, point) => {\n // modal.open() 을 직접 부르지 않는다 — 그러면 모달 상태만 열리고 화면은\n // picking 에 머물러 아무것도 안 보인다. 화면 전환은 컨트롤러가 한다.\n // 좌표는 **열기 인자로** 넘긴다. 열고 난 뒤 setPin 으로 넣으면 뒤늦게 끝나는\n // 자동 캡처가 그것을 지운다(그래서 지목 좌표가 전부 유실됐다).\n void widgetRef?.openReport({ element, pin: point });\n }\n : undefined,\n rankHintsFor: hintProvider\n ? (element) => rankFor(hintProvider, opts, element, POPUP_HINT_DISPLAY_MAX)\n : null,\n });\n\n const widget = new WidgetController({\n ...opts,\n capture,\n reencode,\n platform: \"web\",\n // 새로고침·페이지 이동 직후에도 켜져 있던 모드를 그대로 이어받는다.\n initialPicking: store.isPickingActive(),\n onPickingChange: (active) => {\n if (active) picking.start();\n else picking.stop();\n },\n });\n\n widgetRef = widget;\n\n // 모달을 여는 모든 경로(플로팅 버튼·지목→모달)가 이 한 곳을 거친다. 열릴 때마다 그 시점의\n // 경로·요소 기준으로 다시 고른다 — 지목 대상을 바꾸면 제안도 바뀌어야 한다(계약 원문).\n const originalOpenReport = widget.openReport.bind(widget);\n widget.openReport = async (openOpts = {}) => {\n if (hintProvider) {\n widget.modal.setHints(rankFor(hintProvider, opts, openOpts.element ?? null));\n }\n await originalOpenReport(openOpts);\n };\n\n // 모달로 보낸 지목 제보도 마커로 남긴다. 마커 생성이 옛 팝업 저장 경로에만 있으면\n // 모달로 보낸 제보는 화면에 흔적이 없다 — \"보냈는지 알 수 없다\"가 이 모드가\n // 없애려던 불편이라, 그게 되살아난다.\n const originalSubmit = widget.submitReport.bind(widget);\n widget.submitReport = async () => {\n const outcome = await originalSubmit();\n const report = widget.modal.getLastReport();\n if (report?.element) picking.noteSubmitted(report, outcome?.delivered === true);\n return outcome;\n };\n\n // 저장돼 있던 모드면 여기서 DOM 리스너까지 다시 붙인다. 꺼져 있으면 마커만 읽고 만다.\n picking.restore();\n\n return {\n widget,\n picking,\n store,\n dispose(): void {\n picking.dispose();\n widget.dispose();\n },\n };\n}\n","// 웹 기본 저장소 — `localStorage` 를 FeedbackStorage 계약에 맞춘다.\n//\n// 왜 패키지가 제공하나: 이게 없으면 붙이는 앱마다 같은 20줄을 다시 쓴다.\n// 코어는 저장소 구현을 모르고(플랫폼 무관), 웹 패키지가 기본값을 준다.\n//\n// localStorage 는 동기지만 계약은 비동기라 Promise 로 감싼다. 사파리 프라이빗\n// 모드처럼 접근 자체가 던지는 환경이 있어서, 실패하면 **메모리로 조용히 물러난다**\n// — 제보를 잃는 것보다 세션 동안만 유지되는 편이 낫다.\n\nimport type { FeedbackStorage } from \"@solhun/feedback-kit-core\";\n\ninterface SyncStorage {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n}\n\nfunction memoryStorage(): SyncStorage {\n const map = new Map<string, string>();\n return {\n getItem: (key) => map.get(key) ?? null,\n setItem: (key, value) => void map.set(key, value),\n removeItem: (key) => void map.delete(key),\n };\n}\n\n/** 접근 가능한 localStorage 를 고른다. 못 쓰면 메모리. */\nfunction pickBacking(): SyncStorage {\n try {\n const ls = (globalThis as { localStorage?: SyncStorage }).localStorage;\n if (!ls) return memoryStorage();\n // 존재만으로는 부족하다 — 프라이빗 모드는 쓰기에서 던진다.\n const probe = \"__feedback_kit_probe__\";\n ls.setItem(probe, \"1\");\n ls.removeItem(probe);\n return ls;\n } catch {\n return memoryStorage();\n }\n}\n\n/**\n * 웹 기본 FeedbackStorage.\n *\n * 쓰기가 쿼터를 넘기면 그대로 던진다 — 큐가 그 신호를 받아 스크린샷을 떼어내고\n * 본문만 남기는 축소 경로를 타기 때문에, 여기서 삼키면 안 된다.\n */\nexport function createWebStorage(backing?: SyncStorage): FeedbackStorage {\n const store = backing ?? pickBacking();\n return {\n async get(key) {\n try {\n return store.getItem(key);\n } catch {\n return null;\n }\n },\n async set(key, value) {\n store.setItem(key, value);\n },\n async remove(key) {\n try {\n store.removeItem(key);\n } catch {\n /* 지우기 실패는 다음 쓰기가 덮으므로 무시한다. */\n }\n },\n };\n}\n","import {\n COMMENT_MAX_CHARS,\n FLOATING_BUTTON_ID,\n MODAL_ACTION_CANCEL,\n MODAL_ACTION_HINT_TOGGLE,\n MODAL_ACTION_PICK,\n MODAL_ACTION_REMOVE_SCREENSHOT,\n MODAL_ACTION_RETRY,\n MODAL_ACTION_SEND,\n MODAL_FIELD_COMMENT,\n MODAL_FIELD_PRIORITY,\n type FeedbackHint,\n type FeedbackPriority,\n type FeedbackScreenshot,\n type WidgetState,\n} from \"@solhun/feedback-kit-core\";\nimport {\n type ChangeEvent,\n type CSSProperties,\n type FormEvent,\n type KeyboardEvent,\n type MouseEvent,\n useEffect,\n useRef,\n useState,\n} from \"react\";\n\nimport type { PickingState } from \"./picking.js\";\nimport { VERSION } from \"./version.js\";\nimport { OWN_UI_ATTR } from \"./picking.js\";\nimport { createWebWidget, type WebWidget, type WebWidgetOpts } from \"./widget.js\";\n\n/**\n * 패키지를 붙인 제품의 디자인 시스템을 침범하지 않는 중립 토큰이다.\n * 모든 값은 인라인 스타일로만 쓰여 호스트의 전역 CSS와 충돌하지 않는다.\n */\nconst TOKENS = {\n ink: \"#17202a\",\n muted: \"#667085\",\n surface: \"#ffffff\",\n subtle: \"#f4f6f8\",\n line: \"#d7dce2\",\n accent: \"#315d73\",\n accentHover: \"#274b5d\",\n danger: \"#b42318\",\n warning: \"#8a5a00\",\n success: \"#18794e\",\n shadow: \"0 18px 50px rgba(23, 32, 42, 0.2)\",\n radius: \"14px\",\n} as const;\n\nconst OWN_UI_PROPS = { [OWN_UI_ATTR]: \"\" } as Record<string, string>;\nconst FONT_STACK =\n \"ui-sans-serif, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif\";\n\nexport type FeedbackKitProps = WebWidgetOpts;\n\nconst INITIAL_PICKING_STATE: PickingState = {\n active: false,\n paused: false,\n hovered: null,\n popup: null,\n markers: [],\n};\n\nfunction screenshotSource(screenshot: FeedbackScreenshot): string {\n return `data:${screenshot.contentType};base64,${screenshot.base64}`;\n}\n\nfunction readDataUrl(blob: Blob): Promise<string | null> {\n return new Promise((resolve) => {\n const reader = new FileReader();\n reader.onerror = () => resolve(null);\n reader.onload = () => resolve(typeof reader.result === \"string\" ? reader.result : null);\n reader.readAsDataURL(blob);\n });\n}\n\nfunction base64Of(dataUrl: string): string | null {\n const comma = dataUrl.indexOf(\",\");\n return comma >= 0 ? dataUrl.slice(comma + 1) : null;\n}\n\n/**\n * PNG·JPEG 가 아닌 이미지를 JPEG 로 옮긴다(WebP·AVIF·HEIC 등).\n *\n * 왜 필요한가: 수집 쪽이 받는 형식은 PNG·JPEG 둘뿐인데, 사용자가 붙여넣는 그림의\n * 형식은 **고를 수 없다.** 브라우저·OS·캡처 도구마다 다르다. 형식이 다르다고\n * 조용히 무시하면 사용자는 \"붙여넣기가 안 되는\" 것으로 본다.\n */\nasync function transcodeToJpeg(dataUrl: string): Promise<FeedbackScreenshot | null> {\n const image = await new Promise<HTMLImageElement | null>((resolve) => {\n const img = new Image();\n img.onload = () => resolve(img);\n img.onerror = () => resolve(null);\n img.src = dataUrl;\n });\n if (!image) return null;\n const canvas = document.createElement(\"canvas\");\n canvas.width = image.naturalWidth || image.width;\n canvas.height = image.naturalHeight || image.height;\n if (canvas.width < 1 || canvas.height < 1) return null;\n const context = canvas.getContext(\"2d\");\n if (!context) return null;\n context.drawImage(image, 0, 0);\n try {\n const base64 = base64Of(canvas.toDataURL(\"image/jpeg\", 0.9));\n return base64 ? { base64, contentType: \"image/jpeg\" } : null;\n } catch {\n // 오염된 캔버스 등. 그림 하나 때문에 제보를 막지 않는다.\n return null;\n }\n}\n\n/** 파일·클립보드에서 온 이미지를 제보에 실을 형태로 만든다. 이미지가 아니면 null. */\n/**\n * 파일 이름으로 이미지 여부를 본다. **타입이 비어 올 때가 있다** — macOS 파인더에서 파일을\n * 복사해 붙여넣으면 `DataTransferItem.type` 이 빈 문자열로 오는 경우가 있고, 그때\n * `type.startsWith(\"image/\")` 만 보면 멀쩡한 PNG 를 놓친다(2026-08-18 제보).\n */\nfunction looksLikeImageName(name: string): boolean {\n return /\\.(png|jpe?g|gif|webp|bmp|avif|heic|heif)$/i.test(name);\n}\n\nfunction isImageFile(file: File): boolean {\n if (file.type.startsWith(\"image/\")) return true;\n // 타입이 비었거나 일반 바이너리로 오면 이름으로 한 번 더 본다.\n return (file.type === \"\" || file.type === \"application/octet-stream\") && looksLikeImageName(file.name);\n}\n\n/**\n * 실제 콘텐츠 타입. 타입이 비어 오면 **이름의 확장자로 정한다** — 그러지 않으면 멀쩡한\n * PNG 가 transcode 경로로 빠지고, 거기서 canvas 가 없거나 실패하면 통째로 버려진다.\n */\nfunction contentTypeOf(file: Blob): string {\n if (file.type !== \"\") return file.type;\n const name = file instanceof File ? file.name : \"\";\n if (/\\.png$/i.test(name)) return \"image/png\";\n if (/\\.jpe?g$/i.test(name)) return \"image/jpeg\";\n return \"\";\n}\n\nasync function readImageFile(file: Blob): Promise<FeedbackScreenshot | null> {\n const named = file instanceof File ? file : null;\n if (!file.type.startsWith(\"image/\") && !(named !== null && isImageFile(named))) return null;\n const dataUrl = await readDataUrl(file);\n if (!dataUrl) return null;\n const type = contentTypeOf(file);\n if (type === \"image/png\" || type === \"image/jpeg\") {\n const base64 = base64Of(dataUrl);\n return base64 ? { base64, contentType: type } : null;\n }\n return transcodeToJpeg(dataUrl);\n}\n\n/** 붙여넣기·드롭 이벤트에서 첫 이미지를 꺼낸다. 없으면 null(텍스트 붙여넣기 등). */\nfunction firstImageOf(transfer: DataTransfer | null): File | null {\n if (!transfer) return null;\n for (const item of Array.from(transfer.items ?? [])) {\n if (item.kind !== \"file\") continue;\n const file = item.getAsFile();\n if (file && isImageFile(file)) return file;\n }\n for (const file of Array.from(transfer.files ?? [])) {\n if (isImageFile(file)) return file;\n }\n return null;\n}\n\nfunction nextPaint(): Promise<void> {\n return new Promise((resolve) => {\n if (typeof requestAnimationFrame === \"function\") {\n requestAnimationFrame(() => resolve());\n } else {\n setTimeout(resolve, 0);\n }\n });\n}\n\nfunction focusableElements(container: HTMLElement): HTMLElement[] {\n return Array.from(\n container.querySelectorAll<HTMLElement>(\n \"button:not([disabled]), textarea:not([disabled]), select:not([disabled]), input:not([disabled]):not([type='hidden']), [tabindex]:not([tabindex='-1'])\",\n ),\n ).filter((node) => node.getAttribute(\"aria-hidden\") !== \"true\");\n}\n\nfunction trapTab(event: KeyboardEvent<HTMLElement>, container: HTMLElement): void {\n if (event.key !== \"Tab\") return;\n const items = focusableElements(container);\n if (items.length === 0) {\n event.preventDefault();\n return;\n }\n\n const first = items[0]!;\n const last = items[items.length - 1]!;\n const active = document.activeElement;\n if (event.shiftKey && (active === first || !container.contains(active))) {\n event.preventDefault();\n last.focus();\n } else if (!event.shiftKey && (active === last || !container.contains(active))) {\n event.preventDefault();\n first.focus();\n }\n}\n\nconst baseButton: CSSProperties = {\n minHeight: 38,\n border: `1px solid ${TOKENS.line}`,\n borderRadius: 9,\n background: TOKENS.surface,\n color: TOKENS.ink,\n padding: \"8px 12px\",\n font: \"inherit\",\n fontWeight: 650,\n cursor: \"pointer\",\n};\n\nconst primaryButton: CSSProperties = {\n ...baseButton,\n borderColor: TOKENS.accent,\n background: TOKENS.accent,\n color: TOKENS.surface,\n};\n\n/** 화면에서는 감추고 스크린리더에는 남긴다. 퀵 입력창은 라벨을 그릴 자리가 없다. */\nconst visuallyHidden: CSSProperties = {\n position: \"absolute\",\n width: 1,\n height: 1,\n margin: -1,\n padding: 0,\n overflow: \"hidden\",\n clip: \"rect(0 0 0 0)\",\n whiteSpace: \"nowrap\",\n border: 0,\n};\n\nconst inputStyle: CSSProperties = {\n width: \"100%\",\n boxSizing: \"border-box\",\n border: `1px solid ${TOKENS.line}`,\n borderRadius: 9,\n background: TOKENS.surface,\n color: TOKENS.ink,\n padding: \"10px 11px\",\n font: \"inherit\",\n};\n\n/**\n * 요소 지목 모드 스위치.\n *\n * 왜 버튼이 아니라 토글인가: 지목은 **상태**다. 한 번 켜면 제보를 보내도, 모달을 열었다\n * 닫아도, 페이지를 옮겨도 켜진 채로 남는다(`localStorage`). \"요소 지목\" 이라는 실행 버튼과\n * \"지목 종료\" 라는 별개의 버튼으로 나뉘어 있으면 **지금 켜져 있는지가 화면에 안 보인다** —\n * 사용자는 모달을 열 때마다 다시 켜야 하는 것으로 읽는다.\n *\n * 같은 컴포넌트를 두 곳에서 쓴다: 모달 상단(`inline`)과 지목 중 화면 상단(`floating`).\n * 켜고 끄는 자리가 한 종류여야 \"어디서 껐더라\"가 안 생긴다.\n */\nfunction PickToggle({\n active,\n paused,\n disabled,\n variant,\n focusId,\n onToggle,\n}: {\n active: boolean;\n /** 켜져 있지만 클릭을 안 먹는 중. 켬/끔과 다른 세 번째 상태라 글자로도 드러내야 한다. */\n paused?: boolean;\n disabled?: boolean;\n variant: \"inline\" | \"floating\";\n focusId?: string;\n onToggle: () => void;\n}) {\n const floating = variant === \"floating\";\n return (\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={active}\n aria-label=\"요소 지목 모드\"\n data-fk-pick-toggle={active ? \"on\" : \"off\"}\n data-fk-focus-id={focusId}\n disabled={disabled}\n onClick={onToggle}\n style={{\n ...baseButton,\n display: \"flex\",\n alignItems: \"center\",\n gap: 10,\n opacity: disabled ? 0.5 : 1,\n cursor: disabled ? \"not-allowed\" : \"pointer\",\n // 항상 값을 준다. 조건부로 빼면 리렌더 때 shorthand(border)와 충돌한다고 React 가 경고한다.\n borderColor: active ? TOKENS.accent : TOKENS.line,\n ...(floating\n ? {\n // 위치는 감싸는 `PickBar` 가 잡는다 — 옆에 [잠시 멈춤] 이 함께 서야 하는데\n // 토글이 스스로 fixed 면 둘을 나란히 둘 수 없다.\n background: TOKENS.surface,\n }\n : {\n width: \"100%\",\n justifyContent: \"space-between\",\n marginBottom: 16,\n background: active ? \"rgba(49, 93, 115, 0.06)\" : TOKENS.surface,\n }),\n }}\n >\n <span style={{ display: \"inline-flex\", alignItems: \"center\", gap: 7 }}>\n <span aria-hidden=\"true\" style={{ color: active ? TOKENS.accent : TOKENS.muted }}>\n ◎\n </span>\n 요소 지목 모드\n </span>\n <span aria-hidden=\"true\" style={{ display: \"inline-flex\", alignItems: \"center\", gap: 8 }}>\n <span\n style={{\n fontSize: 12,\n fontWeight: 600,\n color: active && !paused ? TOKENS.accent : TOKENS.muted,\n }}\n >\n {active ? (paused ? \"일시중지\" : \"켜짐\") : \"꺼짐\"}\n </span>\n <span\n style={{\n position: \"relative\",\n display: \"inline-block\",\n width: 38,\n height: 22,\n borderRadius: 11,\n background: active ? (paused ? TOKENS.muted : TOKENS.accent) : TOKENS.line,\n }}\n >\n <span\n style={{\n position: \"absolute\",\n top: 3,\n left: active ? 19 : 3,\n width: 16,\n height: 16,\n borderRadius: \"50%\",\n background: TOKENS.surface,\n boxShadow: \"0 1px 3px rgba(23, 32, 42, 0.35)\",\n }}\n />\n </span>\n </span>\n </button>\n );\n}\n\n/**\n * 지목 중 화면 위쪽에 뜨는 막대. 토글 하나만 있던 자리에 [잠시 멈춤]과 [표시 지우기]를\n * 나란히 둔다.\n *\n * 왜 끄기와 멈추기를 나누나: 지목 모드는 페이지의 모든 클릭을 먹는다. 그래서 \"드롭다운을\n * 열고 그 안을 지목한다\" 같은 일을 하려면 모드를 꺼야 했고, 끄면 다시 켜야 했다. 사용자\n * 눈에는 그게 **모드가 유지되지 않는 것**으로 보인다. 멈춤은 그 왕복을 없앤다 — 페이지를\n * 원래대로 만지고 나서 한 번 더 누르면 그 자리에서 이어진다.\n *\n * [표시 지우기]는 보낸 뒤의 문제를 푼다. 마커는 저장돼서 새로고침에도 살아남는데(그게 목적\n * 이다) 치울 수단이 없어서, 한 번 보내고 나면 [완료] 배지가 그 화면에 영영 붙어 있었다.\n * 여기 두는 이유는 지목 중에만 보이는 것이라 지우는 손잡이도 같은 자리에 있어야 하고,\n * 지울 게 없을 땐 아예 안 보여야 \"이건 뭘 지우지\"가 안 생기기 때문이다.\n */\nfunction PickBar({\n paused,\n markerCount,\n onToggle,\n onTogglePause,\n onClearMarkers,\n}: {\n paused: boolean;\n /** 이 화면에 남아 있는 마커 수. 0 이면 [표시 지우기]를 렌더하지 않는다. */\n markerCount: number;\n onToggle: () => void;\n onTogglePause: () => void;\n onClearMarkers: () => void;\n}) {\n return (\n <div\n style={{\n position: \"fixed\",\n top: 16,\n // `left:50% + translateX(-50%)` 로 가운데를 잡으면 **쓸 수 있는 폭이 화면의 절반**이\n // 된다(transform 은 레이아웃 계산에 안 들어간다). 버튼이 셋이 되면서 그 절반에\n // 안 맞아 좁은 화면에서 버튼이 한 줄에 하나씩 세로로 쌓였다(390px 에서 3줄).\n // 양끝을 물리고 auto 마진으로 가운데를 잡으면 화면 전체 폭을 쓴다 — 같은 화면이 2줄.\n left: 0,\n right: 0,\n marginInline: \"auto\",\n width: \"fit-content\",\n maxWidth: \"calc(100% - 32px)\",\n display: \"flex\",\n alignItems: \"stretch\",\n justifyContent: \"center\",\n // 셋이 한 줄에 안 들어가는 폭에서는 줄을 바꾼다 — 안 그러면 화면 밖으로 나간다.\n flexWrap: \"wrap\",\n gap: 8,\n // 오버레이 자체는 클릭을 통과시킨다(pointerEvents:none) — 이 막대만 되살린다.\n pointerEvents: \"auto\",\n padding: 6,\n borderRadius: 12,\n background: TOKENS.surface,\n boxShadow: TOKENS.shadow,\n }}\n >\n <PickToggle variant=\"floating\" active paused={paused} onToggle={onToggle} />\n <button\n type=\"button\"\n aria-pressed={paused}\n data-fk-pick-pause={paused ? \"paused\" : \"running\"}\n onClick={onTogglePause}\n style={{\n ...baseButton,\n display: \"inline-flex\",\n alignItems: \"center\",\n gap: 6,\n whiteSpace: \"nowrap\",\n borderColor: paused ? TOKENS.accent : TOKENS.line,\n color: paused ? TOKENS.accent : TOKENS.ink,\n fontWeight: 600,\n }}\n >\n <span aria-hidden=\"true\">{paused ? \"▶\" : \"⏸\"}</span>\n {paused ? \"지목 재개\" : \"잠시 멈춤\"}\n </button>\n {markerCount > 0 ? (\n <button\n type=\"button\"\n data-fk-pick-clear={markerCount}\n // 보낸 제보까지 취소되는 걸로 읽히면 아무도 못 누른다 — 무엇이 사라지는지 말해 준다.\n title=\"화면의 표시만 지웁니다. 보낸 제보는 그대로 남습니다.\"\n aria-label={`이 화면의 주석 표시 ${markerCount}개 지우기 (보낸 제보는 그대로 남습니다)`}\n onClick={onClearMarkers}\n style={{\n ...baseButton,\n display: \"inline-flex\",\n alignItems: \"center\",\n gap: 6,\n whiteSpace: \"nowrap\",\n color: TOKENS.muted,\n }}\n >\n <span aria-hidden=\"true\">✕</span>\n 표시 지우기 {markerCount}\n </button>\n ) : null}\n </div>\n );\n}\n\nfunction MarkerStatus({ status }: { status: \"sending\" | \"done\" | \"pending\" }) {\n const presentation =\n status === \"sending\"\n ? { icon: \"↻\", text: \"전송 중\", color: TOKENS.accent }\n : status === \"done\"\n ? { icon: \"✓\", text: \"완료\", color: TOKENS.success }\n : { icon: \"◷\", text: \"대기\", color: TOKENS.warning };\n\n return (\n <span style={{ display: \"inline-flex\", gap: 4, alignItems: \"center\", color: presentation.color }}>\n <span\n aria-hidden=\"true\"\n style={\n status === \"sending\"\n ? { display: \"inline-block\", animation: \"feedback-kit-spin 900ms linear infinite\" }\n : undefined\n }\n >\n {presentation.icon}\n </span>\n <span>{presentation.text}</span>\n </span>\n );\n}\n\n/**\n * 자주 나오는 제보 제안 칩 줄. `hintsAvailable` 이 false 면 아예 렌더하지 않는다 —\n * 눌러도 비어 있는 접힘 버튼을 남기면 고장으로 보인다(계약 원문).\n *\n * 라벨은 JSX 자식으로만 넣는다(`dangerouslySetInnerHTML` 을 쓰지 않는다) — React 는\n * 텍스트 노드를 그대로 이스케이프해서 넣으므로, 서버에서 온 문자열이 `<img>` 같은\n * 마크업이어도 평문으로만 보이고 실제 엘리먼트가 생기지 않는다.\n */\nfunction HintChips({\n hints,\n hintsExpanded,\n usedHintIds,\n disabled,\n onApply,\n onToggle,\n}: {\n hints: readonly FeedbackHint[];\n hintsExpanded: boolean;\n usedHintIds: readonly string[];\n disabled: boolean;\n onApply: (hintId: string) => void;\n onToggle: () => void;\n}) {\n return (\n <div style={{ marginBottom: 16 }}>\n {/*\n 묻는 말 자체가 접기·펴기 버튼이다. 예전엔 \"이런 건가요?\" 제목과 [제안 ▾] 버튼이\n 따로 있었는데, 둘은 같은 것을 두 번 말하는 셈이었다 — 칩 줄을 여닫는 손잡이는\n 하나면 된다.\n */}\n <button\n type=\"button\"\n data-fk-focus-id={MODAL_ACTION_HINT_TOGGLE}\n aria-expanded={hintsExpanded}\n disabled={disabled}\n onClick={onToggle}\n style={{\n display: \"block\",\n margin: \"0 0 7px\",\n padding: 0,\n border: \"none\",\n background: \"none\",\n font: \"inherit\",\n fontSize: 12,\n color: TOKENS.muted,\n cursor: disabled ? \"default\" : \"pointer\",\n textAlign: \"left\",\n }}\n >\n 이런 건가요? <span aria-hidden=\"true\">{hintsExpanded ? \"▴\" : \"▾\"}</span>\n </button>\n {hintsExpanded ? (\n <>\n <div\n role=\"group\"\n aria-label=\"자주 나오는 제보 제안\"\n style={{ display: \"flex\", flexWrap: \"wrap\", gap: 6, marginBottom: 7 }}\n >\n {hints.map((hint) => {\n const used = usedHintIds.includes(hint.id);\n return (\n <button\n key={hint.id}\n type=\"button\"\n data-fk-focus-id={`hint:${hint.id}`}\n disabled={used || disabled}\n aria-pressed={used}\n onClick={() => onApply(hint.id)}\n style={{\n ...baseButton,\n minHeight: 30,\n padding: \"5px 10px\",\n fontSize: 12,\n fontWeight: 600,\n borderRadius: 999,\n ...(used\n ? {\n background: TOKENS.subtle,\n color: TOKENS.muted,\n borderColor: TOKENS.line,\n cursor: \"default\",\n }\n : {}),\n }}\n >\n {hint.label}\n </button>\n );\n })}\n </div>\n </>\n ) : null}\n </div>\n );\n}\n\n/**\n * `createWebWidget`의 헤드리스 상태를 실제 DOM으로 그리는 공개 React 진입점.\n * 큐·제보 조립·스크린샷 캡처는 모두 props로 주입된 기존 계약을 그대로 사용한다.\n */\nexport function FeedbackKit(props: FeedbackKitProps) {\n const rootRef = useRef<HTMLDivElement | null>(null);\n const floatingButtonRef = useRef<HTMLButtonElement | null>(null);\n const modalRef = useRef<HTMLDivElement | null>(null);\n const confirmRef = useRef<HTMLDivElement | null>(null);\n const fileInputRef = useRef<HTMLInputElement | null>(null);\n const previousScreenRef = useRef<WidgetState[\"screen\"]>(\"button\");\n const previousModalOpenRef = useRef(false);\n const previousConfirmVisibleRef = useRef(false);\n const [kit, setKit] = useState<WebWidget | null>(null);\n const [widgetState, setWidgetState] = useState<WidgetState | null>(null);\n const [pickingState, setPickingState] = useState<PickingState>(INITIAL_PICKING_STATE);\n const [announcement, setAnnouncement] = useState<string | null>(null);\n\n const {\n queue,\n createReport,\n capture,\n reencode,\n screenshotLimitBytes,\n store,\n getPathname,\n doc,\n getViewport,\n pickTarget,\n hints,\n hintProvider,\n } = props;\n\n useEffect(() => {\n const created = createWebWidget({\n queue,\n createReport,\n capture,\n reencode,\n screenshotLimitBytes,\n store,\n getPathname,\n doc,\n getViewport,\n pickTarget,\n hints,\n hintProvider,\n });\n setKit(created);\n const unsubscribeWidget = created.widget.subscribe(setWidgetState);\n const unsubscribePicking = created.picking.subscribe(setPickingState);\n\n return () => {\n unsubscribeWidget();\n unsubscribePicking();\n created.dispose();\n };\n }, [\n queue,\n createReport,\n capture,\n reencode,\n screenshotLimitBytes,\n store,\n getPathname,\n pickTarget,\n hints,\n hintProvider,\n doc,\n getViewport,\n ]);\n\n useEffect(() => {\n if (!widgetState) return;\n const { modal, screen } = widgetState;\n if (modal.submitMessage) setAnnouncement(modal.submitMessage);\n\n if (modal.open && !previousModalOpenRef.current && modal.focused) {\n document.querySelector<HTMLElement>(`[data-fk-focus-id=\"${modal.focused}\"]`)?.focus();\n }\n if (modal.closeConfirmVisible && !previousConfirmVisibleRef.current) confirmRef.current?.focus();\n\n const wasModal = previousScreenRef.current === \"modal\";\n if (wasModal && screen === \"button\" && modal.restoreFocusTo === FLOATING_BUTTON_ID) {\n void nextPaint().then(() => floatingButtonRef.current?.focus());\n }\n previousScreenRef.current = screen;\n previousModalOpenRef.current = modal.open;\n previousConfirmVisibleRef.current = modal.closeConfirmVisible;\n }, [widgetState]);\n\n /**\n * 우리 오버레이가 떠 있는 동안 Escape 를 **우리가 가져간다.**\n *\n * 왜 React 의 onKeyDown 으로는 안 되나: 그건 모달 div 에 붙은 핸들러라 `preventDefault`\n * 를 해도 **네이티브 이벤트는 그대로 document 까지 올라간다.** 호스트 앱이 거기서 듣고\n * 있으면(대부분의 모달 구현이 그렇다) 호스트 모달이 닫힌다.\n *\n * 실측(2026-08-09, cardnews 콘솔): 호스트 모달 위에 우리 모달을 띄우고 Escape 를 누르면\n * **뒤에 있던 호스트 모달이 닫히고 우리 모달이 남았다.** 위에 뜬 쪽이 Escape 를 가져가는\n * 게 상식이고, 지금은 사용자가 우리 모달을 닫으려다 뒤에서 쓰던 폼을 잃는다. z-index 는\n * 우리가 최댓값이라 이겼는데 키보드는 호스트가 먼저 듣고 있어서 졌다.\n *\n * 그래서 document 의 **캡처 단계**에서 잡는다. 캡처는 어떤 버블 리스너보다 먼저 돌아서\n * `stopPropagation` 이 호스트에 닿기 전에 끊는다. 대신 우리 React 핸들러에도 안 가므로\n * 닫기 처리를 여기서 직접 한다.\n *\n * **우리 오버레이가 없을 때는 아무것도 하지 않는다** — 호스트의 Escape 를 상시로 뺏으면\n * 그건 그것대로 남의 앱을 망가뜨리는 것이다.\n */\n useEffect(() => {\n if (!kit || !widgetState) return;\n const { screen, modal } = widgetState;\n // 퀵 주석 팝업도 \"위에 뜬 오버레이\" 다 — 열려 있으면 Escape 는 그것을 닫아야 한다.\n const quickOpen = screen === \"picking\" && pickingState.popup !== null;\n if (screen !== \"modal\" && screen !== \"pin\" && !quickOpen) return;\n const widget = kit.widget;\n\n function onKeyDownCapture(event: globalThis.KeyboardEvent): void {\n if (event.key !== \"Escape\") return;\n // 조합 중(한글 입력 등)의 Escape 는 IME 취소다. 모달을 닫으면 글자를 잃는다.\n if (event.isComposing) return;\n event.stopPropagation();\n event.preventDefault();\n if (quickOpen) kit!.picking.cancelAnnotation();\n else if (screen === \"pin\") widget.cancelPin();\n else if (modal.closeConfirmVisible) widget.cancelCloseReport();\n else widget.closeReport();\n }\n\n document.addEventListener(\"keydown\", onKeyDownCapture, true);\n return () => document.removeEventListener(\"keydown\", onKeyDownCapture, true);\n }, [kit, widgetState, pickingState.popup]);\n\n /**\n * 모달에 이미지 붙여넣기. **document 에 단다** — dialog 루트에 달면 포커스가 모달 안에\n * 있을 때만 동작하는데, 실제 흐름(화면을 찍으러 나갔다 돌아와서 바로 붙여넣기)에서는\n * 포커스가 밖에 있는 경우가 흔하다. 2026-08-18 실측: 모달을 열어도 activeElement 가\n * body 였다.\n *\n * 퀵 팝업이 위에 떠 있으면 그쪽 폼 핸들러가 처리하므로 여기서는 비켜준다.\n */\n useEffect(() => {\n if (!kit || !widgetState?.modal.open) return;\n if (pickingState.popup) return;\n const locked = widgetState.modal.submitStatus === \"sending\" || widgetState.modal.submitStatus === \"pending\";\n if (locked) return;\n\n const controller = kit;\n async function onPaste(event: ClipboardEvent): Promise<void> {\n const file = firstImageOf(event.clipboardData);\n // 이미지가 아니면 손대지 않는다 — 텍스트 붙여넣기는 그대로 코멘트로 가야 한다.\n if (!file) return;\n event.preventDefault();\n const shot = await readImageFile(file);\n if (shot) await controller.widget.modal.attachFile(shot);\n }\n const handler = (event: ClipboardEvent): void => void onPaste(event);\n\n document.addEventListener(\"paste\", handler);\n return () => document.removeEventListener(\"paste\", handler);\n }, [kit, widgetState, pickingState.popup]);\n\n if (!kit || !widgetState) {\n return <div {...OWN_UI_PROPS} data-feedback-kit-loading=\"true\" />;\n }\n\n const activeKit = kit;\n const { modal, screen, pickingActive } = widgetState;\n const draftLocked = modal.submitStatus === \"sending\" || modal.submitStatus === \"pending\";\n\n /**\n * 지목 모드 on/off. 끄는 것은 이 토글 하나뿐이다 — 모달을 닫든 제보를 보내든\n * 페이지를 옮기든 모드는 그대로 남는다.\n */\n function togglePicking(): void {\n if (pickingActive) activeKit.widget.stopPicking();\n else activeKit.widget.startPicking();\n }\n\n async function withOwnUiHidden(action: () => Promise<void>): Promise<void> {\n const root = rootRef.current;\n const previousVisibility = root?.style.visibility ?? \"\";\n if (root) root.style.visibility = \"hidden\";\n try {\n await nextPaint();\n await action();\n } finally {\n if (root) root.style.visibility = previousVisibility;\n }\n }\n\n async function openReport(): Promise<void> {\n setAnnouncement(null);\n await withOwnUiHidden(() => activeKit.widget.openReport());\n }\n\n async function recapture(): Promise<void> {\n await withOwnUiHidden(() => activeKit.widget.modal.recapture());\n }\n\n async function attachFile(event: ChangeEvent<HTMLInputElement>): Promise<void> {\n const input = event.currentTarget;\n const file = input.files?.[0];\n if (file) {\n const screenshot = await readImageFile(file);\n if (screenshot) await activeKit.widget.modal.attachFile(screenshot);\n }\n input.value = \"\";\n }\n\n /** 모달에 이미지를 넣는다(드롭용 — 붙여넣기는 document 리스너가 따로 처리한다). */\n async function attachToModal(transfer: DataTransfer | null): Promise<boolean> {\n const file = firstImageOf(transfer);\n if (!file) return false;\n const screenshot = await readImageFile(file);\n if (!screenshot) return false;\n await activeKit.widget.modal.attachFile(screenshot);\n return true;\n }\n\n /** 퀵 주석 입력창에 이미지를 넣는다(붙여넣기·드롭 공용). 이미지가 없으면 아무것도 안 한다. */\n async function attachToPopup(transfer: DataTransfer | null): Promise<boolean> {\n const file = firstImageOf(transfer);\n if (!file) return false;\n const screenshot = await readImageFile(file);\n if (!screenshot) return false;\n await activeKit.picking.attachAnnotationImage(screenshot);\n return true;\n }\n\n function closeReport(): void {\n activeKit.widget.closeReport();\n }\n\n function handleModalKeyDown(event: KeyboardEvent<HTMLDivElement>): void {\n if (event.key === \"Escape\") {\n event.preventDefault();\n if (modal.closeConfirmVisible) activeKit.widget.cancelCloseReport();\n else closeReport();\n return;\n }\n trapTab(event, modal.closeConfirmVisible ? confirmRef.current ?? event.currentTarget : event.currentTarget);\n }\n\n function handleBackdrop(event: MouseEvent<HTMLDivElement>): void {\n if (event.target === event.currentTarget) closeReport();\n }\n\n function submitReport(event: FormEvent<HTMLFormElement>): void {\n event.preventDefault();\n void activeKit.widget.submitReport();\n }\n\n const floating = screen === \"button\" || screen === \"picking\";\n const hoverBox = pickingState.hovered?.boundingBox;\n const popup = pickingState.popup;\n\n return (\n <div\n ref={rootRef}\n {...OWN_UI_PROPS}\n style={{ fontFamily: FONT_STACK, color: TOKENS.ink, fontSize: 14, lineHeight: 1.45 }}\n >\n <style>{`@keyframes feedback-kit-spin{to{transform:rotate(360deg)}}`}</style>\n\n {floating ? (\n <button\n ref={floatingButtonRef}\n id={FLOATING_BUTTON_ID}\n type=\"button\"\n aria-label=\"피드백 보내기\"\n onClick={() => void openReport()}\n style={{\n ...primaryButton,\n position: \"fixed\",\n right: 24,\n bottom: 24,\n zIndex: 2147483600,\n minWidth: 112,\n minHeight: 46,\n borderRadius: 24,\n boxShadow: TOKENS.shadow,\n }}\n >\n {/*\n 지목 중에도 **누를 수 있어야 한다.** 예전에는 여기서 disabled 였고, 그래서\n \"지목을 켠 채 모달을 여는\" 경로가 아예 막혀 있었다 — 모달을 보려면 모드를\n 먼저 꺼야 했고, 사용자에겐 그게 \"모드가 유지되지 않는다\"로 보였다.\n */}\n 피드백\n </button>\n ) : null}\n\n {announcement && !modal.open ? (\n <div\n role=\"status\"\n aria-live=\"polite\"\n style={{\n position: \"fixed\",\n right: 24,\n bottom: 82,\n zIndex: 2147483601,\n padding: \"10px 14px\",\n borderRadius: 10,\n background: TOKENS.ink,\n color: TOKENS.surface,\n boxShadow: TOKENS.shadow,\n }}\n >\n {announcement}\n </div>\n ) : null}\n\n {screen === \"modal\" && modal.open ? (\n <div\n onMouseDown={handleBackdrop}\n style={{\n position: \"fixed\",\n inset: 0,\n zIndex: 2147483602,\n display: \"grid\",\n placeItems: \"center\",\n padding: 20,\n background: \"rgba(23, 32, 42, 0.48)\",\n }}\n >\n <div\n ref={modalRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"feedback-kit-dialog-title\"\n onKeyDown={handleModalKeyDown}\n onDragOver={(event) => {\n if (!draftLocked && firstImageOf(event.dataTransfer)) event.preventDefault();\n }}\n onDrop={(event) => {\n // 드롭은 놓은 자리가 곧 대상이라 루트에 달아도 된다(붙여넣기와 달리 포커스와 무관).\n if (draftLocked || !firstImageOf(event.dataTransfer)) return;\n event.preventDefault();\n void attachToModal(event.dataTransfer);\n }}\n style={{\n width: \"min(520px, 100%)\",\n maxHeight: \"min(760px, calc(100vh - 40px))\",\n overflowY: \"auto\",\n boxSizing: \"border-box\",\n border: `1px solid ${TOKENS.line}`,\n borderRadius: TOKENS.radius,\n background: TOKENS.surface,\n padding: 22,\n boxShadow: TOKENS.shadow,\n }}\n >\n <form onSubmit={submitReport}>\n <h2\n id=\"feedback-kit-dialog-title\"\n style={{\n margin: \"0 0 4px\",\n fontSize: 20,\n display: \"flex\",\n alignItems: \"baseline\",\n gap: 8,\n }}\n >\n 피드백 보내기\n {/*\n 지금 도는 위젯의 버전. 패키지를 올려도 쓰는 쪽 lockfile 이 안 따라오면\n 옛 코드가 계속 도는데 겉으로는 차이가 안 보인다(0.x 에서 `^0.2.0` 은\n `0.2.1` 을 안 잡아준다). 여기 한 줄이 있으면 모달을 여는 것으로 끝난다.\n */}\n <span\n data-feedback-kit-version={VERSION}\n title=\"feedback-kit 버전\"\n style={{ fontSize: 11, fontWeight: 400, color: TOKENS.muted }}\n >\n v{VERSION}\n </span>\n </h2>\n <p style={{ margin: \"0 0 14px\", color: TOKENS.muted }}>\n 현재 화면의 문제나 의견을 남겨주세요.\n </p>\n\n {/* 모드 스위치는 **맨 위**에 둔다. 아래에 실행 버튼으로 있으면 지금 켜져\n 있는지가 안 보여서, 켜 둔 사람도 매번 다시 켜게 된다. */}\n <PickToggle\n variant=\"inline\"\n active={pickingActive}\n paused={pickingState.paused}\n disabled={draftLocked}\n focusId={MODAL_ACTION_PICK}\n onToggle={togglePicking}\n />\n\n <section aria-labelledby=\"feedback-kit-screenshot-label\" style={{ marginBottom: 16 }}>\n <strong id=\"feedback-kit-screenshot-label\">스크린샷</strong>\n <div\n style={{\n display: \"grid\",\n placeItems: \"center\",\n minHeight: 150,\n marginTop: 7,\n overflow: \"hidden\",\n border: `1px solid ${TOKENS.line}`,\n borderRadius: 10,\n background: TOKENS.subtle,\n }}\n >\n {modal.screenshotStatus === \"capturing\" ? (\n <span role=\"status\">화면 캡처 중…</span>\n ) : modal.screenshot ? (\n <img\n src={screenshotSource(modal.screenshot)}\n alt=\"자동 캡처된 현재 화면 미리보기\"\n style={{ display: \"block\", maxWidth: \"100%\", maxHeight: 260, objectFit: \"contain\" }}\n />\n ) : (\n <span role=\"status\" style={{ padding: 18, color: TOKENS.muted, textAlign: \"center\" }}>\n {modal.screenshotMessage ?? \"스크린샷 없음\"}\n </span>\n )}\n </div>\n <div style={{ display: \"flex\", flexWrap: \"wrap\", gap: 8, marginTop: 8 }}>\n {modal.screenshot ? (\n <button\n type=\"button\"\n data-fk-focus-id={MODAL_ACTION_REMOVE_SCREENSHOT}\n disabled={draftLocked}\n onClick={() => kit.widget.modal.removeScreenshot()}\n style={baseButton}\n >\n 제거\n </button>\n ) : null}\n <button\n type=\"button\"\n disabled={draftLocked || modal.screenshotStatus === \"capturing\"}\n onClick={() => void recapture()}\n style={baseButton}\n >\n 다시 찍기\n </button>\n <button\n type=\"button\"\n disabled={draftLocked}\n onClick={() => fileInputRef.current?.click()}\n style={baseButton}\n >\n {modal.screenshot ? \"파일로 교체\" : \"파일로 첨부\"}\n </button>\n <input\n ref={fileInputRef}\n type=\"file\"\n accept=\"image/png,image/jpeg\"\n tabIndex={-1}\n aria-hidden=\"true\"\n onChange={(event) => void attachFile(event)}\n style={{ display: \"none\" }}\n />\n </div>\n </section>\n\n {modal.hintsAvailable ? (\n <HintChips\n hints={modal.hints}\n hintsExpanded={modal.hintsExpanded}\n usedHintIds={modal.usedHintIds}\n disabled={draftLocked}\n onApply={(hintId) => kit.widget.modal.applyHint(hintId)}\n onToggle={() => kit.widget.modal.toggleHints()}\n />\n ) : null}\n\n <label htmlFor=\"feedback-kit-comment\" style={{ display: \"block\", fontWeight: 700 }}>\n 코멘트 <span aria-hidden=\"true\">*</span>\n </label>\n <textarea\n id=\"feedback-kit-comment\"\n data-fk-focus-id={MODAL_FIELD_COMMENT}\n value={modal.comment}\n disabled={draftLocked}\n aria-required=\"true\"\n aria-invalid={modal.commentError ? \"true\" : undefined}\n aria-describedby={modal.commentError ? \"feedback-kit-comment-error\" : undefined}\n onChange={(event) => kit.widget.modal.setComment(event.currentTarget.value)}\n rows={5}\n placeholder=\"무엇이 불편했는지 알려주세요\"\n style={{ ...inputStyle, marginTop: 7, resize: \"vertical\" }}\n />\n <div style={{ minHeight: 21, marginTop: 3 }}>\n {modal.commentError ? (\n <span id=\"feedback-kit-comment-error\" role=\"alert\" style={{ color: TOKENS.danger }}>\n {modal.commentError}\n </span>\n ) : (\n <span style={{ color: TOKENS.muted }}>{modal.comment.length.toLocaleString()} / 4,000</span>\n )}\n </div>\n\n <label htmlFor=\"feedback-kit-priority\" style={{ display: \"block\", marginTop: 10, fontWeight: 700 }}>\n 우선순위\n </label>\n <select\n id=\"feedback-kit-priority\"\n data-fk-focus-id={MODAL_FIELD_PRIORITY}\n value={modal.priority}\n disabled={draftLocked}\n onChange={(event) =>\n kit.widget.modal.setPriority(event.currentTarget.value as FeedbackPriority)\n }\n style={{ ...inputStyle, marginTop: 7 }}\n >\n <option value=\"unset\">선택 안 함</option>\n <option value=\"normal\">보통</option>\n <option value=\"high\">높음</option>\n <option value=\"urgent\">긴급</option>\n </select>\n\n {modal.submitStatus !== \"idle\" || modal.pending > 0 ? (\n <div\n role=\"status\"\n aria-live=\"polite\"\n style={{\n marginTop: 14,\n padding: 11,\n borderRadius: 9,\n background: TOKENS.subtle,\n color: modal.submitStatus === \"pending\" ? TOKENS.warning : TOKENS.ink,\n }}\n >\n {modal.submitStatus === \"sending\" ? \"전송 중…\" : modal.submitMessage}\n {modal.pending > 0 ? ` · 대기 큐 ${modal.pending}건` : \"\"}\n {modal.showRetry ? (\n <button\n type=\"button\"\n data-fk-focus-id={MODAL_ACTION_RETRY}\n onClick={() => void kit.widget.retryReport()}\n style={{ ...baseButton, marginLeft: 10 }}\n >\n 다시 보내기\n </button>\n ) : null}\n </div>\n ) : null}\n\n <div style={{ display: \"flex\", justifyContent: \"flex-end\", gap: 8, marginTop: 18 }}>\n <button\n type=\"button\"\n data-fk-focus-id={MODAL_ACTION_CANCEL}\n onClick={closeReport}\n style={baseButton}\n >\n 취소\n </button>\n <button\n type=\"submit\"\n data-fk-focus-id={MODAL_ACTION_SEND}\n disabled={!modal.canSubmit}\n style={{\n ...primaryButton,\n opacity: modal.canSubmit ? 1 : 0.5,\n cursor: modal.canSubmit ? \"pointer\" : \"not-allowed\",\n }}\n >\n {modal.submitStatus === \"sending\" ? \"전송 중…\" : \"보내기\"}\n </button>\n </div>\n </form>\n\n {modal.closeConfirmVisible ? (\n <div\n style={{\n position: \"fixed\",\n inset: 0,\n zIndex: 2147483604,\n display: \"grid\",\n placeItems: \"center\",\n padding: 20,\n background: \"rgba(23, 32, 42, 0.58)\",\n }}\n >\n <div\n ref={confirmRef}\n role=\"alertdialog\"\n aria-modal=\"true\"\n aria-labelledby=\"feedback-kit-close-title\"\n tabIndex={-1}\n style={{\n width: \"min(360px, 100%)\",\n boxSizing: \"border-box\",\n borderRadius: 12,\n background: TOKENS.surface,\n padding: 20,\n boxShadow: TOKENS.shadow,\n }}\n >\n <h3 id=\"feedback-kit-close-title\" style={{ margin: \"0 0 6px\", fontSize: 17 }}>\n 작성 중인 내용을 닫을까요?\n </h3>\n <p style={{ margin: \"0 0 16px\", color: TOKENS.muted }}>\n 아직 보내지 않은 코멘트가 있습니다.\n </p>\n <div style={{ display: \"flex\", justifyContent: \"flex-end\", gap: 8 }}>\n <button type=\"button\" onClick={() => kit.widget.cancelCloseReport()} style={baseButton}>\n 계속 쓰기\n </button>\n <button type=\"button\" onClick={() => kit.widget.confirmCloseReport()} style={primaryButton}>\n 닫기\n </button>\n </div>\n </div>\n </div>\n ) : null}\n </div>\n </div>\n ) : null}\n\n {screen === \"picking\" && pickingState.active ? (\n <div\n aria-label=\"요소 지목 오버레이\"\n style={{ position: \"fixed\", inset: 0, zIndex: 2147483599, pointerEvents: \"none\" }}\n >\n {hoverBox ? (\n <div\n aria-hidden=\"true\"\n style={{\n position: \"fixed\",\n left: hoverBox.x,\n top: hoverBox.y,\n width: hoverBox.width,\n height: hoverBox.height,\n boxSizing: \"border-box\",\n border: `2px solid ${TOKENS.accent}`,\n background: \"rgba(49, 93, 115, 0.12)\",\n }}\n />\n ) : null}\n\n {/* 지목 중에는 화면 위쪽에 같은 토글이 상시로 떠 있다. 모달을 열지 않고도\n 끌 수 있고, 무엇보다 **모드가 켜져 있다는 사실 자체가 보인다.**\n 옆의 [잠시 멈춤]은 모드를 끄지 않고 페이지를 잠깐 원래대로 쓰게 한다. */}\n <PickBar\n paused={pickingState.paused}\n markerCount={pickingState.markers.length}\n onToggle={togglePicking}\n onTogglePause={() => activeKit.picking.togglePause()}\n onClearMarkers={() => activeKit.picking.clearMarkers()}\n />\n\n {pickingState.markers.map((marker, index) => (\n <div\n key={marker.id}\n role=\"status\"\n aria-label={`주석 ${index + 1}: ${marker.comment}`}\n style={{\n position: \"fixed\",\n left: `${marker.x * 100}%`,\n top: `${marker.y * 100}%`,\n transform: \"translate(-13px, -13px)\",\n display: \"grid\",\n placeItems: \"center\",\n minWidth: 26,\n height: 26,\n padding: \"0 7px\",\n border: `2px solid ${TOKENS.surface}`,\n borderRadius: 16,\n background: TOKENS.ink,\n color: TOKENS.surface,\n boxShadow: \"0 3px 10px rgba(23, 32, 42, 0.3)\",\n pointerEvents: \"none\",\n fontSize: 11,\n fontWeight: 700,\n }}\n >\n <MarkerStatus status={marker.status} />\n </div>\n ))}\n\n {popup ? (\n <form\n aria-label=\"요소 주석\"\n onSubmit={(event) => {\n event.preventDefault();\n void kit.picking.saveAnnotation();\n }}\n onPaste={(event) => {\n // 이미지를 붙였으면 우리가 가져간다. 텍스트 붙여넣기는 건드리지 않는다.\n if (!firstImageOf(event.clipboardData)) return;\n event.preventDefault();\n void attachToPopup(event.clipboardData);\n }}\n onDragOver={(event) => {\n if (firstImageOf(event.dataTransfer)) event.preventDefault();\n }}\n onDrop={(event) => {\n if (!firstImageOf(event.dataTransfer)) return;\n event.preventDefault();\n void attachToPopup(event.dataTransfer);\n }}\n style={{\n position: \"fixed\",\n // 클릭한 지점 바로 옆에 띄운다. 화면 밖으로 나가지 않게 양쪽을 물린다.\n left: `clamp(8px, calc(${popup.point.x * 100}vw + 14px), calc(100vw - 340px))`,\n top: `clamp(8px, calc(${popup.point.y * 100}vh + 14px), calc(100vh - 200px))`,\n width: \"min(332px, calc(100vw - 16px))\",\n boxSizing: \"border-box\",\n border: `1px solid ${TOKENS.line}`,\n borderRadius: 12,\n background: TOKENS.surface,\n padding: 12,\n boxShadow: TOKENS.shadow,\n pointerEvents: \"auto\",\n }}\n >\n {/* 무엇을 가리켰는지 한 줄. 라벨을 따로 두면 입력창이 아래로 밀려\n 클릭 지점에서 멀어진다 — 이 팝업의 존재 이유가 \"그 자리\" 다. */}\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: 6,\n marginBottom: 7,\n color: TOKENS.muted,\n fontSize: 12,\n }}\n >\n <span aria-hidden=\"true\">◎</span>\n <span\n style={{\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n }}\n >\n {popup.element?.text?.trim() || popup.element?.selector || \"이 지점\"}\n </span>\n </div>\n\n {popup.hintsAvailable ? (\n <HintChips\n hints={popup.hints}\n hintsExpanded={popup.hintsExpanded}\n usedHintIds={popup.usedHintIds}\n disabled={popup.saving}\n onApply={(hintId) => kit.picking.applyHint(hintId)}\n onToggle={() => kit.picking.toggleHints()}\n />\n ) : null}\n\n <label htmlFor=\"feedback-kit-annotation\" style={visuallyHidden}>\n 이 요소에 남길 의견\n </label>\n <textarea\n id=\"feedback-kit-annotation\"\n autoFocus\n value={popup.comment}\n disabled={popup.saving}\n onChange={(event) => kit.picking.setAnnotationComment(event.currentTarget.value)}\n onKeyDown={(event) => {\n // Enter 로 바로 보낸다. 줄바꿈이 필요하면 Shift+Enter.\n // 한글 조합 중의 Enter 는 글자를 확정하는 키다 — 여기서 보내면 글자를 잃는다.\n if (event.key !== \"Enter\" || event.shiftKey) return;\n // React 의 합성 이벤트 타입에는 isComposing 이 없다 — 네이티브에서 읽는다.\n const native = event.nativeEvent as unknown as { isComposing?: boolean };\n if (native.isComposing) return;\n event.preventDefault();\n void kit.picking.saveAnnotation();\n }}\n rows={2}\n placeholder=\"무엇이 문제인가요? (Enter 로 보내기)\"\n style={{ ...inputStyle, resize: \"vertical\", minHeight: 58 }}\n />\n {popup.comment.length > COMMENT_MAX_CHARS ? (\n <div role=\"alert\" style={{ marginTop: 4, color: TOKENS.danger }}>\n 4,000자 이하로 입력해주세요\n </div>\n ) : null}\n\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: 8,\n marginTop: 8,\n color: TOKENS.muted,\n fontSize: 12,\n }}\n >\n {popup.screenshot ? (\n <>\n <img\n src={screenshotSource(popup.screenshot)}\n alt=\"이 주석에 첨부된 화면\"\n style={{\n width: 40,\n height: 26,\n objectFit: \"cover\",\n borderRadius: 4,\n border: `1px solid ${TOKENS.line}`,\n }}\n />\n <span style={{ flex: 1 }}>화면 첨부됨 · 붙여넣기로 교체</span>\n <button\n type=\"button\"\n disabled={popup.saving}\n onClick={() => kit.picking.removeAnnotationImage()}\n style={{ ...baseButton, minHeight: 26, padding: \"2px 8px\", fontSize: 12 }}\n >\n 제거\n </button>\n </>\n ) : (\n <span role=\"status\" style={{ flex: 1 }}>\n {popup.screenshotStatus === \"capturing\"\n ? \"화면 캡처 중…\"\n : popup.screenshotStatus === \"failed\"\n ? \"캡처 실패 — 이미지를 붙여넣어 주세요\"\n : \"이미지를 붙여넣거나 끌어다 놓을 수 있어요\"}\n </span>\n )}\n </div>\n\n <div style={{ display: \"flex\", justifyContent: \"flex-end\", gap: 8, marginTop: 10 }}>\n <button\n type=\"button\"\n disabled={popup.saving}\n onClick={() => kit.picking.cancelAnnotation()}\n style={{ ...baseButton, minHeight: 32, padding: \"5px 10px\" }}\n >\n 취소\n </button>\n <button\n type=\"submit\"\n disabled={!popup.canSave || popup.saving}\n style={{\n ...primaryButton,\n minHeight: 32,\n padding: \"5px 12px\",\n opacity: popup.canSave && !popup.saving ? 1 : 0.5,\n cursor: popup.canSave && !popup.saving ? \"pointer\" : \"not-allowed\",\n }}\n >\n {popup.saving ? \"보내는 중…\" : \"보내기\"}\n </button>\n </div>\n </form>\n ) : null}\n </div>\n ) : null}\n </div>\n );\n}\n","// 지금 도는 위젯이 몇 버전인지.\n//\n// 왜 화면에 띄우나: 패키지를 올려도 **쓰는 쪽 lockfile 이 안 따라오면 옛 코드가 계속\n// 돈다**(0.x 에서 `^0.2.0` 은 `0.2.1` 을 안 잡아준다). 그때 겉으로는 아무 차이가 없어서\n// \"올렸는데 왜 그대로지\"를 매번 lockfile 을 뒤져 확인해야 했다. 모달에 한 줄 있으면\n// 열어보는 것으로 끝난다.\n//\n// 값은 **빌드 타임에 박는다**(tsup `define`). 런타임에 package.json 을 읽으면 번들러마다\n// 깨지고, 손으로 적은 상수는 릴리스 때마다 잊는다.\n\ndeclare const __FK_VERSION__: string | undefined;\n\n/**\n * 배포된 패키지에서는 실제 버전, 소스로 직접 돌릴 때(테스트·워크스페이스 링크)는 `dev`.\n *\n * `dev` 가 보이면 npm 에서 받은 게 아니라는 뜻이라, 그것도 정보다.\n */\nexport const VERSION: string =\n typeof __FK_VERSION__ === \"string\" && __FK_VERSION__ !== \"\" ? __FK_VERSION__ : \"dev\";\n","// 웹 기본 컨텍스트 제공자.\n//\n// 왜 패키지가 주나: `getUrl` 을 호스트가 깜빡하면 제보의 url 이 빈 문자열로 나가고,\n// 수집 서버는 그걸 400 으로 거절한다. 400 은 재시도 불가라 **제보가 조용히 버려진다.**\n// 실제로 e-fm 첫 배선에서 이 일이 났다 — 화면에는 \"보내지 못했습니다\"만 뜨고,\n// 원인(빈 url)은 네트워크를 까 봐야 알 수 있었다.\n//\n// 기본값을 패키지가 쥐고 있으면 붙이는 쪽이 잊을 수 없다. 필요하면 덮어쓰면 된다.\n\nimport type {\n ContextProviders,\n DiagnosticsInstallOpts,\n WebContextInput,\n} from \"@solhun/feedback-kit-core\";\nimport { webRouteStorage, webRouteWatcher } from \"./route-watcher.js\";\n\n/**\n * 브라우저 전역에서 뽑을 수 있는 기본 제공자들.\n *\n * SSR·비브라우저에서 불려도 던지지 않는다(`null` 을 돌려준다) — 위젯이 마운트되기 전\n * 서버 렌더 단계에서 실행될 수 있기 때문이다.\n */\nexport function webContextProviders(): ContextProviders {\n return {\n getUrl: () => {\n const loc = (globalThis as { location?: { href?: string } }).location;\n return typeof loc?.href === \"string\" && loc.href.length > 0 ? loc.href : null;\n },\n getWebContext: (): WebContextInput | null => {\n const g = globalThis as {\n location?: unknown;\n navigator?: { userAgent?: string };\n innerWidth?: number;\n innerHeight?: number;\n devicePixelRatio?: number;\n };\n if (!g.location) return null;\n return {\n userAgent: g.navigator?.userAgent ?? null,\n viewport: {\n width: typeof g.innerWidth === \"number\" ? g.innerWidth : null,\n height: typeof g.innerHeight === \"number\" ? g.innerHeight : null,\n devicePixelRatio: typeof g.devicePixelRatio === \"number\" ? g.devicePixelRatio : null,\n },\n };\n },\n };\n}\n\n/**\n * 웹 진단 수집의 기본 배선.\n *\n * `sharedDiagnostics.install(webDiagnosticsOptions({ ... }))` 로 쓴다. 넘긴 값이 기본값을\n * 덮으므로 감시자·저장소를 직접 갈아 끼울 수도 있다.\n *\n * 왜 패키지가 주나: 감시자를 호스트가 깜빡하면 **궤적만 조용히 빈다.** 네트워크·로그는\n * 그대로 모이니 화면상 아무 문제가 없어 보이고, \"이 제보엔 이동이 없었나 보다\"로 읽힌다 —\n * 빠진 것과 없는 것이 구분되지 않는 실패다.\n *\n * 진단을 끄면(`captureDiagnostics: false`) install 자체가 안 불리므로 궤적도 같이 꺼진다.\n * 궤적만 따로 켜고 끄는 스위치는 만들지 않는다.\n */\nexport function webDiagnosticsOptions(opts: DiagnosticsInstallOpts = {}): DiagnosticsInstallOpts {\n return {\n watchRoutes: webRouteWatcher,\n routeStorage: webRouteStorage(),\n ...opts,\n };\n}\n"],"mappings":"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","names":["isOwnUi","import_feedback_kit_core","isOwnUi","html2canvas","import_feedback_kit_core","defaultPathname","import_feedback_kit_core","modal","screen"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/react-tree.ts","../src/element-info.ts","../src/marker-store.ts","../src/picking.ts","../src/route-watcher.ts","../src/screenshot.ts","../src/widget.ts","../src/storage.ts","../src/feedback-kit.tsx","../src/version.ts","../src/dev-clipboard.ts","../src/providers.ts"],"sourcesContent":["// @solhun/feedback-kit-web\n//\n// 웹용 피드백 위젯 엔트리.\n//\n// 화면 지도·리포트 모달·핀 좌표 계산은 전부 코어에 있다. 여기 있는 건 웹에서만 뜻이 있는 것들 —\n// DOM 요소를 제보에 실을 형태로 옮기고(`element-info`), 지목 모드와 마커를 브라우저 저장소에\n// 남기고(`marker-store`), 페이지 클릭을 가로채 주석을 다는 것(`picking`) — 뿐이다.\n\nexport type {\n FeedbackReport,\n SubmitResult,\n FeedbackContext,\n FeedbackUser,\n FeedbackStorage,\n FeedbackAdapter,\n ElementInfo,\n FeedbackPin,\n} from \"@solhun/feedback-kit-core\";\n\n// 설정은 코어가 정의한다. 웹/앱이 각자 다른 설정 타입을 갖지 않는 게 중요해서\n// (같은 설정을 두 곳에 적으면 갈라진다) 여기서는 다시 내보내기만 한다.\nexport type {\n FeedbackConfig,\n ResolvedConfig,\n Visibility,\n VisibilityEnv,\n VisibilityFn,\n WidgetPosition,\n} from \"@solhun/feedback-kit-core\";\nexport { resolveConfig, shouldShowWidget } from \"@solhun/feedback-kit-core\";\n\n// 빌드 플러그인이 심은 소스 경로를 읽는 쪽. 플러그인이 없으면 조용히 null 이다.\nexport { parseSourceAttr, SOURCE_ATTR, sourceFromElement } from \"@solhun/feedback-kit-core\";\n\n// 코어의 헤드리스 위젯 계층. 웹 컴포넌트가 이 상태기계를 그대로 그린다.\nexport type {\n ModalSubmitStatus,\n ReportModalState,\n ScreenshotStatus,\n WidgetScreen,\n WidgetState,\n} from \"@solhun/feedback-kit-core\";\nexport {\n COMMENT_MAX_CHARS,\n COMMENT_REQUIRED_MESSAGE,\n COMMENT_TOO_LONG_MESSAGE,\n denormalizePin,\n FLOATING_BUTTON_ID,\n MODAL_ACTION_HINT_TOGGLE,\n MODAL_ACTION_PICK,\n normalizePin,\n ReportModalController,\n SCREENSHOT_FAILED_MESSAGE,\n SUBMIT_DONE_MESSAGE,\n SUBMIT_PENDING_MESSAGE,\n WidgetController,\n} from \"@solhun/feedback-kit-core\";\n\n// 힌트(자주 나오는 제보 제안). `WebWidgetOpts.hints`/`hintProvider` 를 타입 지정하거나\n// 테스트에서 직접 주입하려는 소비처를 위해 다시 내보낸다.\nexport type {\n ElementLike,\n FeedbackHint,\n HintCatalog,\n HintElementRule,\n HintPlatform,\n HintRankInput,\n HintScope,\n} from \"@solhun/feedback-kit-core\";\nexport { HintProvider, rankHints } from \"@solhun/feedback-kit-core\";\n\n// ── 웹 전용 ────────────────────────────────────────────────────────────────\n\nexport {\n cssSelectorPath,\n describeElement,\n ELEMENT_TEXT_MAX_CHARS,\n SELECTOR_MAX_DEPTH,\n visibleText,\n} from \"./element-info.js\";\n\nexport type { MarkerStatus, StorageLike, StoredMarker } from \"./marker-store.js\";\nexport {\n MARKER_KEY_PREFIX,\n MarkerStore,\n MAX_MARKERS_PER_PATH,\n PICKING_MODE_KEY,\n} from \"./marker-store.js\";\n\nexport type {\n AnnotationPopupState,\n ElementPickingOpts,\n PickingListener,\n PickingQueueLike,\n PickingState,\n} from \"./picking.js\";\nexport { ElementPickingController, OWN_UI_ATTR } from \"./picking.js\";\n\nexport { captureWebScreenshot, isOwnUi, reencodeWebScreenshot } from \"./screenshot.js\";\n\n// 지목한 요소의 React 컴포넌트 경로. 제보에서 코드를 찾는 시간을 가장 크게 줄인다.\nexport { reactComponentPath, reactComponentSummary } from \"./react-tree.js\";\n\nexport type { WebWidget, WebWidgetOpts } from \"./widget.js\";\nexport { createWebWidget } from \"./widget.js\";\n\nexport type { FeedbackKitProps } from \"./feedback-kit.js\";\nexport { FeedbackKit } from \"./feedback-kit.js\";\n\n// 웹 기본 저장소(localStorage). 앱마다 다시 쓰지 않게 패키지가 제공한다.\nexport { createWebStorage } from \"./storage.js\";\n\n// 웹 기본 컨텍스트 제공자(getUrl 등). 붙이는 쪽이 잊으면 제보가 버려진다.\nexport { webContextProviders, webDiagnosticsOptions } from \"./providers.js\";\nexport {\n createRouteWatcher,\n webRouteStorage,\n webRouteWatcher,\n type RouteWatchTarget,\n} from \"./route-watcher.js\";\n","// 지목한 요소가 **어느 React 컴포넌트에 속하는지** 알아낸다.\n//\n// 왜 필요한가: `div.flex.items-center` 같은 선택자만 오면 고치는 사람이 그걸\n// 코드에서 다시 찾아야 한다. `LoginForm > SubmitButton` 이 오면 바로 파일이 열린다.\n// 제보 하나당 이 한 줄이 탐색 시간을 가장 크게 줄인다.\n//\n// 방법: React 는 DOM 노드에 `__reactFiber$<랜덤>` 프로퍼티를 심는다(공개 API 가 아니다).\n// 거기서 fiber 를 잡아 부모로 거슬러 올라가며 컴포넌트 이름을 모은다.\n//\n// 한계를 미리 적어둔다:\n// - **프로덕션 빌드에서는 이름이 minify 되어 `t`·`Xe` 처럼 나올 수 있다.** 그래도 트리\n// 모양은 단서가 되므로 버리지 않는다. 이름을 살리려면 빌드에서 displayName 을 남긴다.\n// - React 19 도 같은 프로퍼티를 쓰지만 내부 구조라 언제든 바뀔 수 있다. 못 읽으면\n// **조용히 빈 배열**을 돌려준다 — 제보 자체를 막지 않는다.\n\n/**\n * 프레임워크가 만든 이름. 사람이 지은 게 아니라서 제보에 실어도 단서가 안 된다.\n *\n * 왜 필요한가 (2026-08-09 실측, cardnews 랜딩 = Next.js App Router):\n * 지목 제보의 컴포넌트 경로가 **전부 프레임워크 내부로만** 채워져 왔다 —\n * `HotReload > AppDevOverlayErrorBoundary > ... > InnerLayoutRouter > LayoutRouterContext`.\n * 페이지가 서버 컴포넌트면 클라이언트 fiber 에 그 이름이 안 남고, 위로는 전부 경계\n * 컴포넌트다. 원래 목적(\"`LoginForm > SubmitButton` 이 오면 바로 파일이 열린다\")이\n * 무너지는데다 300자 넘는 잡음이 제보마다 들어갔다. Vite + 클라이언트 컴포넌트인\n * KITS 웹에서는 정상이라 안 드러났다.\n *\n * **접미사로 거르지 않는다.** `*Provider`·`*Context`·`*Boundary` 를 통째로 지우면\n * 사람이 지은 `AuthProvider`·`ThemeContext` 까지 사라진다. 그래서 실제로 관측된\n * 이름만 정확히 적는다 — 새 프레임워크 잡음이 보이면 여기 추가한다.\n */\nconst FRAMEWORK_INTERNALS = new Set([\n // Next.js App Router\n \"AppRouter\",\n \"InnerLayoutRouter\",\n \"OuterLayoutRouter\",\n \"LayoutRouterContext\",\n \"RenderFromTemplateContext\",\n \"TemplateContext\",\n \"SegmentStateProvider\",\n \"SegmentViewNode\",\n \"ScrollAndMaybeFocusHandler\",\n \"InnerScrollAndFocusHandler\",\n \"InnerScrollAndFocusHandlerOld\",\n \"RedirectBoundary\",\n \"RedirectErrorBoundary\",\n \"NotFoundBoundary\",\n \"NotFoundErrorBoundary\",\n \"HTTPAccessFallbackBoundary\",\n \"HTTPAccessFallbackErrorBoundary\",\n \"DevRootHTTPAccessFallbackBoundary\",\n \"LoadingBoundary\",\n \"ErrorBoundaryHandler\",\n \"MetadataBoundary\",\n \"ViewportBoundary\",\n \"OutletBoundary\",\n // 개발 전용 오버레이 — 프로덕션엔 없지만 dev 제보에는 항상 낀다\n \"HotReload\",\n \"AppDevOverlay\",\n \"AppDevOverlayErrorBoundary\",\n \"ReactDevOverlay\",\n \"DevOverlay\",\n \"Router\",\n \"ServerRoot\",\n \"RSCComponent\",\n]);\n\n/** `__next_root_layout_boundary__` 처럼 규칙이 이름에 드러나는 것들. */\nfunction isFrameworkName(name: string): boolean {\n return FRAMEWORK_INTERNALS.has(name) || name.startsWith(\"__next\");\n}\n\n/** 호스트 요소(div, span…)는 건너뛴다. 우리가 원하는 건 사람이 지은 컴포넌트 이름이다. */\nfunction componentName(type: unknown): string | null {\n if (typeof type === \"function\") {\n const fn = type as { displayName?: string; name?: string };\n return fn.displayName || fn.name || null;\n }\n if (type !== null && typeof type === \"object\") {\n // memo/forwardRef 등 래퍼. 한 겹 안쪽을 본다.\n const wrapped = type as { displayName?: string; render?: unknown; type?: unknown };\n if (wrapped.displayName) return wrapped.displayName;\n if (wrapped.render) return componentName(wrapped.render);\n if (wrapped.type) return componentName(wrapped.type);\n }\n return null;\n}\n\nfunction fiberOf(node: Element): Record<string, unknown> | null {\n // Object.keys 가 아니라 getOwnPropertyNames — React 는 열거 가능하게 심지만,\n // 그렇지 않은 환경/버전에서도 찾을 수 있어야 한다.\n for (const key of Object.getOwnPropertyNames(node)) {\n if (key.startsWith(\"__reactFiber$\") || key.startsWith(\"__reactInternalInstance$\")) {\n const value = (node as unknown as Record<string, unknown>)[key];\n if (value !== null && typeof value === \"object\") return value as Record<string, unknown>;\n }\n }\n return null;\n}\n\n/**\n * 바깥 → 안쪽 순서의 **앱** 컴포넌트 이름 목록.\n * 예: `[\"App\", \"LoginPage\", \"LoginForm\", \"SubmitButton\"]`\n *\n * 프레임워크가 만든 이름은 뺀다. 다 빼고 나면 빈 배열이고, 그게 정직한 답이다 —\n * 서버 컴포넌트만 있는 화면에는 클라이언트 fiber 에 앱 이름이 애초에 없다.\n *\n * @param limit 안쪽에서부터 이만큼만 남긴다. 요소에 **가까운 쪽이 단서**라, 넘치면\n * 바깥(앱 루트 쪽)을 버린다. 예전엔 20 이었는데 그 길이가 그대로 제보에 실려서\n * 한 줄이 300자를 넘었다.\n */\nexport function reactComponentPath(node: Element, limit = 5): string[] {\n try {\n let fiber = fiberOf(node);\n const names: string[] = [];\n // 안쪽 → 바깥 순으로 훑는다. 프레임워크 이름을 거른 뒤에 세야 limit 이\n // 잡음으로 먼저 차지 않는다.\n let depth = 0;\n while (fiber && names.length < limit && depth < 200) {\n depth += 1;\n const name = componentName(fiber.elementType ?? fiber.type);\n // 같은 이름이 연속으로 나오는 건(래퍼) 한 번만 남긴다.\n if (name && !isFrameworkName(name) && names[0] !== name) names.unshift(name);\n fiber = (fiber.return ?? null) as Record<string, unknown> | null;\n }\n return names;\n } catch {\n return [];\n }\n}\n\n/** 제보 본문에 실을 한 줄. 컴포넌트를 못 찾으면 null(빈 줄을 만들지 않는다). */\nexport function reactComponentSummary(node: Element): string | null {\n const path = reactComponentPath(node);\n return path.length > 0 ? path.join(\" > \") : null;\n}\n","import { reactComponentSummary } from \"./react-tree.js\";\n// 지목한 DOM 요소를 제보에 실을 수 있는 형태로 옮긴다.\n//\n// 목표는 \"나중에 이 요소를 다시 찾을 수 있게\" 하는 것이다. 그래서 태그·id·클래스만이 아니라\n// 형제 순서까지 담은 CSS 선택자 경로를 함께 만든다. 못 만들어도 제보는 성립해야 하므로\n// 이 파일의 함수들은 절대 던지지 않는다.\n\nimport type { ElementInfo } from \"@solhun/feedback-kit-core\";\n\n/** 요소 텍스트를 담을 때의 상한. 본문 전체가 실려 페이로드가 붓는 걸 막는다. */\nexport const ELEMENT_TEXT_MAX_CHARS = 200;\n\n/** 선택자 경로를 몇 단계까지 거슬러 올라갈지. 너무 깊으면 오히려 잘 깨진다. */\nexport const SELECTOR_MAX_DEPTH = 8;\n\n/**\n * 빈 여백을 눌렀을 때 가까운 실제 콘텐츠로 붙여 주는 거리.\n *\n * 레이아웃용 `div` 의 padding 을 누르면 브라우저의 event.target 은 그 큰 `div` 다.\n * 그 상태로 박스를 그리면 사용자가 보고 누른 카드 바로 옆인데도 패널 전체를 가리킨다.\n * 다만 멀리 있는 자식까지 임의로 고르면 빈 영역을 지목할 수 없으므로, 한 번의 클릭 오차로\n * 볼 수 있는 24px 안에서만 더 작은 자식으로 붙인다.\n */\nexport const PICK_TARGET_SNAP_PX = 24;\n\n/** 담아 둘 속성. 전부 담으면 인라인 style 같은 게 페이로드를 채운다. */\nconst KEPT_ATTRS = [\n \"id\",\n \"class\",\n \"name\",\n \"type\",\n \"role\",\n \"href\",\n \"aria-label\",\n \"data-testid\",\n \"data-fk-source\",\n];\n\nfunction cssEscape(value: string): string {\n const fn = (globalThis as { CSS?: { escape?: (v: string) => string } }).CSS?.escape;\n if (typeof fn === \"function\") return fn(value);\n // CSS.escape 가 없는 환경 대비 최소 폴백. 식별자에 쓰이면 곤란한 문자만 막는다.\n return value.replace(/[^a-zA-Z0-9_-]/g, (ch) => `\\\\${ch}`);\n}\n\n/** 요소가 형제들 사이에서 몇 번째 같은 태그인지. 1부터 센다. */\nfunction nthOfType(el: Element): number {\n const parent = el.parentElement;\n if (!parent) return 1;\n let index = 0;\n for (const child of Array.from(parent.children)) {\n if (child.tagName === el.tagName) {\n index += 1;\n if (child === el) return index;\n }\n }\n return 1;\n}\n\n/** 같은 태그 형제가 둘 이상인가. 하나뿐이면 `:nth-of-type` 을 붙일 이유가 없다. */\nfunction hasTypeSiblings(el: Element): boolean {\n const parent = el.parentElement;\n if (!parent) return false;\n let count = 0;\n for (const child of Array.from(parent.children)) {\n if (child.tagName === el.tagName) {\n count += 1;\n if (count > 1) return true;\n }\n }\n return false;\n}\n\n/**\n * 요소까지의 CSS 선택자 경로.\n *\n * id 를 만나면 거기서 멈춘다 — 문서에서 유일하다고 가정할 수 있는 지점이라 그 위는 군더더기다.\n * 클래스는 쓰지 않는다: 유틸리티 클래스(`px-4` 같은 것)가 섞이면 선택자가 길기만 하고\n * 재현성은 오히려 떨어진다.\n */\nexport function cssSelectorPath(el: Element | null | undefined): string | null {\n if (!el || typeof el.tagName !== \"string\") return null;\n try {\n const parts: string[] = [];\n let node: Element | null = el;\n let depth = 0;\n\n while (node && depth < SELECTOR_MAX_DEPTH) {\n const tag = node.tagName.toLowerCase();\n if (tag === \"html\" || tag === \"body\") {\n parts.unshift(tag);\n break;\n }\n\n const id = node.getAttribute(\"id\");\n if (id) {\n parts.unshift(`#${cssEscape(id)}`);\n break; // id 위로는 올라갈 필요가 없다.\n }\n\n parts.unshift(hasTypeSiblings(node) ? `${tag}:nth-of-type(${nthOfType(node)})` : tag);\n node = node.parentElement;\n depth += 1;\n }\n\n return parts.length > 0 ? parts.join(\" > \") : null;\n } catch {\n return null;\n }\n}\n\n/** 눈에 보이는 텍스트를 한 줄로 눌러 담는다. */\nexport function visibleText(el: Element): string | null {\n const raw = (el as HTMLElement).innerText ?? el.textContent ?? \"\";\n const collapsed = raw.replace(/\\s+/g, \" \").trim();\n if (collapsed === \"\") return null;\n return collapsed.length > ELEMENT_TEXT_MAX_CHARS\n ? collapsed.slice(0, ELEMENT_TEXT_MAX_CHARS)\n : collapsed;\n}\n\ntype ClientPoint = { x: number; y: number };\n\ntype ElementRect = {\n element: Element;\n left: number;\n top: number;\n right: number;\n bottom: number;\n width: number;\n height: number;\n};\n\nfunction elementRect(element: Element): ElementRect | null {\n try {\n const rect = element.getBoundingClientRect();\n const left = rect.left;\n const top = rect.top;\n const width = rect.width;\n const height = rect.height;\n if (![left, top, width, height].every(Number.isFinite) || width <= 0 || height <= 0) {\n return null;\n }\n return {\n element,\n left,\n top,\n right: left + width,\n bottom: top + height,\n width,\n height,\n };\n } catch {\n return null;\n }\n}\n\nfunction distanceFromPoint(rect: ElementRect, point: ClientPoint): number {\n const dx = Math.max(rect.left - point.x, 0, point.x - rect.right);\n const dy = Math.max(rect.top - point.y, 0, point.y - rect.bottom);\n return Math.hypot(dx, dy);\n}\n\n/**\n * 브라우저가 돌려준 event.target 을 사용자가 보고 누른 더 정확한 요소로 좁힌다.\n *\n * 1. 클릭을 포함하는 자식이 있으면 가장 작은 자식으로 계속 내려간다. `pointer-events: none`\n * 장식 아래의 실제 시각 요소도 고를 수 있어야 해서 hit-test 결과만 다시 묻지 않는다.\n * 2. 클릭이 padding 에 떨어졌다면 24px 안의 더 작은 직계 자식까지만 한 번 붙인다.\n * 그보다 멀면 사용자가 빈 레이아웃 자체를 고른 것으로 보고 원래 target 을 보존한다.\n *\n * DOM/레이아웃 접근이 실패해도 원래 target 을 돌려 제보 자체는 성립하게 한다.\n */\nexport function resolvePickTarget(\n target: Element | null | undefined,\n point: ClientPoint,\n): Element | null {\n if (!target || typeof target.tagName !== \"string\") return null;\n if (!Number.isFinite(point.x) || !Number.isFinite(point.y)) return target;\n\n let current = target;\n while (true) {\n const currentRect = elementRect(current);\n const children = Array.from(current.children)\n .map(elementRect)\n .filter((rect): rect is ElementRect => rect !== null)\n .map((rect) => ({ rect, distance: distanceFromPoint(rect, point) }));\n\n if (children.length === 0) return current;\n\n const containing = children\n .filter(({ distance }) => distance === 0)\n .sort((a, b) => a.rect.width * a.rect.height - b.rect.width * b.rect.height);\n if (containing[0]) {\n current = containing[0].rect.element;\n continue;\n }\n\n const nearest = children.sort((a, b) => a.distance - b.distance)[0];\n const isMorePrecise = currentRect\n ? nearest.rect.width * nearest.rect.height < currentRect.width * currentRect.height\n : true;\n if (nearest.distance <= PICK_TARGET_SNAP_PX && isMorePrecise) {\n return nearest.rect.element;\n }\n return current;\n }\n}\n\n/** 지목된 요소를 `ElementInfo` 로 옮긴다. 실패해도 던지지 않는다. */\nexport function describeElement(el: Element | null | undefined): ElementInfo | null {\n if (!el || typeof el.tagName !== \"string\") return null;\n\n const attributes: Record<string, string> = {};\n for (const name of KEPT_ATTRS) {\n const value = el.getAttribute(name);\n if (value !== null) attributes[name] = value;\n }\n\n let boundingBox: ElementInfo[\"boundingBox\"] = null;\n try {\n const rect = el.getBoundingClientRect();\n boundingBox = { x: rect.left, y: rect.top, width: rect.width, height: rect.height };\n } catch {\n boundingBox = null;\n }\n\n const react = reactComponentSummary(el);\n\n return {\n tag: el.tagName.toLowerCase(),\n id: el.getAttribute(\"id\"),\n className: el.getAttribute(\"class\"),\n text: visibleText(el),\n boundingBox,\n attributes: {\n ...attributes,\n // 지목한 요소가 어느 컴포넌트에 속하는지. `div.flex.items-center` 만 오면\n // 고치는 사람이 코드에서 다시 찾아야 하지만, `LoginForm > SubmitButton` 이\n // 오면 바로 파일이 열린다. React 가 아니면 이 키 자체가 없다.\n ...(react ? { \"react-components\": react } : {}),\n },\n selector: cssSelectorPath(el),\n };\n}\n","// 요소 지목 모드와 마커를 브라우저 저장소에 남긴다.\n//\n// 왜 저장하나: 지목 모드는 페이지를 옮겨 다니며 쓰는 기능이라 새로고침·이동에 살아남아야 하고,\n// 찍은 마커는 같은 경로로 돌아왔을 때 원래 자리에 다시 보여야 한다.\n//\n// 키 이름은 `feedback-kit:` 로 시작하는 우리 고유 네임스페이스를 쓴다. 남의 위젯과 키가 겹치면\n// 서로의 상태를 지우게 된다.\n\n/** 지목 모드 on/off 플래그. */\nexport const PICKING_MODE_KEY = \"feedback-kit:picking-mode\";\n\n/** 경로별 마커 목록. 실제 키는 여기에 pathname 을 이어 붙인다. */\nexport const MARKER_KEY_PREFIX = \"feedback-kit:markers:\";\n\n/** 한 경로에 남겨 둘 마커 상한. 넘으면 오래된 것부터 버린다. */\nexport const MAX_MARKERS_PER_PATH = 50;\n\n/** 마커에 보이는 전송 상태. `sending` → `done`, 실패하면 `pending`. */\nexport type MarkerStatus = \"sending\" | \"done\" | \"pending\";\n\nexport interface StoredMarker {\n /** 제보의 `clientSubmissionId` 와 같다 — 전송 상태를 되짚어 갱신하려고 맞춰 둔다. */\n id: string;\n /** 뷰포트 기준 상대 좌표(0~1). 해상도가 달라도 같은 지점을 가리킨다. */\n x: number;\n y: number;\n selector: string | null;\n comment: string;\n status: MarkerStatus;\n /** 생성 시각(ISO). 상한을 넘겼을 때 무엇을 버릴지 정하는 기준. */\n at: string;\n}\n\n/** `localStorage` 가 만족하는 최소 계약. 테스트에서 갈아 끼운다. */\nexport interface StorageLike {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n}\n\nfunction defaultStorage(): StorageLike | null {\n try {\n const ls = (globalThis as { localStorage?: StorageLike }).localStorage;\n return ls ?? null;\n } catch {\n // 사파리 프라이빗 모드처럼 접근 자체가 던지는 환경이 있다.\n return null;\n }\n}\n\nfunction markerKey(pathname: string): string {\n return `${MARKER_KEY_PREFIX}${pathname}`;\n}\n\n/**\n * 저장소 접근을 한 곳으로 모은다.\n *\n * 저장소가 없거나(SSR) 던지는 환경(프라이빗 모드·용량 초과)에서도 위젯이 죽으면 안 되므로\n * 모든 접근을 try/catch 로 감싸고 실패는 조용히 흘린다. 저장 실패는 \"마커가 안 남는다\" 정도의\n * 불편이지 제보 자체를 막을 이유가 아니다.\n */\nexport class MarkerStore {\n private readonly storage: StorageLike | null;\n\n constructor(storage?: StorageLike | null) {\n this.storage = storage === undefined ? defaultStorage() : storage;\n }\n\n private read(key: string): string | null {\n try {\n return this.storage?.getItem(key) ?? null;\n } catch {\n return null;\n }\n }\n\n private write(key: string, value: string): void {\n try {\n this.storage?.setItem(key, value);\n } catch {\n /* 저장 실패는 흘린다 */\n }\n }\n\n private drop(key: string): void {\n try {\n this.storage?.removeItem(key);\n } catch {\n /* 삭제 실패도 흘린다 */\n }\n }\n\n // ── 지목 모드 ─────────────────────────────────────────────────────────────\n\n isPickingActive(): boolean {\n return this.read(PICKING_MODE_KEY) === \"1\";\n }\n\n setPickingActive(active: boolean): void {\n if (active) this.write(PICKING_MODE_KEY, \"1\");\n else this.drop(PICKING_MODE_KEY);\n }\n\n // ── 마커 ──────────────────────────────────────────────────────────────────\n\n list(pathname: string): StoredMarker[] {\n const raw = this.read(markerKey(pathname));\n if (!raw) return [];\n try {\n const parsed: unknown = JSON.parse(raw);\n if (!Array.isArray(parsed)) return [];\n // 남이 쓴 값이나 옛 포맷이 섞여 있어도 위젯이 깨지지 않게 형태를 확인하고 거른다.\n return parsed.filter((item): item is StoredMarker => {\n if (typeof item !== \"object\" || item === null) return false;\n const m = item as Partial<StoredMarker>;\n return (\n typeof m.id === \"string\" && typeof m.x === \"number\" && typeof m.y === \"number\"\n );\n });\n } catch {\n return [];\n }\n }\n\n /** 같은 id 가 있으면 덮어쓰고, 없으면 뒤에 붙인다. */\n upsert(pathname: string, marker: StoredMarker): StoredMarker[] {\n const current = this.list(pathname);\n const index = current.findIndex((m) => m.id === marker.id);\n if (index >= 0) current[index] = marker;\n else current.push(marker);\n\n const trimmed =\n current.length > MAX_MARKERS_PER_PATH\n ? current.slice(current.length - MAX_MARKERS_PER_PATH)\n : current;\n this.write(markerKey(pathname), JSON.stringify(trimmed));\n return trimmed;\n }\n\n /** 전송 상태만 바꾼다. 없는 id 면 아무것도 하지 않는다. */\n updateStatus(pathname: string, id: string, status: MarkerStatus): StoredMarker[] {\n const current = this.list(pathname);\n const index = current.findIndex((m) => m.id === id);\n if (index < 0) return current;\n current[index] = { ...current[index], status };\n this.write(markerKey(pathname), JSON.stringify(current));\n return current;\n }\n\n clear(pathname: string): void {\n this.drop(markerKey(pathname));\n }\n}\n","// 요소 지목 모드 (웹 전용).\n//\n// 켜져 있는 동안 페이지의 아무 요소나 클릭하면 그 자리에 주석을 남긴다. 리포트 모달과 달리\n// \"열고 → 쓰고 → 닫는\" 절차가 없다 — 한 번 켜 두고 여러 곳을 연달아 지목하는 게 이 모드의 전부다.\n// 그래서 주석을 저장해도 모드는 꺼지지 않고, 페이지를 옮겨도 살아남는다.\n//\n// 페이지의 원래 클릭(링크 이동·버튼 동작)은 막아야 한다. 지목하려고 누른 링크가 실제로 이동해\n// 버리면 주석을 달 화면이 사라진다.\n\nimport {\n COMMENT_MAX_CHARS,\n captureWithinLimit,\n normalizePin,\n type ElementInfo,\n type FeedbackHint,\n type FeedbackPin,\n type FeedbackReport,\n type FeedbackScreenshot,\n type ReportParts,\n type QueueStatus,\n type ScreenshotCapture,\n type ScreenshotReencode,\n type RouteWatcher,\n type SubmitOutcome,\n} from \"@solhun/feedback-kit-core\";\nimport { describeElement, resolvePickTarget } from \"./element-info.js\";\nimport { MarkerStore, type StoredMarker } from \"./marker-store.js\";\nimport { webRouteWatcher } from \"./route-watcher.js\";\n\n/** 위젯 자신의 UI 를 표시하는 속성. 이 안쪽 클릭은 가로채지 않는다. */\nexport const OWN_UI_ATTR = \"data-feedback-kit\";\n\n/** 지목 컨트롤러가 큐에게 요구하는 최소 계약. */\nexport interface PickingQueueLike {\n submit(report: FeedbackReport): Promise<SubmitOutcome>;\n subscribe?(listener: (status: QueueStatus) => void): () => void;\n getOutcome?(clientSubmissionId: string): Pick<SubmitOutcome, \"delivered\" | \"id\"> | null;\n}\n\n/** 퀵 주석의 스크린샷 상태. 모달과 같은 어휘를 쓴다(렌더러가 둘을 같은 방식으로 그린다). */\nexport type AnnotationShotStatus = \"none\" | \"capturing\" | \"ready\" | \"failed\";\n\nexport interface AnnotationPopupState {\n element: ElementInfo | null;\n /** 클릭한 지점의 뷰포트 상대 좌표(0~1). */\n point: FeedbackPin;\n comment: string;\n canSave: boolean;\n /**\n * 이 주석에 실려 갈 그림. 클릭 직후 자동 캡처가 채우고, 사용자가 붙여넣기(Cmd+V)나\n * 드래그로 덮을 수 있다.\n *\n * 왜 퀵 주석에도 그림을 싣나: 이게 없으면 **들어온 문에 따라 정보량이 달라진다.**\n * 지목으로 남긴 제보만 화면 증거가 비는 일이 실제로 있었고(0.2.0), 그때 얻은 규칙이\n * \"같은 제보는 어느 문으로 들어와도 같은 것을 담는다\" 다.\n */\n screenshot: FeedbackScreenshot | null;\n screenshotStatus: AnnotationShotStatus;\n /** 저장 중인가. 두 번 눌러 같은 주석이 두 번 가는 것을 막는다. */\n saving: boolean;\n /**\n * 자주 나오는 제보 제안 칩. 리포트 모달과 같은 계약(같은 `rankHints` 결과, 같은 접힘 규칙,\n * 같은 `hintIds` 흐름)이다 — \"들어온 문이 달라도 담기는 정보는 같다\"가 이 위젯의 약속이고,\n * 제안 칩도 예외가 아니다. `widget.ts` 가 만든 `HintProvider` 를 그대로 물려 쓴다\n * (팝업이 자기 카탈로그를 따로 들고 있지 않는다).\n */\n hints: readonly FeedbackHint[];\n hintsExpanded: boolean;\n hintsAvailable: boolean;\n usedHintIds: readonly string[];\n}\n\nexport interface PickingState {\n active: boolean;\n /**\n * 켜져 있지만 **클릭 가로채기만 잠시 끈 상태**.\n *\n * 왜 필요한가: 지목 모드는 페이지의 모든 클릭을 먹는다(그래야 지목하려고 누른 링크가\n * 실제로 이동해 버리지 않는다). 그 대가로 **제보할 화면을 만들 수가 없다** — 드롭다운을\n * 열고 그 안의 항목을 지목하고 싶어도 여는 클릭부터 막힌다. 그때 모드를 끄면 다시\n * 켜야 하고, 사용자에겐 그게 \"모드가 유지되지 않는다\"로 읽힌다.\n *\n * 그래서 끄는 것과 멈추는 것을 나눈다. 멈춘 동안 페이지는 원래대로 동작하고, 모드는\n * 켜진 채로 남는다.\n */\n paused: boolean;\n /** 지금 마우스가 올라가 있는 요소. 하이라이트를 그리는 근거. */\n hovered: ElementInfo | null;\n popup: AnnotationPopupState | null;\n /** 현재 경로에 남아 있는 마커들. */\n markers: readonly StoredMarker[];\n}\n\nexport type PickingListener = (state: PickingState) => void;\n\nexport interface ElementPickingOpts {\n queue: PickingQueueLike;\n createReport: (parts: ReportParts) => Promise<FeedbackReport>;\n store?: MarkerStore;\n /** 현재 경로. 마커는 경로별로 나눠 저장한다. */\n getPathname?: () => string;\n /** 이벤트를 들을 문서. 기본은 전역 `document`. */\n doc?: Document | null;\n /** 좌표를 정규화할 기준 크기. 기본은 현재 뷰포트. */\n getViewport?: () => { width: number; height: number };\n /**\n * 퀵 주석에 붙일 자동 캡처. 없으면 그림 없이 코멘트만 받는다.\n * 웹 기본 캡처는 위젯 자신의 UI 를 결과에서 빼므로(`ignoreElements`) 팝업이 찍히지 않는다.\n */\n capture?: ScreenshotCapture | null;\n reencode?: ScreenshotReencode | null;\n screenshotLimitBytes?: number;\n /**\n * 요소를 고르면 호출된다. 주면 **퀵 주석 팝업 대신 이걸 부른다** —\n * 호스트가 전체 리포트 모달을 열어 우선순위까지 받고 싶을 때의 탈출구다.\n * 기본값은 팝업이다: 지목은 \"클릭 → 한 줄 → Enter\" 로 끝나는 게 목적이라,\n * 매번 중앙 모달이 뜨면 흐름이 끊긴다.\n *\n * 모드는 켜진 채로 둔다. 한 번 켜고 여러 요소를 연달아 지목하는 게 이 모드의 목적이라,\n * 요소 하나 고를 때마다 모드가 꺼지면 매번 다시 켜야 한다.\n */\n onPick?: (element: ElementInfo | null, point: FeedbackPin) => void;\n /**\n * 힌트 랭킹 계산. `widget.ts` 가 만든 `HintProvider` 의 카탈로그를 그 시점의 경로·요소로\n * `rankHints` 한 결과를 돌려준다. 없으면(힌트 기능이 꺼져 있으면) 팝업에 칩이 안 뜬다.\n */\n rankHintsFor?: ((element: ElementInfo | null) => readonly FeedbackHint[]) | null;\n /**\n * 화면 이동 감시자. 기본은 웹 공용 감시자(`webRouteWatcher`) — **진단의 화면 이동 궤적과\n * 같은 것**이다. 여기만 따로 구현하면 한쪽이 이동을 놓쳤을 때 원인을 가릴 수 없다.\n * `null` 을 주면 감시하지 않는다(테스트에서 `syncPath()` 를 직접 부를 때).\n */\n watchRoutes?: RouteWatcher | null;\n}\n\nfunction defaultDocument(): Document | null {\n return (globalThis as { document?: Document }).document ?? null;\n}\n\nfunction defaultPathname(): string {\n const loc = (globalThis as { location?: { pathname?: string } }).location;\n return loc?.pathname ?? \"/\";\n}\n\nfunction defaultViewport(): { width: number; height: number } {\n const w = globalThis as { innerWidth?: number; innerHeight?: number };\n return { width: w.innerWidth ?? 0, height: w.innerHeight ?? 0 };\n}\n\n/** 위젯 자신의 UI 안에서 일어난 일인가. */\nfunction isOwnUi(target: EventTarget | null): boolean {\n const el = target as Element | null;\n if (!el || typeof el.closest !== \"function\") return false;\n return el.closest(`[${OWN_UI_ATTR}]`) !== null;\n}\n\nexport class ElementPickingController {\n private readonly queue: PickingQueueLike;\n private readonly createReport: (parts: ReportParts) => Promise<FeedbackReport>;\n private readonly onPick:\n | ((element: ElementInfo | null, point: FeedbackPin) => void)\n | null;\n private readonly store: MarkerStore;\n private readonly getPathname: () => string;\n private readonly doc: Document | null;\n private readonly getViewport: () => { width: number; height: number };\n private readonly capture: ScreenshotCapture | null;\n private readonly reencode: ScreenshotReencode | null;\n private readonly screenshotLimitBytes: number | undefined;\n private readonly unsubscribeQueue: () => void;\n private readonly rankHintsFor: ((element: ElementInfo | null) => readonly FeedbackHint[]) | null;\n\n private readonly listeners = new Set<PickingListener>();\n\n private active = false;\n /**\n * 일시중지. **저장하지 않는다** — 멈추는 목적이 대개 \"링크를 눌러 다른 화면으로 가는 것\"\n * 이라, 그 이동이 끝나면 지목이 돌아와 있는 게 목적에 맞다. 같은 이유로 경로가 바뀌면\n * 스스로 풀린다(`syncPath`) — 그래야 SPA 이동과 새로고침이 같게 동작한다.\n */\n private paused = false;\n private attached = false;\n /** 같은 요소 위 mousemove 마다 React 경로·선택자를 다시 만들지 않기 위한 캐시. */\n private hoveredTarget: Element | null = null;\n private hovered: ElementInfo | null = null;\n private popup: AnnotationPopupState | null = null;\n private markers: StoredMarker[] = [];\n private readonly watchRoutes: RouteWatcher | null;\n /** 경로 감시 해제. 감시 중이 아니면 null. */\n private unwatchRoutes: (() => void) | null = null;\n private lastPathname: string;\n private saving = false;\n /** 늦게 끝난 캡처가 다음 주석의 그림을 덮지 못하게 하는 세대 번호. */\n private shotGeneration = 0;\n /** 지금 도는 자동 캡처. Enter 가 캡처보다 빨랐을 때 기다릴 대상. */\n private capturing: Promise<void> | null = null;\n /**\n * 사용자가 [이런 건가요? ▾] 를 직접 건드렸는가. 한 번이라도 건드리면 이후 타이핑에 의한 자동\n * 접힘/펼침이 멈춘다 — 모달의 `userToggledHints` 와 같은 규칙. 팝업을 새로 열 때마다 리셋된다.\n */\n private userToggledHints = false;\n\n private readonly onClick = (event: Event) => this.handleClick(event);\n private readonly onMouseOver = (event: Event) => this.handleMouseOver(event);\n private readonly onMouseMove = (event: Event) => this.handleMouseOver(event);\n\n constructor(opts: ElementPickingOpts) {\n this.queue = opts.queue;\n this.createReport = opts.createReport;\n this.store = opts.store ?? new MarkerStore();\n this.onPick = opts.onPick ?? null;\n this.getPathname = opts.getPathname ?? defaultPathname;\n this.doc = opts.doc === undefined ? defaultDocument() : opts.doc;\n this.getViewport = opts.getViewport ?? defaultViewport;\n this.capture = opts.capture ?? null;\n this.reencode = opts.reencode ?? null;\n this.screenshotLimitBytes = opts.screenshotLimitBytes;\n this.rankHintsFor = opts.rankHintsFor ?? null;\n this.watchRoutes = opts.watchRoutes === undefined ? webRouteWatcher : opts.watchRoutes;\n this.lastPathname = this.getPathname();\n this.markers = this.store.list(this.lastPathname);\n this.unsubscribeQueue =\n this.queue.subscribe?.(() => {\n if (this.reconcileMarkerOutcomes()) this.emit();\n }) ?? (() => undefined);\n }\n\n // ── 조회 ──────────────────────────────────────────────────────────────────\n\n getState(): PickingState {\n return {\n active: this.active,\n paused: this.paused,\n hovered: this.hovered,\n popup: this.popup,\n markers: this.markers,\n };\n }\n\n get isActive(): boolean {\n return this.active;\n }\n\n /** 켜져 있지만 클릭을 안 먹는 상태인가. */\n get isPaused(): boolean {\n return this.paused;\n }\n\n subscribe(listener: PickingListener): () => void {\n this.listeners.add(listener);\n listener(this.getState());\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n // ── 모드 on/off ───────────────────────────────────────────────────────────\n\n start(): void {\n if (this.active) return;\n this.active = true;\n this.paused = false;\n this.store.setPickingActive(true);\n this.attach();\n this.startPathWatch();\n this.markers = this.store.list(this.getPathname());\n this.emit();\n }\n\n /** [지목 종료]. 저장된 플래그까지 지워서 새로고침해도 다시 켜지지 않게 한다. */\n stop(): void {\n this.active = false;\n this.paused = false;\n this.store.setPickingActive(false);\n this.detach();\n this.stopPathWatch();\n this.hoveredTarget = null;\n this.hovered = null;\n this.popup = null;\n this.emit();\n }\n\n /**\n * 잠시 멈춘다 — 페이지 클릭이 원래대로 동작하고, 모드는 켜진 채로 남는다.\n *\n * 팝업이 열려 있으면 **그대로 둔다.** 쓰던 한 줄을 여기서 지우면 「지목 켜면 모달 초안이\n * 날아간다」와 같은 사고를 다른 자리에 만드는 셈이다. 팝업은 위젯 자신의 UI 라\n * 리스너를 떼도 계속 눌린다.\n */\n pause(): void {\n if (!this.active || this.paused) return;\n this.paused = true;\n this.detach();\n this.hoveredTarget = null;\n this.hovered = null; // 멈춘 동안 하이라이트가 남아 있으면 아직 먹는 줄로 읽힌다\n this.emit();\n }\n\n /** 다시 지목을 받는다. */\n resume(): void {\n if (!this.active || !this.paused) return;\n this.paused = false;\n this.attach();\n this.emit();\n }\n\n togglePause(): void {\n if (this.paused) this.resume();\n else this.pause();\n }\n\n /**\n * 저장돼 있던 모드를 되살린다. 새로고침·페이지 이동 직후에 한 번 부른다.\n * @returns 되살아났으면 true.\n */\n restore(): boolean {\n if (!this.store.isPickingActive()) {\n this.markers = this.store.list(this.getPathname());\n this.emit();\n return false;\n }\n this.start();\n return true;\n }\n\n /**\n * 이 화면에 쌓인 마커 표시를 지운다.\n *\n * 지워지는 건 **화면 표시뿐**이다. 제보는 이미 큐를 거쳐 수집처로 갔으므로 없어지지 않는다.\n * 마커는 `localStorage` 에 남아 새로고침에도 살아남는데(그게 원래 목적이다) 정작 치울\n * 수단이 없어서, 한 번 보낸 [완료] 배지가 그 화면을 볼 때마다 계속 따라다녔다.\n *\n * 화면에 보이는 목록의 기준은 `lastPathname` 이다(`reconcileMarkerOutcomes` 와 같다).\n * 이동 직후 아직 `syncPath` 가 안 돈 순간에도 \"지금 눈에 보이는 것\"이 지워져야 한다.\n */\n clearMarkers(): void {\n if (this.markers.length === 0) return;\n this.store.clear(this.lastPathname);\n this.markers = [];\n this.emit();\n }\n\n /** 경로가 바뀌었을 때 그 경로의 마커로 갈아 끼운다. */\n syncPath(): readonly StoredMarker[] {\n // 멈춘 이유가 대개 \"여길 눌러 다른 화면으로 가려고\" 다. 이동이 끝났으면 그 이유도\n // 끝났으므로 스스로 재개한다 — 안 그러면 이동할 때마다 사용자가 다시 켜야 한다.\n if (this.paused) this.resume();\n this.lastPathname = this.getPathname();\n this.markers = this.store.list(this.lastPathname);\n this.reconcileMarkerOutcomes();\n this.popup = null;\n this.hoveredTarget = null;\n this.hovered = null;\n this.emit();\n return this.markers;\n }\n\n dispose(): void {\n this.detach();\n this.stopPathWatch();\n this.unsubscribeQueue();\n this.listeners.clear();\n }\n\n // ── DOM 이벤트 ────────────────────────────────────────────────────────────\n\n private attach(): void {\n if (this.attached || !this.doc) return;\n // 캡처 단계에서 잡아야 페이지의 핸들러보다 먼저 막을 수 있다.\n this.doc.addEventListener(\"click\", this.onClick, true);\n this.doc.addEventListener(\"mouseover\", this.onMouseOver, true);\n // padding 안에서는 event.target 이 그대로라 mouseover 가 다시 오지 않는다. 포인터가\n // 가까운 콘텐츠의 24px 범위에 들어왔는지 갱신하려면 이동 좌표도 봐야 한다.\n this.doc.addEventListener(\"mousemove\", this.onMouseMove, true);\n this.attached = true;\n }\n\n private detach(): void {\n if (!this.attached || !this.doc) return;\n this.doc.removeEventListener(\"click\", this.onClick, true);\n this.doc.removeEventListener(\"mouseover\", this.onMouseOver, true);\n this.doc.removeEventListener(\"mousemove\", this.onMouseMove, true);\n this.attached = false;\n }\n\n private handleMouseOver(event: Event): void {\n if (!this.active || this.paused || isOwnUi(event.target)) return;\n const mouse = event as MouseEvent;\n const target = resolvePickTarget(event.target as Element | null, {\n x: mouse.clientX ?? 0,\n y: mouse.clientY ?? 0,\n });\n if (target === this.hoveredTarget) return;\n this.hoveredTarget = target;\n this.hovered = describeElement(target);\n this.emit();\n }\n\n private handleClick(event: Event): void {\n if (!this.active || this.paused) return;\n if (isOwnUi(event.target)) return;\n\n // 페이지가 이 클릭을 보지 못하게 한다 — 링크 이동·버튼 동작을 전부 막는다.\n event.preventDefault();\n event.stopPropagation();\n if (typeof event.stopImmediatePropagation === \"function\") {\n event.stopImmediatePropagation();\n }\n\n const mouse = event as MouseEvent;\n this.openAnnotation(event.target as Element | null, {\n x: mouse.clientX ?? 0,\n y: mouse.clientY ?? 0,\n });\n }\n\n // ── 주석 팝업 ─────────────────────────────────────────────────────────────\n\n /** 클릭 지점 기준으로 주석 팝업을 연다. 좌표는 해상도 무관한 상대값으로 접어 둔다. */\n openAnnotation(element: Element | null, clientPoint: { x: number; y: number }): void {\n const point = normalizePin(clientPoint, this.getViewport());\n const target = resolvePickTarget(element, clientPoint);\n if (this.onPick) {\n // 호스트가 모달을 연다. 팝업은 만들지 않는다(둘이 동시에 뜨면 안 된다).\n this.onPick(describeElement(target), point);\n return;\n }\n const info = describeElement(target);\n // 새로 여는 팝업이니 이전 판단은 잊는다 — 다음 판단은 이번 코멘트 값으로 한다.\n this.userToggledHints = false;\n const hints = this.rankHintsFor ? this.rankHintsFor(info) : [];\n this.popup = {\n element: info,\n point,\n comment: \"\",\n canSave: false,\n screenshot: null,\n screenshotStatus: this.capture ? \"capturing\" : \"none\",\n saving: false,\n hints,\n // 코멘트가 비어 있을 때만 펼친다 — 모달과 같은 규칙(계약 원문). 방금 연 팝업은 항상 빈다.\n hintsExpanded: true,\n hintsAvailable: hints.length > 0,\n usedHintIds: [],\n };\n this.emit();\n if (this.capture) this.capturing = this.runCapture();\n }\n\n setAnnotationComment(value: string): void {\n if (!this.popup) return;\n this.popup = {\n ...this.popup,\n comment: value,\n canSave: this.isSavable(value),\n ...this.hintsPatchFor(value),\n };\n this.emit();\n }\n\n /**\n * 코멘트가 비었는지에 따라 칩 줄 상태를 갱신하는 조각. `setAnnotationComment`·`applyHint`\n * 양쪽에서 쓴다 — 모달의 `hintsPatchFor` 와 같은 규칙이다.\n */\n private hintsPatchFor(nextComment: string): Partial<AnnotationPopupState> {\n if (nextComment.trim().length === 0) {\n return { hintsExpanded: true, usedHintIds: [] };\n }\n return this.userToggledHints ? {} : { hintsExpanded: false };\n }\n\n /**\n * 칩 누름. 이미 눌린 칩이면 아무 일도 하지 않는다. 코멘트가 비어 있으면 초안으로 치환,\n * 아니면 줄바꿈 뒤 이어붙인다 — 모달의 `applyHint` 와 동일한 규칙이다.\n */\n applyHint(hintId: string): void {\n if (!this.popup || this.popup.saving) return;\n if (this.popup.usedHintIds.includes(hintId)) return;\n const hint = this.popup.hints.find((h) => h.id === hintId);\n if (!hint) return;\n const comment = this.popup.comment.length === 0\n ? hint.draft\n : `${this.popup.comment}\\n${hint.draft}`;\n this.popup = {\n ...this.popup,\n comment,\n canSave: this.isSavable(comment),\n usedHintIds: [...this.popup.usedHintIds, hintId],\n ...this.hintsPatchFor(comment),\n };\n this.emit();\n }\n\n /** [이런 건가요? ▾] 토글. 방향과 무관하게 이후 자동 접힘/펼침을 멈춘다. */\n toggleHints(): void {\n if (!this.popup) return;\n this.userToggledHints = true;\n this.popup = { ...this.popup, hintsExpanded: !this.popup.hintsExpanded };\n this.emit();\n }\n\n /**\n * 사용자가 붙여넣기(Cmd+V)나 드래그로 넣은 그림. 자동 캡처 결과를 덮는다.\n *\n * 자동 캡처보다 이게 우선인 이유: 사용자가 굳이 그림을 붙였다면 그건 \"지금 화면\"이\n * 아니라 **보여주고 싶은 다른 것**이다(잘라낸 부분, 다른 탭, 기대하는 디자인).\n */\n async attachAnnotationImage(shot: FeedbackScreenshot): Promise<void> {\n if (!this.popup || this.popup.saving) return;\n // 붙여넣은 그림도 8 MiB 정책을 통과해야 한다. 통과 못 하면 실패로 표시하고 그림은 비운다.\n const generation = ++this.shotGeneration;\n const outcome = await captureWithinLimit({\n capture: () => shot,\n reencode: this.reencode,\n limitBytes: this.screenshotLimitBytes,\n });\n if (generation !== this.shotGeneration || !this.popup) return;\n this.popup = {\n ...this.popup,\n screenshot: outcome.failed ? null : outcome.screenshot,\n screenshotStatus: outcome.failed || !outcome.screenshot ? \"failed\" : \"ready\",\n };\n this.emit();\n }\n\n /** 그림만 뗀다. 코멘트는 그대로 — 그림이 없어도 주석은 성립한다. */\n removeAnnotationImage(): void {\n if (!this.popup || this.popup.saving) return;\n this.shotGeneration += 1; // 진행 중인 캡처가 되살리지 못하게\n this.popup = { ...this.popup, screenshot: null, screenshotStatus: \"none\" };\n this.emit();\n }\n\n /** 취소 — 마커도 제보도 남기지 않는다. 모드는 켜진 채로 둔다. */\n cancelAnnotation(): void {\n this.shotGeneration += 1;\n this.popup = null;\n this.emit();\n }\n\n private isSavable(comment: string): boolean {\n return comment.trim().length > 0 && comment.length <= COMMENT_MAX_CHARS;\n }\n\n /** 도는 캡처가 있으면 끝나기를 기다린다. 캡처가 던져도 저장을 막지 않는다. */\n private async settleCapture(): Promise<void> {\n try {\n await this.capturing;\n } catch {\n /* 캡처 실패는 그림 없음일 뿐이다 */\n }\n }\n\n private async runCapture(): Promise<void> {\n if (!this.capture) return;\n const generation = ++this.shotGeneration;\n const outcome = await captureWithinLimit({\n capture: this.capture,\n reencode: this.reencode,\n limitBytes: this.screenshotLimitBytes,\n });\n // 그 사이 팝업이 닫혔거나 다른 요소를 지목했으면 결과를 버린다.\n if (generation !== this.shotGeneration || !this.popup) return;\n this.popup = {\n ...this.popup,\n screenshot: outcome.failed ? null : outcome.screenshot,\n screenshotStatus: outcome.failed || !outcome.screenshot ? \"failed\" : \"ready\",\n };\n this.emit();\n }\n\n /**\n * 제출된 제보를 마커로 남긴다. 모달 경로에서 쓴다 — 마커 생성이 팝업 저장 안에만\n * 있으면, 리포트 모달로 보낸 지목 제보는 화면에 흔적이 남지 않는다(\"보냈는지 알 수\n * 없다\"가 이 모드가 없애려던 불편이다).\n */\n noteSubmitted(report: FeedbackReport, delivered: boolean): void {\n if (!report.element && !report.pin) return;\n const pathname = this.getPathname();\n const marker: StoredMarker = {\n id: report.clientSubmissionId,\n x: report.pin?.x ?? 0,\n y: report.pin?.y ?? 0,\n selector: report.element?.selector ?? null,\n comment: report.comment,\n status: delivered ? \"done\" : \"pending\",\n at: report.createdAt,\n };\n this.markers = this.store.upsert(pathname, marker);\n this.emit();\n }\n\n /**\n * 주석 저장. 마커를 먼저 \"전송 중\"으로 찍고 나서 보낸다 — 사용자는 결과를 기다리지 않고\n * 다음 요소로 넘어갈 수 있어야 한다.\n *\n * @returns 검증에 걸려 아무것도 하지 않았으면 `null`.\n */\n async saveAnnotation(): Promise<SubmitOutcome | null> {\n const popup = this.popup;\n if (!popup || !popup.canSave || this.saving) return null;\n this.saving = true;\n // 캡처가 아직 도는 중일 수 있다. Enter 를 눌렀다고 그림을 잃지 않게 여기서 기다린다 —\n // 사용자가 한 줄 쓰는 동안 대개 끝나므로 실제로 기다리는 시간은 거의 없다.\n if (popup.screenshotStatus === \"capturing\") await this.settleCapture();\n // 기다리는 사이 취소됐으면 아무것도 보내지 않는다.\n const settled = this.popup;\n if (!settled) {\n this.saving = false;\n return null;\n }\n this.popup = { ...settled, saving: true };\n this.emit();\n\n const pathname = this.getPathname();\n const parts: ReportParts = {\n kind: \"annotation\",\n comment: settled.comment,\n priority: \"unset\",\n screenshot: settled.screenshot,\n pin: settled.point,\n element: settled.element,\n // 눌린 칩 기록. 리포트 모달과 같은 필드 — 들어온 문이 달라도 채택 신호는 같은 값으로 쌓인다.\n hintIds: settled.usedHintIds,\n };\n\n let report: FeedbackReport;\n try {\n report = await this.createReport(parts);\n } catch {\n // 조립이 실패하면 남길 마커의 id 도 없다. 팝업만 닫고 조용히 물러난다.\n this.popup = null;\n this.emit();\n this.saving = false;\n return null;\n }\n\n const marker: StoredMarker = {\n id: report.clientSubmissionId,\n x: settled.point.x,\n y: settled.point.y,\n selector: settled.element?.selector ?? null,\n comment: settled.comment,\n status: \"sending\",\n at: report.createdAt,\n };\n this.markers = this.store.upsert(pathname, marker);\n this.popup = null; // 모드는 그대로 — 다음 요소를 바로 지목할 수 있다.\n this.emit();\n\n let outcome: SubmitOutcome;\n try {\n outcome = await this.queue.submit(report);\n } catch {\n outcome = { delivered: false, id: null, queued: true };\n }\n\n const next = this.store.updateStatus(\n pathname,\n marker.id,\n outcome.delivered ? \"done\" : \"pending\",\n );\n // 저장 중에 경로가 바뀌었으면 화면에 보이는 목록은 건드리지 않는다.\n if (pathname === this.getPathname()) this.markers = next;\n this.emit();\n\n this.saving = false;\n return outcome;\n }\n\n private startPathWatch(): void {\n if (this.unwatchRoutes !== null || !this.watchRoutes) return;\n this.lastPathname = this.getPathname();\n // 감시자가 흘리는 경로를 그대로 쓰지 않고 `getPathname()` 을 다시 묻는다 — 호스트가\n // 경로 기준을 바꿔 끼웠을 수 있고(마커는 그 기준으로 저장돼 있다), 구독 즉시 오는\n // 첫 통지가 지금 경로라 이 비교 덕에 아무 일도 일어나지 않는다.\n this.unwatchRoutes = this.watchRoutes(() => {\n const pathname = this.getPathname();\n if (pathname !== this.lastPathname) this.syncPath();\n });\n }\n\n private stopPathWatch(): void {\n if (this.unwatchRoutes === null) return;\n this.unwatchRoutes();\n this.unwatchRoutes = null;\n }\n\n /** 전역 pending 수가 아니라 마커와 같은 clientSubmissionId의 확정 성공만 완료로 바꾼다. */\n private reconcileMarkerOutcomes(): boolean {\n if (!this.queue.getOutcome) return false;\n let changed = false;\n for (const marker of this.markers) {\n const outcome = this.queue.getOutcome(marker.id);\n if (marker.status !== \"done\" && outcome?.delivered) {\n this.markers = this.store.updateStatus(this.lastPathname, marker.id, \"done\");\n changed = true;\n }\n }\n return changed;\n }\n\n private emit(): void {\n const state = this.getState();\n for (const listener of this.listeners) listener(state);\n }\n}\n","// 웹 경로 감시자.\n//\n// SPA 는 페이지를 다시 로드하지 않고 화면을 바꾼다. 그래서 \"지금 어느 화면인가\"를 알려면\n// `history.pushState`/`replaceState` 와 `popstate`(뒤로/앞으로) 를 **함께** 봐야 한다.\n// 셋 중 하나라도 빠지면 그 방식으로 이동한 화면을 통째로 놓친다.\n//\n// **이 감시자는 한 곳이다.** 화면 이동 궤적(진단)과 요소 지목 모드의 경로 추적이 같은 것을 쓴다.\n// 따로 구현하면 한쪽이 이동을 놓쳤을 때 \"감시가 안 붙었나 / 이 경로만 다르게 도나\"를 가릴 수\n// 없다. 실제로 지목 쪽은 200ms 폴링이었어서, 이동 직후 200ms 안에 벌어진 일은 옛 경로로 찍혔다.\n//\n// 코어는 이 파일을 모른다(`window` 를 보므로 웹 전용이다). 코어에는 구독 함수 하나\n// (`RouteWatcher`)만 주입된다.\n\nimport type { RouteTrailStore, RouteWatcher } from \"@solhun/feedback-kit-core\";\n\n/** 감시자가 들여다보는 전역. 테스트에서는 가짜를 넣는다. */\nexport interface RouteWatchTarget {\n location?: { pathname?: string } | null;\n history?: {\n pushState?: (...args: unknown[]) => unknown;\n replaceState?: (...args: unknown[]) => unknown;\n } | null;\n addEventListener?: (type: string, listener: () => void) => void;\n removeEventListener?: (type: string, listener: () => void) => void;\n}\n\nfunction defaultTarget(): RouteWatchTarget | null {\n const g = globalThis as RouteWatchTarget;\n // SSR·비브라우저에서는 감시할 게 없다. null 을 돌려주면 구독은 성립하되 아무것도 안 흐른다\n // (= 궤적만 비고 나머지 수집은 그대로) — 여기서 던지면 서버 렌더가 깨진다.\n return g.location ? g : null;\n}\n\n/**\n * 경로 감시자 하나를 만든다.\n *\n * 돌려주는 것은 **구독 함수**다. 구독하면 현재 경로를 한 번 흘리고(마운트 시점의 화면이\n * 궤적의 첫 줄이 된다), 이후 이동마다 새 경로를 흘린다. 해제 함수를 부르면 구독이 끊긴다.\n *\n * `history` 패치는 **첫 구독자가 생길 때 걸고 마지막이 나가면 푼다.** 구독자가 없는데 패치가\n * 남아 있으면, 위젯을 떼어낸 뒤에도 호스트의 모든 화면 이동이 우리 코드를 거친다.\n */\nexport function createRouteWatcher(target: RouteWatchTarget | null = defaultTarget()): RouteWatcher {\n const listeners = new Set<(path: string) => void>();\n let detach: (() => void) | null = null;\n\n const currentPath = (): string => {\n const p = target?.location?.pathname;\n return typeof p === \"string\" && p.length > 0 ? p : \"/\";\n };\n\n const emit = (): void => {\n const path = currentPath();\n // 사본을 돌린다 — 구독자가 콜백 안에서 해제하면 순회 중 집합이 바뀐다.\n for (const listener of [...listeners]) {\n try {\n listener(path);\n } catch {\n // 한 구독자가 던져도 나머지는 받아야 한다. 이동 기록이 남의 예외로 멈추면 안 된다.\n }\n }\n };\n\n const attach = (): void => {\n if (detach !== null || !target) return;\n\n const onPop = (): void => emit();\n target.addEventListener?.(\"popstate\", onPop);\n\n const history = target.history;\n const originalPush = typeof history?.pushState === \"function\" ? history.pushState : null;\n const originalReplace = typeof history?.replaceState === \"function\" ? history.replaceState : null;\n\n if (history && originalPush) {\n history.pushState = function patched(this: unknown, ...args: unknown[]): unknown {\n // 원본을 **먼저** 부른다. 그래야 emit 시점의 location.pathname 이 새 경로다.\n const out = originalPush.apply(this, args);\n emit();\n return out;\n };\n }\n if (history && originalReplace) {\n history.replaceState = function patched(this: unknown, ...args: unknown[]): unknown {\n const out = originalReplace.apply(this, args);\n emit();\n return out;\n };\n }\n\n detach = (): void => {\n target.removeEventListener?.(\"popstate\", onPop);\n if (history && originalPush) history.pushState = originalPush;\n if (history && originalReplace) history.replaceState = originalReplace;\n };\n };\n\n return (onPath: (path: string) => void): (() => void) => {\n listeners.add(onPath);\n if (listeners.size === 1) attach();\n\n // 구독 즉시 현재 경로를 흘린다 — 이게 없으면 궤적이 \"두 번째 화면\"부터 시작해서\n // 정작 어디서 출발했는지가 빈다.\n try {\n onPath(currentPath());\n } catch {\n // 첫 통지가 실패해도 구독 자체는 유지한다.\n }\n\n return (): void => {\n listeners.delete(onPath);\n if (listeners.size === 0 && detach) {\n detach();\n detach = null;\n }\n };\n };\n}\n\n/**\n * 웹 기본 감시자. **모듈 스코프에 하나**라서 궤적과 지목이 같은 인스턴스를 물려 쓴다.\n * 각자 `createRouteWatcher()` 를 부르면 `history` 가 두 겹으로 패치돼, 해제 순서가 엇갈릴 때\n * 원본이 복구되지 않는다.\n */\nexport const webRouteWatcher: RouteWatcher = createRouteWatcher();\n\n/**\n * 궤적을 남길 탭 단위 저장소(`sessionStorage`).\n *\n * `localStorage` 가 아닌 이유: 궤적은 **이 탭에서 지금 지나온 길**이다. 탭을 나눠 쓰면\n * 다른 탭의 이동이 섞여 재현 절차가 거짓이 된다.\n *\n * 프라이빗 모드·차단 설정에서는 접근 자체가 던진다. 그때는 `null` 을 돌려주고 궤적은\n * 메모리로 떨어진다 — 저장을 못 한다고 제보를 막지 않는다.\n */\nexport function webRouteStorage(): RouteTrailStore | null {\n try {\n const s = (globalThis as { sessionStorage?: RouteTrailStore | null }).sessionStorage;\n if (!s || typeof s.getItem !== \"function\" || typeof s.setItem !== \"function\") return null;\n // 있다고 쓸 수 있는 건 아니다(사파리 프라이빗 모드는 setItem 에서 던진다). 한 번 찔러 본다.\n s.getItem(\"feedback-kit:probe\");\n return s;\n } catch {\n return null;\n }\n}\n","import type { ScreenshotCapture, ScreenshotReencode } from \"@solhun/feedback-kit-core\";\nimport html2canvas from \"html2canvas-pro\";\n\nimport { OWN_UI_ATTR } from \"./picking.js\";\n\n/**\n * 왜 라이브러리를 쓰나 (직접 만든 SVG foreignObject 방식에서 갈아탄 이유):\n *\n * `<foreignObject>` 로 DOM 을 감싼 SVG 를 캔버스에 그리면 **캔버스가 tainted 되어**\n * `toDataURL()` 이 `SecurityError` 를 던진다. 즉 캡처가 항상 null 이 되고, 사용자는\n * 매번 \"캡처 실패 — 파일로 첨부할 수 있습니다\"만 본다. 실측으로 확인했다:\n * 같은 브라우저에서 일반 SVG(rect)는 오염되지 않고 `foreignObject` 만 오염된다.\n *\n * html2canvas-pro(MIT)는 foreignObject 를 쓰지 않는다. DOM 을 순회하며 캔버스에\n * 직접 그리므로 오염 자체가 생기지 않는다. 명세의 \"DOM 을 이미지로 변환하는 MIT\n * 라이브러리를 쓴다\"도 원래 이 뜻이었다.\n */\n\nfunction payload(dataUrl: string): string | null {\n const comma = dataUrl.indexOf(\",\");\n return comma >= 0 ? dataUrl.slice(comma + 1) : null;\n}\n\n/** 캡처에서 빼야 할 노드인가(위젯 자신의 UI). 결과에 자기 모달이 찍히면 안 된다. */\nexport function isOwnUi(node: Element): boolean {\n return node.hasAttribute?.(OWN_UI_ATTR) === true\n || node.closest?.(`[${OWN_UI_ATTR}]`) !== null;\n}\n\n/**\n * 웹 기본 캡처 — 현재 viewport 를 JPEG 로.\n *\n * 실패하면 **null 을 돌려준다**(던지지 않는다). 코어가 \"캡처 실패\" 상태로 바꾸고\n * 사용자는 파일로 첨부할 수 있다 — 그림 때문에 제보 자체를 잃지 않는다.\n */\n/**\n * 캔버스를 같은 크기의 <img> 로 바꾼다.\n *\n * html2canvas 는 <canvas> 의 픽셀은 가져오지만 **배치를 잘못 잡는 경우가 있다.**\n * 실측: KITS 홈의 Rive 캔버스(700×933, 왼쪽 x=0)가 캡처본에서는 가운데로 옮겨져\n * 본문 텍스트와 버튼을 덮었다. 원본 화면과 캡처를 나란히 놓고서야 보였다.\n *\n * 캔버스를 이미지로 바꾸면 일반 요소로 배치되므로 위치가 어긋나지 않는다.\n * 픽셀을 못 읽는 캔버스(WebGL 에서 preserveDrawingBuffer 미설정, 오염 등)는\n * 원본을 그대로 둔다 — 자리를 비우는 것보다 어긋나더라도 뭔가 보이는 편이 낫다.\n */\nfunction freezeCanvases(source: Document, clone: Document): void {\n const originals = source.querySelectorAll(\"canvas\");\n const clones = clone.querySelectorAll(\"canvas\");\n for (let index = 0; index < originals.length && index < clones.length; index++) {\n const from = originals[index];\n const to = clones[index];\n let dataUrl: string;\n try {\n dataUrl = from.toDataURL(\"image/png\");\n } catch {\n continue;\n }\n if (!dataUrl.startsWith(\"data:image\") || dataUrl.length < 64) continue;\n\n const box = from.getBoundingClientRect();\n if (box.width < 1 || box.height < 1) continue;\n\n const image = clone.createElement(\"img\");\n image.src = dataUrl;\n // 원래 캔버스가 받던 레이아웃을 그대로 물려받게 한다.\n image.setAttribute(\"style\", to.getAttribute(\"style\") ?? \"\");\n image.style.width = `${box.width}px`;\n image.style.height = `${box.height}px`;\n image.className = to.className;\n to.replaceWith(image);\n }\n}\n\nexport const captureWebScreenshot: ScreenshotCapture = async () => {\n const doc = globalThis.document;\n const view = doc?.defaultView;\n if (!doc?.documentElement || !view) return null;\n\n try {\n const canvas = await html2canvas(doc.documentElement, {\n // 현재 보이는 화면만. 문서 전체를 그리면 제보와 무관한 영역까지 커진다.\n width: view.innerWidth,\n height: view.innerHeight,\n x: view.scrollX,\n y: view.scrollY,\n windowWidth: view.innerWidth,\n windowHeight: view.innerHeight,\n // 배율은 2배까지만. 그 위는 용량만 커지고 8 MiB 한도에 먼저 걸린다.\n scale: Math.min(2, Math.max(1, view.devicePixelRatio || 1)),\n // 위젯 자신의 UI 는 결과에서 뺀다.\n ignoreElements: isOwnUi,\n // 캔버스(Rive·차트 등)는 이미지로 바꿔 넘긴다 — freezeCanvases 주석 참고.\n onclone: (cloned: Document) => freezeCanvases(doc, cloned),\n // 외부 이미지가 CORS 를 안 열어두면 그것 때문에 전체가 실패할 수 있다.\n // 못 가져오는 리소스는 건너뛰고 나머지를 그린다.\n useCORS: true,\n allowTaint: false,\n logging: false,\n backgroundColor: null,\n });\n return { base64: payload(canvas.toDataURL(\"image/jpeg\", 0.9)) ?? \"\", contentType: \"image/jpeg\" };\n } catch {\n return null;\n }\n};\n\n/** 8 MiB 정책이 요구할 때 기존 이미지를 JPEG 품질 단계로 다시 인코딩한다. */\nexport const reencodeWebScreenshot: ScreenshotReencode = async (shot, quality) => {\n const doc = globalThis.document;\n if (!doc) return null;\n try {\n const image = await new Promise<HTMLImageElement | null>((resolve) => {\n const img = new Image();\n img.onload = () => resolve(img);\n img.onerror = () => resolve(null);\n img.src = `data:${shot.contentType};base64,${shot.base64}`;\n });\n if (!image) return null;\n const canvas = doc.createElement(\"canvas\");\n canvas.width = image.naturalWidth || image.width;\n canvas.height = image.naturalHeight || image.height;\n const context = canvas.getContext(\"2d\");\n if (!context) return null;\n context.drawImage(image, 0, 0);\n const base64 = payload(canvas.toDataURL(\"image/jpeg\", quality));\n return base64 ? { base64, contentType: \"image/jpeg\" } : null;\n } catch {\n return null;\n }\n};\n","// 웹 위젯 조립.\n//\n// 코어의 화면 지도(`WidgetController`)와 웹 전용 지목 모드(`ElementPickingController`)를\n// 한 덩어리로 묶는다. 둘을 따로 두면 \"모달에서 [요소 지목]을 눌렀다\"와 \"지목 오버레이가 떴다\"가\n// 서로 모르는 상태가 되므로, 연결 지점을 여기 한 곳으로 모은다.\n//\n// 지목 모드 on/off 의 유일한 입구는 `widget.startPicking()` / `widget.stopPicking()` 이다.\n// 지목 컨트롤러를 직접 켜면 화면 지도가 그 사실을 모른다.\n\nimport {\n WidgetController,\n HintProvider,\n rankHints,\n type WidgetControllerOpts,\n type ElementInfo,\n type FeedbackAdapter,\n type FeedbackHint,\n type HintRankInput,\n type ModalQueueLike,\n} from \"@solhun/feedback-kit-core\";\nimport { MarkerStore } from \"./marker-store.js\";\nimport { ElementPickingController } from \"./picking.js\";\nimport { captureWebScreenshot, reencodeWebScreenshot } from \"./screenshot.js\";\nimport { createWebStorage } from \"./storage.js\";\n\n/**\n * 요소를 고른 뒤 무엇을 띄우는가.\n *\n * - `popup`(기본): 클릭한 자리에 **한 줄 입력창**. Enter 로 바로 보낸다.\n * - `modal`: 화면 중앙의 전체 리포트 모달(우선순위까지 고른다).\n *\n * 기본이 팝업인 이유: 지목은 \"여기 이거요\" 를 여러 곳에 연달아 남기는 작업이다.\n * 클릭마다 중앙 모달이 뜨면 시선이 클릭 지점을 떠나고, 어디를 가리켰는지 잊는다.\n */\nexport type PickTarget = \"popup\" | \"modal\";\n\nexport interface WebWidgetOpts\n extends Omit<WidgetControllerOpts, \"platform\" | \"initialPicking\" | \"onPickingChange\"> {\n /** 지목 모드 플래그와 마커를 담아 둘 저장소. 기본은 `localStorage`. */\n store?: MarkerStore;\n /** 현재 경로. 마커를 경로별로 나눠 담는 기준. */\n getPathname?: () => string;\n /** 지목 이벤트를 들을 문서. 기본은 전역 `document`. */\n doc?: Document | null;\n getViewport?: () => { width: number; height: number };\n /** 요소를 고른 뒤 띄울 UI. 기본은 `popup`. */\n pickTarget?: PickTarget;\n /**\n * 플로팅 버튼을 눌렀을 때 무엇이 열리는가. **웹 기본은 `\"pick\"`(요소 지목)** 이다.\n *\n * 웹 제보는 대개 \"이 요소요\" 라서, 지목을 매번 켜게 하면 그 한 단계가 계속 남는다. 0.8.0\n * 까지는 모달이 기본이었으므로, 그 동작을 유지하려면 `\"report\"` 를 준다.\n *\n * 앱에는 지목이 없어 이 설정이 없다(앱은 언제나 모달이다) — 없는 기능을 켤 수는 없다.\n */\n defaultMode?: \"pick\" | \"report\";\n /**\n * 개발용 모드. 제보를 **수집처로 보내지 않고 클립보드에 복사**한다.\n *\n * 개발자가 로컬에서 고치는 중일 때 라쏘런 왕복(보내고·트리아지하고·이슈가 되고)이 오히려\n * 느리다. 정작 고칠 사람은 지금 그 화면을 보고 있는 본인이다.\n *\n * **기본은 꺼짐이고 마운트 시점에 고정한다** — 도는 중에 바뀌면 \"지금 보낸 건 어디로 갔나\"가\n * 생긴다. 켜져 있는 동안은 그 사실이 화면에 계속 보여야 한다(조용한 유실을 막는 유일한 방어).\n *\n * 웹 전용이다. 앱에는 DOM 이 없어 선택자·컴포넌트 경로·소스 경로가 성립하지 않는다.\n */\n devMode?: boolean;\n /**\n * 정적 힌트 목록(설정으로 직접 넘김). 주면 서버에서 받아오지 않는다 — 도입 초기·오프라인\n * 데모·테스트용이다. 서버 목록과 합치지 않는다(둘을 섞으면 무엇이 보이는지 설명이 안 된다).\n */\n hints?: readonly FeedbackHint[];\n /** 힌트 조회 컨트롤러 주입(테스트용). 주면 큐 어댑터의 fetchHints 를 보지 않는다. */\n hintProvider?: HintProvider;\n}\n\nexport interface WebWidget {\n widget: WidgetController;\n picking: ElementPickingController;\n store: MarkerStore;\n dispose(): void;\n}\n\n/** 경로에서 쿼리스트링·해시를 뺀다. 같은 화면이 쿼리 값 때문에 다른 화면으로 갈리지 않게 한다. */\nfunction stripQueryHash(path: string): string {\n const cut = [path.indexOf(\"?\"), path.indexOf(\"#\")].filter((i) => i >= 0);\n return cut.length > 0 ? path.slice(0, Math.min(...cut)) : path;\n}\n\nfunction defaultPathname(): string {\n const loc = (globalThis as { location?: { pathname?: string } }).location;\n return loc?.pathname ?? \"/\";\n}\n\n/**\n * 큐가 `adapter` 를 노출하면 그 힌트 조회 함수를 꺼낸다.\n *\n * `queue` 타입은 `ModalQueueLike`(제출 최소 계약)라 `adapter` 가 없다 — 실제로 오는 건\n * `FeedbackQueue` 인스턴스이고 그쪽엔 `readonly adapter` 가 공개돼 있어 좁혀서 읽는다.\n * 테스트 대역처럼 `adapter` 가 없는 큐가 와도 힌트 기능만 조용히 꺼질 뿐 제보 전송에는\n * 영향이 없다.\n */\nfunction hintSourceFrom(\n queue: ModalQueueLike\n): { fetchHints: NonNullable<FeedbackAdapter[\"fetchHints\"]>; cacheKey: string } | null {\n const adapter = (queue as ModalQueueLike & { adapter?: FeedbackAdapter }).adapter;\n if (!adapter || typeof adapter.fetchHints !== \"function\") return null;\n const cacheKey = adapter.hintsCacheKey ?? \"default\";\n return { fetchHints: adapter.fetchHints.bind(adapter), cacheKey };\n}\n\n/** 정적 목록 > 테스트 주입 > 큐 어댑터 순으로 힌트 공급자를 고른다. 셋 다 없으면 기능이 꺼진다. */\nfunction resolveHintProvider(opts: WebWidgetOpts): HintProvider | null {\n if (opts.hintProvider) return opts.hintProvider;\n if (opts.hints) {\n // 정적 목록은 cacheKey 가 뜻이 없다(저장소·네트워크를 아예 안 본다) — 값만 채운다.\n return new HintProvider({ source: null, staticHints: opts.hints, cacheKey: \"static\" });\n }\n const found = hintSourceFrom(opts.queue);\n if (!found) return null;\n return new HintProvider({\n source: { fetchHints: (etag) => found.fetchHints(etag) },\n storage: createWebStorage(),\n cacheKey: found.cacheKey,\n });\n}\n\n/**\n * 클릭 지점의 퀵 입력창에서 보여줄 칩 개수 상한. 리포트 모달(기본 4, 최대 6)보다 좁게 잡는다 —\n * 팝업은 클릭 지점 옆의 332px짜리 카드라서, 카탈로그의 maxDisplay 를 그대로 쓰면 줄바꿈이\n * 심해져 정작 코멘트란이 화면 밖으로 밀린다. 카탈로그 쪽 maxDisplay 가 이보다 작으면 그 값을\n * 따른다(rankFor 참고) — 여기서는 상한만 정한다.\n */\nconst POPUP_HINT_DISPLAY_MAX = 3;\n\n/**\n * 이 시점의 카탈로그로 랭킹한다. 모달을 열 때와 퀵 팝업을 열 때 **같은 계산**을 쓰기 위한\n * 한 곳 — 따로 구현하면 두 문이 다른 순위를 보여주는 사고가 난다.\n * @param limitOverride 팝업처럼 화면이 좁아 더 적게 보여줘야 할 때만 준다. 카탈로그의\n * `maxDisplay` 보다 크게는 못 넘는다(프로젝트가 정한 상한이 우선).\n */\nfunction rankFor(\n hintProvider: HintProvider,\n opts: WebWidgetOpts,\n element: ElementInfo | null,\n limitOverride?: number\n): readonly FeedbackHint[] {\n const catalog = hintProvider.catalog();\n if (!catalog) return [];\n const input: HintRankInput = {\n platform: \"web\",\n path: stripQueryHash(opts.getPathname ? opts.getPathname() : defaultPathname()),\n element,\n components: element ? (element.attributes[\"react-components\"] ?? null) : null,\n };\n const limit = limitOverride !== undefined ? Math.min(limitOverride, catalog.maxDisplay) : catalog.maxDisplay;\n return rankHints(catalog.hints, input, limit);\n}\n\nexport function createWebWidget(opts: WebWidgetOpts): WebWidget {\n const store = opts.store ?? new MarkerStore();\n // widget 이 아래에서 만들어지므로, 콜백이 불릴 때 참조할 수 있게 늦게 채운다.\n let widgetRef: WidgetController | null = null;\n const capture = opts.capture === undefined ? captureWebScreenshot : opts.capture;\n const reencode = opts.reencode === undefined ? reencodeWebScreenshot : opts.reencode;\n\n // 힌트(자주 나오는 제보 제안). 셋 다 없으면(정적 목록도, 테스트 주입도, 큐 어댑터도)\n // 기능이 조용히 꺼진다 — 제안은 거들 뿐이라 이것 때문에 위젯이 막히면 안 된다.\n // 리포트 모달과 퀵 팝업 **둘 다 이 인스턴스 하나를 물려 쓴다** — 같은 카탈로그, 같은\n // rankFor 계산. 따로 만들면 두 문이 다른 제안을 보여주는 사고가 난다.\n const hintProvider = resolveHintProvider(opts);\n if (hintProvider) {\n // 마운트 후 캐시 복원 → 필요하면 네트워크. await 로 붙잡지 않는다 — 모달 열기를 막지 않는다.\n void hintProvider.load().then(() => hintProvider.refresh());\n }\n\n const picking = new ElementPickingController({\n queue: opts.queue,\n createReport: opts.createReport,\n store,\n getPathname: opts.getPathname,\n doc: opts.doc,\n getViewport: opts.getViewport,\n // 퀵 주석도 같은 캡처를 쓴다. 캡처가 위젯 자신의 UI 를 빼주므로 팝업이 찍히지 않는다.\n capture,\n reencode,\n screenshotLimitBytes: opts.screenshotLimitBytes,\n // 기본은 클릭 자리의 퀵 입력창이라 onPick 을 주지 않는다(주면 팝업 대신 그걸 부른다).\n // 모드는 어느 쪽이든 켜진 채로 둬서 한 번 켜고 여러 요소를 연달아 지목할 수 있다.\n onPick: opts.pickTarget === \"modal\"\n ? (element, point) => {\n // modal.open() 을 직접 부르지 않는다 — 그러면 모달 상태만 열리고 화면은\n // picking 에 머물러 아무것도 안 보인다. 화면 전환은 컨트롤러가 한다.\n // 좌표는 **열기 인자로** 넘긴다. 열고 난 뒤 setPin 으로 넣으면 뒤늦게 끝나는\n // 자동 캡처가 그것을 지운다(그래서 지목 좌표가 전부 유실됐다).\n void widgetRef?.openReport({ element, pin: point });\n }\n : undefined,\n rankHintsFor: hintProvider\n ? (element) => rankFor(hintProvider, opts, element, POPUP_HINT_DISPLAY_MAX)\n : null,\n });\n\n const widget = new WidgetController({\n ...opts,\n capture,\n reencode,\n platform: \"web\",\n // 새로고침·페이지 이동 직후에도 켜져 있던 모드를 그대로 이어받는다.\n initialPicking: store.isPickingActive(),\n onPickingChange: (active) => {\n if (active) picking.start();\n else picking.stop();\n },\n });\n\n widgetRef = widget;\n\n // 모달을 여는 모든 경로(플로팅 버튼·지목→모달)가 이 한 곳을 거친다. 열릴 때마다 그 시점의\n // 경로·요소 기준으로 다시 고른다 — 지목 대상을 바꾸면 제안도 바뀌어야 한다(계약 원문).\n const originalOpenReport = widget.openReport.bind(widget);\n widget.openReport = async (openOpts = {}) => {\n if (hintProvider) {\n widget.modal.setHints(rankFor(hintProvider, opts, openOpts.element ?? null));\n }\n await originalOpenReport(openOpts);\n };\n\n // 모달로 보낸 지목 제보도 마커로 남긴다. 마커 생성이 옛 팝업 저장 경로에만 있으면\n // 모달로 보낸 제보는 화면에 흔적이 없다 — \"보냈는지 알 수 없다\"가 이 모드가\n // 없애려던 불편이라, 그게 되살아난다.\n const originalSubmit = widget.submitReport.bind(widget);\n widget.submitReport = async () => {\n const outcome = await originalSubmit();\n const report = widget.modal.getLastReport();\n if (report?.element) picking.noteSubmitted(report, outcome?.delivered === true);\n return outcome;\n };\n\n // 저장돼 있던 모드면 여기서 DOM 리스너까지 다시 붙인다. 꺼져 있으면 마커만 읽고 만다.\n picking.restore();\n\n return {\n widget,\n picking,\n store,\n dispose(): void {\n picking.dispose();\n widget.dispose();\n },\n };\n}\n","// 웹 기본 저장소 — `localStorage` 를 FeedbackStorage 계약에 맞춘다.\n//\n// 왜 패키지가 제공하나: 이게 없으면 붙이는 앱마다 같은 20줄을 다시 쓴다.\n// 코어는 저장소 구현을 모르고(플랫폼 무관), 웹 패키지가 기본값을 준다.\n//\n// localStorage 는 동기지만 계약은 비동기라 Promise 로 감싼다. 사파리 프라이빗\n// 모드처럼 접근 자체가 던지는 환경이 있어서, 실패하면 **메모리로 조용히 물러난다**\n// — 제보를 잃는 것보다 세션 동안만 유지되는 편이 낫다.\n\nimport type { FeedbackStorage } from \"@solhun/feedback-kit-core\";\n\ninterface SyncStorage {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n}\n\nfunction memoryStorage(): SyncStorage {\n const map = new Map<string, string>();\n return {\n getItem: (key) => map.get(key) ?? null,\n setItem: (key, value) => void map.set(key, value),\n removeItem: (key) => void map.delete(key),\n };\n}\n\n/** 접근 가능한 localStorage 를 고른다. 못 쓰면 메모리. */\nfunction pickBacking(): SyncStorage {\n try {\n const ls = (globalThis as { localStorage?: SyncStorage }).localStorage;\n if (!ls) return memoryStorage();\n // 존재만으로는 부족하다 — 프라이빗 모드는 쓰기에서 던진다.\n const probe = \"__feedback_kit_probe__\";\n ls.setItem(probe, \"1\");\n ls.removeItem(probe);\n return ls;\n } catch {\n return memoryStorage();\n }\n}\n\n/**\n * 웹 기본 FeedbackStorage.\n *\n * 쓰기가 쿼터를 넘기면 그대로 던진다 — 큐가 그 신호를 받아 스크린샷을 떼어내고\n * 본문만 남기는 축소 경로를 타기 때문에, 여기서 삼키면 안 된다.\n */\nexport function createWebStorage(backing?: SyncStorage): FeedbackStorage {\n const store = backing ?? pickBacking();\n return {\n async get(key) {\n try {\n return store.getItem(key);\n } catch {\n return null;\n }\n },\n async set(key, value) {\n store.setItem(key, value);\n },\n async remove(key) {\n try {\n store.removeItem(key);\n } catch {\n /* 지우기 실패는 다음 쓰기가 덮으므로 무시한다. */\n }\n },\n };\n}\n","import {\n COMMENT_MAX_CHARS,\n FLOATING_BUTTON_ID,\n MODAL_ACTION_CANCEL,\n MODAL_ACTION_HINT_TOGGLE,\n MODAL_ACTION_PICK,\n MODAL_ACTION_REMOVE_SCREENSHOT,\n MODAL_ACTION_RETRY,\n MODAL_ACTION_SEND,\n MODAL_FIELD_COMMENT,\n MODAL_FIELD_PRIORITY,\n type FeedbackHint,\n type FeedbackPriority,\n type FeedbackScreenshot,\n type WidgetState,\n} from \"@solhun/feedback-kit-core\";\nimport {\n type ChangeEvent,\n type CSSProperties,\n type FormEvent,\n type KeyboardEvent,\n type MouseEvent,\n useEffect,\n useRef,\n useState,\n} from \"react\";\n\nimport type { PickingState } from \"./picking.js\";\nimport { VERSION } from \"./version.js\";\nimport { OWN_UI_ATTR } from \"./picking.js\";\nimport { renderDevClipItem, renderDevClipList, writeToClipboard, type DevClipItem } from \"./dev-clipboard.js\";\nimport { createWebWidget, type WebWidget, type WebWidgetOpts } from \"./widget.js\";\n\n/**\n * 패키지를 붙인 제품의 디자인 시스템을 침범하지 않는 중립 토큰이다.\n * 모든 값은 인라인 스타일로만 쓰여 호스트의 전역 CSS와 충돌하지 않는다.\n */\nconst TOKENS = {\n ink: \"#17202a\",\n muted: \"#667085\",\n surface: \"#ffffff\",\n subtle: \"#f4f6f8\",\n line: \"#d7dce2\",\n accent: \"#315d73\",\n accentHover: \"#274b5d\",\n danger: \"#b42318\",\n warning: \"#8a5a00\",\n success: \"#18794e\",\n shadow: \"0 18px 50px rgba(23, 32, 42, 0.2)\",\n radius: \"14px\",\n} as const;\n\nconst OWN_UI_PROPS = { [OWN_UI_ATTR]: \"\" } as Record<string, string>;\nconst FONT_STACK =\n \"ui-sans-serif, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif\";\n\nexport type FeedbackKitProps = WebWidgetOpts;\n\nconst INITIAL_PICKING_STATE: PickingState = {\n active: false,\n paused: false,\n hovered: null,\n popup: null,\n markers: [],\n};\n\nfunction screenshotSource(screenshot: FeedbackScreenshot): string {\n return `data:${screenshot.contentType};base64,${screenshot.base64}`;\n}\n\nfunction readDataUrl(blob: Blob): Promise<string | null> {\n return new Promise((resolve) => {\n const reader = new FileReader();\n reader.onerror = () => resolve(null);\n reader.onload = () => resolve(typeof reader.result === \"string\" ? reader.result : null);\n reader.readAsDataURL(blob);\n });\n}\n\nfunction base64Of(dataUrl: string): string | null {\n const comma = dataUrl.indexOf(\",\");\n return comma >= 0 ? dataUrl.slice(comma + 1) : null;\n}\n\n/**\n * PNG·JPEG 가 아닌 이미지를 JPEG 로 옮긴다(WebP·AVIF·HEIC 등).\n *\n * 왜 필요한가: 수집 쪽이 받는 형식은 PNG·JPEG 둘뿐인데, 사용자가 붙여넣는 그림의\n * 형식은 **고를 수 없다.** 브라우저·OS·캡처 도구마다 다르다. 형식이 다르다고\n * 조용히 무시하면 사용자는 \"붙여넣기가 안 되는\" 것으로 본다.\n */\nasync function transcodeToJpeg(dataUrl: string): Promise<FeedbackScreenshot | null> {\n const image = await new Promise<HTMLImageElement | null>((resolve) => {\n const img = new Image();\n img.onload = () => resolve(img);\n img.onerror = () => resolve(null);\n img.src = dataUrl;\n });\n if (!image) return null;\n const canvas = document.createElement(\"canvas\");\n canvas.width = image.naturalWidth || image.width;\n canvas.height = image.naturalHeight || image.height;\n if (canvas.width < 1 || canvas.height < 1) return null;\n const context = canvas.getContext(\"2d\");\n if (!context) return null;\n context.drawImage(image, 0, 0);\n try {\n const base64 = base64Of(canvas.toDataURL(\"image/jpeg\", 0.9));\n return base64 ? { base64, contentType: \"image/jpeg\" } : null;\n } catch {\n // 오염된 캔버스 등. 그림 하나 때문에 제보를 막지 않는다.\n return null;\n }\n}\n\n/** 파일·클립보드에서 온 이미지를 제보에 실을 형태로 만든다. 이미지가 아니면 null. */\n/**\n * 파일 이름으로 이미지 여부를 본다. **타입이 비어 올 때가 있다** — macOS 파인더에서 파일을\n * 복사해 붙여넣으면 `DataTransferItem.type` 이 빈 문자열로 오는 경우가 있고, 그때\n * `type.startsWith(\"image/\")` 만 보면 멀쩡한 PNG 를 놓친다(2026-08-18 제보).\n */\nfunction looksLikeImageName(name: string): boolean {\n return /\\.(png|jpe?g|gif|webp|bmp|avif|heic|heif)$/i.test(name);\n}\n\nfunction isImageFile(file: File): boolean {\n if (file.type.startsWith(\"image/\")) return true;\n // 타입이 비었거나 일반 바이너리로 오면 이름으로 한 번 더 본다.\n return (file.type === \"\" || file.type === \"application/octet-stream\") && looksLikeImageName(file.name);\n}\n\n/**\n * 실제 콘텐츠 타입. 타입이 비어 오면 **이름의 확장자로 정한다** — 그러지 않으면 멀쩡한\n * PNG 가 transcode 경로로 빠지고, 거기서 canvas 가 없거나 실패하면 통째로 버려진다.\n */\nfunction contentTypeOf(file: Blob): string {\n if (file.type !== \"\") return file.type;\n const name = file instanceof File ? file.name : \"\";\n if (/\\.png$/i.test(name)) return \"image/png\";\n if (/\\.jpe?g$/i.test(name)) return \"image/jpeg\";\n return \"\";\n}\n\nasync function readImageFile(file: Blob): Promise<FeedbackScreenshot | null> {\n const named = file instanceof File ? file : null;\n if (!file.type.startsWith(\"image/\") && !(named !== null && isImageFile(named))) return null;\n const dataUrl = await readDataUrl(file);\n if (!dataUrl) return null;\n const type = contentTypeOf(file);\n if (type === \"image/png\" || type === \"image/jpeg\") {\n const base64 = base64Of(dataUrl);\n return base64 ? { base64, contentType: type } : null;\n }\n return transcodeToJpeg(dataUrl);\n}\n\n/** 붙여넣기·드롭 이벤트에서 첫 이미지를 꺼낸다. 없으면 null(텍스트 붙여넣기 등). */\nfunction firstImageOf(transfer: DataTransfer | null): File | null {\n if (!transfer) return null;\n for (const item of Array.from(transfer.items ?? [])) {\n if (item.kind !== \"file\") continue;\n const file = item.getAsFile();\n if (file && isImageFile(file)) return file;\n }\n for (const file of Array.from(transfer.files ?? [])) {\n if (isImageFile(file)) return file;\n }\n return null;\n}\n\nfunction nextPaint(): Promise<void> {\n return new Promise((resolve) => {\n if (typeof requestAnimationFrame === \"function\") {\n requestAnimationFrame(() => resolve());\n } else {\n setTimeout(resolve, 0);\n }\n });\n}\n\nfunction focusableElements(container: HTMLElement): HTMLElement[] {\n return Array.from(\n container.querySelectorAll<HTMLElement>(\n \"button:not([disabled]), textarea:not([disabled]), select:not([disabled]), input:not([disabled]):not([type='hidden']), [tabindex]:not([tabindex='-1'])\",\n ),\n ).filter((node) => node.getAttribute(\"aria-hidden\") !== \"true\");\n}\n\nfunction trapTab(event: KeyboardEvent<HTMLElement>, container: HTMLElement): void {\n if (event.key !== \"Tab\") return;\n const items = focusableElements(container);\n if (items.length === 0) {\n event.preventDefault();\n return;\n }\n\n const first = items[0]!;\n const last = items[items.length - 1]!;\n const active = document.activeElement;\n if (event.shiftKey && (active === first || !container.contains(active))) {\n event.preventDefault();\n last.focus();\n } else if (!event.shiftKey && (active === last || !container.contains(active))) {\n event.preventDefault();\n first.focus();\n }\n}\n\nconst baseButton: CSSProperties = {\n minHeight: 38,\n border: `1px solid ${TOKENS.line}`,\n borderRadius: 9,\n background: TOKENS.surface,\n color: TOKENS.ink,\n padding: \"8px 12px\",\n font: \"inherit\",\n fontWeight: 650,\n cursor: \"pointer\",\n};\n\nconst primaryButton: CSSProperties = {\n ...baseButton,\n borderColor: TOKENS.accent,\n background: TOKENS.accent,\n color: TOKENS.surface,\n};\n\n/** 화면에서는 감추고 스크린리더에는 남긴다. 퀵 입력창은 라벨을 그릴 자리가 없다. */\nconst visuallyHidden: CSSProperties = {\n position: \"absolute\",\n width: 1,\n height: 1,\n margin: -1,\n padding: 0,\n overflow: \"hidden\",\n clip: \"rect(0 0 0 0)\",\n whiteSpace: \"nowrap\",\n border: 0,\n};\n\nconst inputStyle: CSSProperties = {\n width: \"100%\",\n boxSizing: \"border-box\",\n border: `1px solid ${TOKENS.line}`,\n borderRadius: 9,\n background: TOKENS.surface,\n color: TOKENS.ink,\n padding: \"10px 11px\",\n font: \"inherit\",\n};\n\n/**\n * 요소 지목 모드 스위치.\n *\n * 왜 버튼이 아니라 토글인가: 지목은 **상태**다. 한 번 켜면 제보를 보내도, 모달을 열었다\n * 닫아도, 페이지를 옮겨도 켜진 채로 남는다(`localStorage`). \"요소 지목\" 이라는 실행 버튼과\n * \"지목 종료\" 라는 별개의 버튼으로 나뉘어 있으면 **지금 켜져 있는지가 화면에 안 보인다** —\n * 사용자는 모달을 열 때마다 다시 켜야 하는 것으로 읽는다.\n *\n * 같은 컴포넌트를 두 곳에서 쓴다: 모달 상단(`inline`)과 지목 중 화면 상단(`floating`).\n * 켜고 끄는 자리가 한 종류여야 \"어디서 껐더라\"가 안 생긴다.\n */\nfunction PickToggle({\n active,\n paused,\n disabled,\n variant,\n focusId,\n onToggle,\n}: {\n active: boolean;\n /** 켜져 있지만 클릭을 안 먹는 중. 켬/끔과 다른 세 번째 상태라 글자로도 드러내야 한다. */\n paused?: boolean;\n disabled?: boolean;\n variant: \"inline\" | \"floating\";\n focusId?: string;\n onToggle: () => void;\n}) {\n const floating = variant === \"floating\";\n return (\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={active}\n aria-label=\"요소 지목 모드\"\n data-fk-pick-toggle={active ? \"on\" : \"off\"}\n data-fk-focus-id={focusId}\n disabled={disabled}\n onClick={onToggle}\n style={{\n ...baseButton,\n display: \"flex\",\n alignItems: \"center\",\n gap: 10,\n opacity: disabled ? 0.5 : 1,\n cursor: disabled ? \"not-allowed\" : \"pointer\",\n // 항상 값을 준다. 조건부로 빼면 리렌더 때 shorthand(border)와 충돌한다고 React 가 경고한다.\n borderColor: active ? TOKENS.accent : TOKENS.line,\n ...(floating\n ? {\n // 위치는 감싸는 `PickBar` 가 잡는다 — 옆에 [잠시 멈춤] 이 함께 서야 하는데\n // 토글이 스스로 fixed 면 둘을 나란히 둘 수 없다.\n background: TOKENS.surface,\n }\n : {\n width: \"100%\",\n justifyContent: \"space-between\",\n marginBottom: 16,\n background: active ? \"rgba(49, 93, 115, 0.06)\" : TOKENS.surface,\n }),\n }}\n >\n <span style={{ display: \"inline-flex\", alignItems: \"center\", gap: 7 }}>\n <span aria-hidden=\"true\" style={{ color: active ? TOKENS.accent : TOKENS.muted }}>\n ◎\n </span>\n 요소 지목 모드\n </span>\n <span aria-hidden=\"true\" style={{ display: \"inline-flex\", alignItems: \"center\", gap: 8 }}>\n <span\n style={{\n fontSize: 12,\n fontWeight: 600,\n color: active && !paused ? TOKENS.accent : TOKENS.muted,\n }}\n >\n {active ? (paused ? \"일시중지\" : \"켜짐\") : \"꺼짐\"}\n </span>\n <span\n style={{\n position: \"relative\",\n display: \"inline-block\",\n width: 38,\n height: 22,\n borderRadius: 11,\n background: active ? (paused ? TOKENS.muted : TOKENS.accent) : TOKENS.line,\n }}\n >\n <span\n style={{\n position: \"absolute\",\n top: 3,\n left: active ? 19 : 3,\n width: 16,\n height: 16,\n borderRadius: \"50%\",\n background: TOKENS.surface,\n boxShadow: \"0 1px 3px rgba(23, 32, 42, 0.35)\",\n }}\n />\n </span>\n </span>\n </button>\n );\n}\n\n/**\n * 지목 중 화면 위쪽에 뜨는 막대. 토글 하나만 있던 자리에 [잠시 멈춤]과 [표시 지우기]를\n * 나란히 둔다.\n *\n * 왜 끄기와 멈추기를 나누나: 지목 모드는 페이지의 모든 클릭을 먹는다. 그래서 \"드롭다운을\n * 열고 그 안을 지목한다\" 같은 일을 하려면 모드를 꺼야 했고, 끄면 다시 켜야 했다. 사용자\n * 눈에는 그게 **모드가 유지되지 않는 것**으로 보인다. 멈춤은 그 왕복을 없앤다 — 페이지를\n * 원래대로 만지고 나서 한 번 더 누르면 그 자리에서 이어진다.\n *\n * [표시 지우기]는 보낸 뒤의 문제를 푼다. 마커는 저장돼서 새로고침에도 살아남는데(그게 목적\n * 이다) 치울 수단이 없어서, 한 번 보내고 나면 [완료] 배지가 그 화면에 영영 붙어 있었다.\n * 여기 두는 이유는 지목 중에만 보이는 것이라 지우는 손잡이도 같은 자리에 있어야 하고,\n * 지울 게 없을 땐 아예 안 보여야 \"이건 뭘 지우지\"가 안 생기기 때문이다.\n */\nfunction PickBar({\n paused,\n markerCount,\n onToggle,\n onTogglePause,\n onClearMarkers,\n onReportWholeScreen,\n devClipCount,\n onCopyAll,\n}: {\n paused: boolean;\n /** 이 화면에 남아 있는 마커 수. 0 이면 [표시 지우기]를 렌더하지 않는다. */\n markerCount: number;\n onToggle: () => void;\n onTogglePause: () => void;\n onClearMarkers: () => void;\n /** 가리킬 요소가 없는 제보로 나가는 길. 지목이 기본이 되면서 필요해졌다. */\n onReportWholeScreen: () => void;\n /** 개발용 모드에서 이번 세션에 복사해 둔 건수. 0 이면 [전부 복사]를 렌더하지 않는다. */\n devClipCount: number;\n onCopyAll: () => void;\n}) {\n return (\n <div\n style={{\n position: \"fixed\",\n top: 16,\n // `left:50% + translateX(-50%)` 로 가운데를 잡으면 **쓸 수 있는 폭이 화면의 절반**이\n // 된다(transform 은 레이아웃 계산에 안 들어간다). 버튼이 셋이 되면서 그 절반에\n // 안 맞아 좁은 화면에서 버튼이 한 줄에 하나씩 세로로 쌓였다(390px 에서 3줄).\n // 양끝을 물리고 auto 마진으로 가운데를 잡으면 화면 전체 폭을 쓴다 — 같은 화면이 2줄.\n left: 0,\n right: 0,\n marginInline: \"auto\",\n width: \"fit-content\",\n maxWidth: \"calc(100% - 32px)\",\n display: \"flex\",\n alignItems: \"stretch\",\n justifyContent: \"center\",\n // 셋이 한 줄에 안 들어가는 폭에서는 줄을 바꾼다 — 안 그러면 화면 밖으로 나간다.\n flexWrap: \"wrap\",\n gap: 8,\n // 오버레이 자체는 클릭을 통과시킨다(pointerEvents:none) — 이 막대만 되살린다.\n pointerEvents: \"auto\",\n padding: 6,\n borderRadius: 12,\n background: TOKENS.surface,\n boxShadow: TOKENS.shadow,\n }}\n >\n <PickToggle variant=\"floating\" active paused={paused} onToggle={onToggle} />\n <button\n type=\"button\"\n aria-pressed={paused}\n data-fk-pick-pause={paused ? \"paused\" : \"running\"}\n onClick={onTogglePause}\n style={{\n ...baseButton,\n display: \"inline-flex\",\n alignItems: \"center\",\n gap: 6,\n whiteSpace: \"nowrap\",\n borderColor: paused ? TOKENS.accent : TOKENS.line,\n color: paused ? TOKENS.accent : TOKENS.ink,\n fontWeight: 600,\n }}\n >\n <span aria-hidden=\"true\">{paused ? \"▶\" : \"⏸\"}</span>\n {paused ? \"지목 재개\" : \"잠시 멈춤\"}\n </button>\n <button\n type=\"button\"\n data-fk-pick-whole=\"\"\n // 지목이 기본이 되면서 \"화면 전체\" 제보로 가는 길이 사라졌다. 플로팅 버튼으로도\n // 같은 모달이 열리지만, 지목 중에 보이는 자리가 여기라 찾기 쉽다.\n title=\"가리킨 요소 없이 이 화면 전체에 대해 제보합니다.\"\n onClick={onReportWholeScreen}\n style={{\n ...baseButton,\n display: \"inline-flex\",\n alignItems: \"center\",\n gap: 6,\n whiteSpace: \"nowrap\",\n }}\n >\n <span aria-hidden=\"true\">▣</span>\n 화면 전체로 보내기\n </button>\n {devClipCount > 0 ? (\n <button\n type=\"button\"\n data-fk-copy-all={devClipCount}\n title=\"이번 세션에 찍은 것을 번호 매긴 목록 하나로 복사합니다.\"\n onClick={onCopyAll}\n style={{\n ...baseButton,\n display: \"inline-flex\",\n alignItems: \"center\",\n gap: 6,\n whiteSpace: \"nowrap\",\n fontWeight: 600,\n }}\n >\n <span aria-hidden=\"true\">⧉</span>\n 전부 복사 {devClipCount}\n </button>\n ) : null}\n {markerCount > 0 ? (\n <button\n type=\"button\"\n data-fk-pick-clear={markerCount}\n // 보낸 제보까지 취소되는 걸로 읽히면 아무도 못 누른다 — 무엇이 사라지는지 말해 준다.\n title=\"화면의 표시만 지웁니다. 보낸 제보는 그대로 남습니다.\"\n aria-label={`이 화면의 주석 표시 ${markerCount}개 지우기 (보낸 제보는 그대로 남습니다)`}\n onClick={onClearMarkers}\n style={{\n ...baseButton,\n display: \"inline-flex\",\n alignItems: \"center\",\n gap: 6,\n whiteSpace: \"nowrap\",\n color: TOKENS.muted,\n }}\n >\n <span aria-hidden=\"true\">✕</span>\n 표시 지우기 {markerCount}\n </button>\n ) : null}\n </div>\n );\n}\n\nfunction MarkerStatus({ status }: { status: \"sending\" | \"done\" | \"pending\" }) {\n const presentation =\n status === \"sending\"\n ? { icon: \"↻\", text: \"전송 중\", color: TOKENS.accent }\n : status === \"done\"\n ? { icon: \"✓\", text: \"완료\", color: TOKENS.success }\n : { icon: \"◷\", text: \"대기\", color: TOKENS.warning };\n\n return (\n <span style={{ display: \"inline-flex\", gap: 4, alignItems: \"center\", color: presentation.color }}>\n <span\n aria-hidden=\"true\"\n style={\n status === \"sending\"\n ? { display: \"inline-block\", animation: \"feedback-kit-spin 900ms linear infinite\" }\n : undefined\n }\n >\n {presentation.icon}\n </span>\n <span>{presentation.text}</span>\n </span>\n );\n}\n\n/**\n * 자주 나오는 제보 제안 칩 줄. `hintsAvailable` 이 false 면 아예 렌더하지 않는다 —\n * 눌러도 비어 있는 접힘 버튼을 남기면 고장으로 보인다(계약 원문).\n *\n * 라벨은 JSX 자식으로만 넣는다(`dangerouslySetInnerHTML` 을 쓰지 않는다) — React 는\n * 텍스트 노드를 그대로 이스케이프해서 넣으므로, 서버에서 온 문자열이 `<img>` 같은\n * 마크업이어도 평문으로만 보이고 실제 엘리먼트가 생기지 않는다.\n */\nfunction HintChips({\n hints,\n hintsExpanded,\n usedHintIds,\n disabled,\n onApply,\n onToggle,\n}: {\n hints: readonly FeedbackHint[];\n hintsExpanded: boolean;\n usedHintIds: readonly string[];\n disabled: boolean;\n onApply: (hintId: string) => void;\n onToggle: () => void;\n}) {\n return (\n <div style={{ marginBottom: 16 }}>\n {/*\n 묻는 말 자체가 접기·펴기 버튼이다. 예전엔 \"이런 건가요?\" 제목과 [제안 ▾] 버튼이\n 따로 있었는데, 둘은 같은 것을 두 번 말하는 셈이었다 — 칩 줄을 여닫는 손잡이는\n 하나면 된다.\n */}\n <button\n type=\"button\"\n data-fk-focus-id={MODAL_ACTION_HINT_TOGGLE}\n aria-expanded={hintsExpanded}\n disabled={disabled}\n onClick={onToggle}\n style={{\n display: \"block\",\n margin: \"0 0 7px\",\n padding: 0,\n border: \"none\",\n background: \"none\",\n font: \"inherit\",\n fontSize: 12,\n color: TOKENS.muted,\n cursor: disabled ? \"default\" : \"pointer\",\n textAlign: \"left\",\n }}\n >\n 이런 건가요? <span aria-hidden=\"true\">{hintsExpanded ? \"▴\" : \"▾\"}</span>\n </button>\n {hintsExpanded ? (\n <>\n <div\n role=\"group\"\n aria-label=\"자주 나오는 제보 제안\"\n style={{ display: \"flex\", flexWrap: \"wrap\", gap: 6, marginBottom: 7 }}\n >\n {hints.map((hint) => {\n const used = usedHintIds.includes(hint.id);\n return (\n <button\n key={hint.id}\n type=\"button\"\n data-fk-focus-id={`hint:${hint.id}`}\n disabled={used || disabled}\n aria-pressed={used}\n onClick={() => onApply(hint.id)}\n style={{\n ...baseButton,\n minHeight: 30,\n padding: \"5px 10px\",\n fontSize: 12,\n fontWeight: 600,\n borderRadius: 999,\n ...(used\n ? {\n background: TOKENS.subtle,\n color: TOKENS.muted,\n borderColor: TOKENS.line,\n cursor: \"default\",\n }\n : {}),\n }}\n >\n {hint.label}\n </button>\n );\n })}\n </div>\n </>\n ) : null}\n </div>\n );\n}\n\n/**\n * `createWebWidget`의 헤드리스 상태를 실제 DOM으로 그리는 공개 React 진입점.\n * 큐·제보 조립·스크린샷 캡처는 모두 props로 주입된 기존 계약을 그대로 사용한다.\n */\nexport function FeedbackKit(props: FeedbackKitProps) {\n const rootRef = useRef<HTMLDivElement | null>(null);\n const floatingButtonRef = useRef<HTMLButtonElement | null>(null);\n const modalRef = useRef<HTMLDivElement | null>(null);\n const confirmRef = useRef<HTMLDivElement | null>(null);\n const fileInputRef = useRef<HTMLInputElement | null>(null);\n const previousScreenRef = useRef<WidgetState[\"screen\"]>(\"button\");\n const previousModalOpenRef = useRef(false);\n const previousConfirmVisibleRef = useRef(false);\n const [kit, setKit] = useState<WebWidget | null>(null);\n const [widgetState, setWidgetState] = useState<WidgetState | null>(null);\n const [pickingState, setPickingState] = useState<PickingState>(INITIAL_PICKING_STATE);\n const [announcement, setAnnouncement] = useState<string | null>(null);\n // 마운트 시점에 고정한다 — 도는 중에 바뀌면 \"지금 보낸 건 어디로 갔나\"가 생긴다.\n const [devMode] = useState(() => props.devMode === true);\n // 개발용 모드에서 이번 세션에 찍은 것들. **저장하지 않는다**(새로고침하면 사라진다).\n const devClipsRef = useRef<DevClipItem[]>([]);\n const [devClipCount, setDevClipCount] = useState(0);\n const [devFallbackText, setDevFallbackText] = useState<string | null>(null);\n\n const {\n queue,\n createReport,\n capture,\n reencode,\n screenshotLimitBytes,\n store,\n getPathname,\n doc,\n getViewport,\n pickTarget,\n hints,\n hintProvider,\n } = props;\n\n useEffect(() => {\n const created = createWebWidget({\n queue,\n createReport,\n capture,\n reencode,\n screenshotLimitBytes,\n store,\n getPathname,\n doc,\n getViewport,\n pickTarget,\n hints,\n hintProvider,\n });\n setKit(created);\n const unsubscribeWidget = created.widget.subscribe(setWidgetState);\n const unsubscribePicking = created.picking.subscribe(setPickingState);\n\n return () => {\n unsubscribeWidget();\n unsubscribePicking();\n created.dispose();\n };\n }, [\n queue,\n createReport,\n capture,\n reencode,\n screenshotLimitBytes,\n store,\n getPathname,\n pickTarget,\n hints,\n hintProvider,\n doc,\n getViewport,\n ]);\n\n useEffect(() => {\n if (!widgetState) return;\n const { modal, screen } = widgetState;\n if (modal.submitMessage) setAnnouncement(modal.submitMessage);\n\n if (modal.open && !previousModalOpenRef.current && modal.focused) {\n document.querySelector<HTMLElement>(`[data-fk-focus-id=\"${modal.focused}\"]`)?.focus();\n }\n if (modal.closeConfirmVisible && !previousConfirmVisibleRef.current) confirmRef.current?.focus();\n\n const wasModal = previousScreenRef.current === \"modal\";\n if (wasModal && screen === \"button\" && modal.restoreFocusTo === FLOATING_BUTTON_ID) {\n void nextPaint().then(() => floatingButtonRef.current?.focus());\n }\n previousScreenRef.current = screen;\n previousModalOpenRef.current = modal.open;\n previousConfirmVisibleRef.current = modal.closeConfirmVisible;\n }, [widgetState]);\n\n /**\n * 우리 오버레이가 떠 있는 동안 Escape 를 **우리가 가져간다.**\n *\n * 왜 React 의 onKeyDown 으로는 안 되나: 그건 모달 div 에 붙은 핸들러라 `preventDefault`\n * 를 해도 **네이티브 이벤트는 그대로 document 까지 올라간다.** 호스트 앱이 거기서 듣고\n * 있으면(대부분의 모달 구현이 그렇다) 호스트 모달이 닫힌다.\n *\n * 실측(2026-08-09, cardnews 콘솔): 호스트 모달 위에 우리 모달을 띄우고 Escape 를 누르면\n * **뒤에 있던 호스트 모달이 닫히고 우리 모달이 남았다.** 위에 뜬 쪽이 Escape 를 가져가는\n * 게 상식이고, 지금은 사용자가 우리 모달을 닫으려다 뒤에서 쓰던 폼을 잃는다. z-index 는\n * 우리가 최댓값이라 이겼는데 키보드는 호스트가 먼저 듣고 있어서 졌다.\n *\n * 그래서 document 의 **캡처 단계**에서 잡는다. 캡처는 어떤 버블 리스너보다 먼저 돌아서\n * `stopPropagation` 이 호스트에 닿기 전에 끊는다. 대신 우리 React 핸들러에도 안 가므로\n * 닫기 처리를 여기서 직접 한다.\n *\n * **우리 오버레이가 없을 때는 아무것도 하지 않는다** — 호스트의 Escape 를 상시로 뺏으면\n * 그건 그것대로 남의 앱을 망가뜨리는 것이다.\n */\n useEffect(() => {\n if (!kit || !widgetState) return;\n const { screen, modal } = widgetState;\n // 퀵 주석 팝업도 \"위에 뜬 오버레이\" 다 — 열려 있으면 Escape 는 그것을 닫아야 한다.\n const quickOpen = screen === \"picking\" && pickingState.popup !== null;\n if (screen !== \"modal\" && screen !== \"pin\" && !quickOpen) return;\n const widget = kit.widget;\n\n function onKeyDownCapture(event: globalThis.KeyboardEvent): void {\n if (event.key !== \"Escape\") return;\n // 조합 중(한글 입력 등)의 Escape 는 IME 취소다. 모달을 닫으면 글자를 잃는다.\n if (event.isComposing) return;\n event.stopPropagation();\n event.preventDefault();\n if (quickOpen) kit!.picking.cancelAnnotation();\n else if (screen === \"pin\") widget.cancelPin();\n else if (modal.closeConfirmVisible) widget.cancelCloseReport();\n else widget.closeReport();\n }\n\n document.addEventListener(\"keydown\", onKeyDownCapture, true);\n return () => document.removeEventListener(\"keydown\", onKeyDownCapture, true);\n }, [kit, widgetState, pickingState.popup]);\n\n /**\n * 모달에 이미지 붙여넣기. **document 에 단다** — dialog 루트에 달면 포커스가 모달 안에\n * 있을 때만 동작하는데, 실제 흐름(화면을 찍으러 나갔다 돌아와서 바로 붙여넣기)에서는\n * 포커스가 밖에 있는 경우가 흔하다. 2026-08-18 실측: 모달을 열어도 activeElement 가\n * body 였다.\n *\n * 퀵 팝업이 위에 떠 있으면 그쪽 폼 핸들러가 처리하므로 여기서는 비켜준다.\n */\n useEffect(() => {\n if (!kit || !widgetState?.modal.open) return;\n if (pickingState.popup) return;\n const locked = widgetState.modal.submitStatus === \"sending\" || widgetState.modal.submitStatus === \"pending\";\n if (locked) return;\n\n const controller = kit;\n async function onPaste(event: ClipboardEvent): Promise<void> {\n const file = firstImageOf(event.clipboardData);\n // 이미지가 아니면 손대지 않는다 — 텍스트 붙여넣기는 그대로 코멘트로 가야 한다.\n if (!file) return;\n event.preventDefault();\n const shot = await readImageFile(file);\n if (shot) await controller.widget.modal.attachFile(shot);\n }\n const handler = (event: ClipboardEvent): void => void onPaste(event);\n\n document.addEventListener(\"paste\", handler);\n return () => document.removeEventListener(\"paste\", handler);\n }, [kit, widgetState, pickingState.popup]);\n\n if (!kit || !widgetState) {\n return <div {...OWN_UI_PROPS} data-feedback-kit-loading=\"true\" />;\n }\n\n const activeKit = kit;\n const { modal, screen, pickingActive } = widgetState;\n const draftLocked = modal.submitStatus === \"sending\" || modal.submitStatus === \"pending\";\n\n /**\n * 지목 모드 on/off. 끄는 것은 이 토글 하나뿐이다 — 모달을 닫든 제보를 보내든\n * 페이지를 옮기든 모드는 그대로 남는다.\n */\n function togglePicking(): void {\n if (pickingActive) activeKit.widget.stopPicking();\n else activeKit.widget.startPicking();\n }\n\n async function withOwnUiHidden(action: () => Promise<void>): Promise<void> {\n const root = rootRef.current;\n const previousVisibility = root?.style.visibility ?? \"\";\n if (root) root.style.visibility = \"hidden\";\n try {\n await nextPaint();\n await action();\n } finally {\n if (root) root.style.visibility = previousVisibility;\n }\n }\n\n function currentClip(): DevClipItem | null {\n const state = pickingState.popup;\n if (!state) return null;\n return {\n comment: state.comment,\n element: state.element ?? null,\n screen: typeof location === \"undefined\" ? null : location.pathname,\n };\n }\n\n async function putOnClipboard(text: string): Promise<void> {\n const ok = await writeToClipboard(text);\n // 조용히 실패하면 사용자는 복사된 줄 알고 붙여넣기를 시도한다.\n setDevFallbackText(ok ? null : text);\n }\n\n async function copyOne(): Promise<void> {\n const clip = currentClip();\n if (!clip || clip.comment.trim() === \"\") return;\n devClipsRef.current = [...devClipsRef.current, clip];\n setDevClipCount(devClipsRef.current.length);\n activeKit.picking.cancelAnnotation();\n await putOnClipboard(renderDevClipItem(clip));\n }\n\n async function copyAll(): Promise<void> {\n if (devClipsRef.current.length === 0) return;\n await putOnClipboard(renderDevClipList(devClipsRef.current));\n }\n\n /**\n * 플로팅 버튼이 하는 일. **웹 기본은 지목 모드를 켜는 것**이다(`defaultMode`).\n *\n * 이미 지목 중이면 모달을 연다 — 그때 버튼을 누르는 사람은 \"화면 전체로 보내겠다\"는\n * 뜻이지 이미 켜진 모드를 또 켜겠다는 게 아니다. 상단 막대의 [화면 전체로 보내기]와\n * 같은 곳으로 간다.\n */\n async function pressFloatingButton(): Promise<void> {\n if (props.defaultMode !== \"report\" && !pickingActive) {\n setAnnouncement(null);\n activeKit.widget.startPicking();\n return;\n }\n await openReport();\n }\n\n async function openReport(): Promise<void> {\n setAnnouncement(null);\n await withOwnUiHidden(() => activeKit.widget.openReport());\n }\n\n async function recapture(): Promise<void> {\n await withOwnUiHidden(() => activeKit.widget.modal.recapture());\n }\n\n async function attachFile(event: ChangeEvent<HTMLInputElement>): Promise<void> {\n const input = event.currentTarget;\n const file = input.files?.[0];\n if (file) {\n const screenshot = await readImageFile(file);\n if (screenshot) await activeKit.widget.modal.attachFile(screenshot);\n }\n input.value = \"\";\n }\n\n /** 모달에 이미지를 넣는다(드롭용 — 붙여넣기는 document 리스너가 따로 처리한다). */\n async function attachToModal(transfer: DataTransfer | null): Promise<boolean> {\n const file = firstImageOf(transfer);\n if (!file) return false;\n const screenshot = await readImageFile(file);\n if (!screenshot) return false;\n await activeKit.widget.modal.attachFile(screenshot);\n return true;\n }\n\n /** 퀵 주석 입력창에 이미지를 넣는다(붙여넣기·드롭 공용). 이미지가 없으면 아무것도 안 한다. */\n async function attachToPopup(transfer: DataTransfer | null): Promise<boolean> {\n const file = firstImageOf(transfer);\n if (!file) return false;\n const screenshot = await readImageFile(file);\n if (!screenshot) return false;\n await activeKit.picking.attachAnnotationImage(screenshot);\n return true;\n }\n\n function closeReport(): void {\n activeKit.widget.closeReport();\n }\n\n function handleModalKeyDown(event: KeyboardEvent<HTMLDivElement>): void {\n if (event.key === \"Escape\") {\n event.preventDefault();\n if (modal.closeConfirmVisible) activeKit.widget.cancelCloseReport();\n else closeReport();\n return;\n }\n trapTab(event, modal.closeConfirmVisible ? confirmRef.current ?? event.currentTarget : event.currentTarget);\n }\n\n function handleBackdrop(event: MouseEvent<HTMLDivElement>): void {\n if (event.target === event.currentTarget) closeReport();\n }\n\n function submitReport(event: FormEvent<HTMLFormElement>): void {\n event.preventDefault();\n void activeKit.widget.submitReport();\n }\n\n const floating = screen === \"button\" || screen === \"picking\";\n const hoverBox = pickingState.hovered?.boundingBox;\n const popup = pickingState.popup;\n\n return (\n <div\n ref={rootRef}\n {...OWN_UI_PROPS}\n style={{ fontFamily: FONT_STACK, color: TOKENS.ink, fontSize: 14, lineHeight: 1.45 }}\n >\n <style>{`@keyframes feedback-kit-spin{to{transform:rotate(360deg)}}`}</style>\n\n {floating ? (\n <button\n ref={floatingButtonRef}\n id={FLOATING_BUTTON_ID}\n type=\"button\"\n aria-label=\"피드백 보내기\"\n onClick={() => void pressFloatingButton()}\n style={{\n ...primaryButton,\n position: \"fixed\",\n right: 24,\n bottom: 24,\n zIndex: 2147483600,\n minWidth: 112,\n minHeight: 46,\n borderRadius: 24,\n boxShadow: TOKENS.shadow,\n }}\n >\n {/*\n 지목 중에도 **누를 수 있어야 한다.** 예전에는 여기서 disabled 였고, 그래서\n \"지목을 켠 채 모달을 여는\" 경로가 아예 막혀 있었다 — 모달을 보려면 모드를\n 먼저 꺼야 했고, 사용자에겐 그게 \"모드가 유지되지 않는다\"로 보였다.\n */}\n 피드백\n </button>\n ) : null}\n\n {floating && devMode ? (\n // 켠 줄 모르고 제보하면 \"보냈다\"고 믿는데 수집처엔 아무것도 안 남는다.\n // 조용한 유실이라 이 표시가 유일한 방어다. 버튼 **밖**에 두는 이유는 버튼의\n // 접근성 이름을 오염시키지 않기 위해서다.\n <span\n data-fk-dev-badge=\"\"\n aria-label=\"개발용 모드가 켜져 있습니다 — 제보가 수집처로 가지 않고 클립보드로 복사됩니다\"\n style={{\n position: \"fixed\",\n right: 24,\n bottom: 62,\n zIndex: 2147483601,\n padding: \"2px 8px\",\n borderRadius: 6,\n background: TOKENS.warning,\n color: \"#1f2933\",\n fontSize: 11,\n fontWeight: 700,\n pointerEvents: \"none\",\n }}\n >\n 개발용 — 클립보드로 복사\n </span>\n ) : null}\n\n {devFallbackText !== null ? (\n <div\n role=\"dialog\"\n aria-label=\"클립보드에 못 써서 대신 보여줍니다\"\n data-fk-dev-fallback=\"\"\n style={{\n position: \"fixed\",\n right: 24,\n bottom: 92,\n zIndex: 2147483602,\n width: \"min(420px, calc(100vw - 48px))\",\n border: `1px solid ${TOKENS.line}`,\n borderRadius: 12,\n background: TOKENS.surface,\n padding: 12,\n boxShadow: TOKENS.shadow,\n pointerEvents: \"auto\",\n }}\n >\n <div style={{ marginBottom: 6, color: TOKENS.muted, fontSize: 12 }}>\n 클립보드에 쓰지 못했습니다. 아래에서 직접 복사하세요.\n </div>\n <textarea\n readOnly\n value={devFallbackText}\n onFocus={(event) => event.currentTarget.select()}\n style={{ ...inputStyle, height: 140, fontFamily: \"ui-monospace, monospace\", fontSize: 12 }}\n />\n <div style={{ display: \"flex\", justifyContent: \"flex-end\", marginTop: 8 }}>\n <button type=\"button\" onClick={() => setDevFallbackText(null)} style={baseButton}>\n 닫기\n </button>\n </div>\n </div>\n ) : null}\n\n {announcement && !modal.open ? (\n <div\n role=\"status\"\n aria-live=\"polite\"\n style={{\n position: \"fixed\",\n right: 24,\n bottom: 82,\n zIndex: 2147483601,\n padding: \"10px 14px\",\n borderRadius: 10,\n background: TOKENS.ink,\n color: TOKENS.surface,\n boxShadow: TOKENS.shadow,\n }}\n >\n {announcement}\n </div>\n ) : null}\n\n {screen === \"modal\" && modal.open ? (\n <div\n onMouseDown={handleBackdrop}\n style={{\n position: \"fixed\",\n inset: 0,\n zIndex: 2147483602,\n display: \"grid\",\n placeItems: \"center\",\n padding: 20,\n background: \"rgba(23, 32, 42, 0.48)\",\n }}\n >\n <div\n ref={modalRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"feedback-kit-dialog-title\"\n onKeyDown={handleModalKeyDown}\n onDragOver={(event) => {\n if (!draftLocked && firstImageOf(event.dataTransfer)) event.preventDefault();\n }}\n onDrop={(event) => {\n // 드롭은 놓은 자리가 곧 대상이라 루트에 달아도 된다(붙여넣기와 달리 포커스와 무관).\n if (draftLocked || !firstImageOf(event.dataTransfer)) return;\n event.preventDefault();\n void attachToModal(event.dataTransfer);\n }}\n style={{\n width: \"min(520px, 100%)\",\n maxHeight: \"min(760px, calc(100vh - 40px))\",\n overflowY: \"auto\",\n boxSizing: \"border-box\",\n border: `1px solid ${TOKENS.line}`,\n borderRadius: TOKENS.radius,\n background: TOKENS.surface,\n padding: 22,\n boxShadow: TOKENS.shadow,\n }}\n >\n <form onSubmit={submitReport}>\n <h2\n id=\"feedback-kit-dialog-title\"\n style={{\n margin: \"0 0 4px\",\n fontSize: 20,\n display: \"flex\",\n alignItems: \"baseline\",\n gap: 8,\n }}\n >\n 피드백 보내기\n {/*\n 지금 도는 위젯의 버전. 패키지를 올려도 쓰는 쪽 lockfile 이 안 따라오면\n 옛 코드가 계속 도는데 겉으로는 차이가 안 보인다(0.x 에서 `^0.2.0` 은\n `0.2.1` 을 안 잡아준다). 여기 한 줄이 있으면 모달을 여는 것으로 끝난다.\n */}\n <span\n data-feedback-kit-version={VERSION}\n title=\"feedback-kit 버전\"\n style={{ fontSize: 11, fontWeight: 400, color: TOKENS.muted }}\n >\n v{VERSION}\n </span>\n </h2>\n <p style={{ margin: \"0 0 14px\", color: TOKENS.muted }}>\n 현재 화면의 문제나 의견을 남겨주세요.\n </p>\n\n {/* 모드 스위치는 **맨 위**에 둔다. 아래에 실행 버튼으로 있으면 지금 켜져\n 있는지가 안 보여서, 켜 둔 사람도 매번 다시 켜게 된다. */}\n <PickToggle\n variant=\"inline\"\n active={pickingActive}\n paused={pickingState.paused}\n disabled={draftLocked}\n focusId={MODAL_ACTION_PICK}\n onToggle={togglePicking}\n />\n\n <section aria-labelledby=\"feedback-kit-screenshot-label\" style={{ marginBottom: 16 }}>\n <strong id=\"feedback-kit-screenshot-label\">스크린샷</strong>\n <div\n style={{\n display: \"grid\",\n placeItems: \"center\",\n minHeight: 150,\n marginTop: 7,\n overflow: \"hidden\",\n border: `1px solid ${TOKENS.line}`,\n borderRadius: 10,\n background: TOKENS.subtle,\n }}\n >\n {modal.screenshotStatus === \"capturing\" ? (\n <span role=\"status\">화면 캡처 중…</span>\n ) : modal.screenshot ? (\n <img\n src={screenshotSource(modal.screenshot)}\n alt=\"자동 캡처된 현재 화면 미리보기\"\n style={{ display: \"block\", maxWidth: \"100%\", maxHeight: 260, objectFit: \"contain\" }}\n />\n ) : (\n <span role=\"status\" style={{ padding: 18, color: TOKENS.muted, textAlign: \"center\" }}>\n {modal.screenshotMessage ?? \"스크린샷 없음\"}\n </span>\n )}\n </div>\n <div style={{ display: \"flex\", flexWrap: \"wrap\", gap: 8, marginTop: 8 }}>\n {modal.screenshot ? (\n <button\n type=\"button\"\n data-fk-focus-id={MODAL_ACTION_REMOVE_SCREENSHOT}\n disabled={draftLocked}\n onClick={() => kit.widget.modal.removeScreenshot()}\n style={baseButton}\n >\n 제거\n </button>\n ) : null}\n <button\n type=\"button\"\n disabled={draftLocked || modal.screenshotStatus === \"capturing\"}\n onClick={() => void recapture()}\n style={baseButton}\n >\n 다시 찍기\n </button>\n <button\n type=\"button\"\n disabled={draftLocked}\n onClick={() => fileInputRef.current?.click()}\n style={baseButton}\n >\n {modal.screenshot ? \"파일로 교체\" : \"파일로 첨부\"}\n </button>\n <input\n ref={fileInputRef}\n type=\"file\"\n accept=\"image/png,image/jpeg\"\n tabIndex={-1}\n aria-hidden=\"true\"\n onChange={(event) => void attachFile(event)}\n style={{ display: \"none\" }}\n />\n </div>\n </section>\n\n {modal.hintsAvailable ? (\n <HintChips\n hints={modal.hints}\n hintsExpanded={modal.hintsExpanded}\n usedHintIds={modal.usedHintIds}\n disabled={draftLocked}\n onApply={(hintId) => kit.widget.modal.applyHint(hintId)}\n onToggle={() => kit.widget.modal.toggleHints()}\n />\n ) : null}\n\n <label htmlFor=\"feedback-kit-comment\" style={{ display: \"block\", fontWeight: 700 }}>\n 코멘트 <span aria-hidden=\"true\">*</span>\n </label>\n <textarea\n id=\"feedback-kit-comment\"\n data-fk-focus-id={MODAL_FIELD_COMMENT}\n value={modal.comment}\n disabled={draftLocked}\n aria-required=\"true\"\n aria-invalid={modal.commentError ? \"true\" : undefined}\n aria-describedby={modal.commentError ? \"feedback-kit-comment-error\" : undefined}\n onChange={(event) => kit.widget.modal.setComment(event.currentTarget.value)}\n rows={5}\n placeholder=\"무엇이 불편했는지 알려주세요\"\n style={{ ...inputStyle, marginTop: 7, resize: \"vertical\" }}\n />\n <div style={{ minHeight: 21, marginTop: 3 }}>\n {modal.commentError ? (\n <span id=\"feedback-kit-comment-error\" role=\"alert\" style={{ color: TOKENS.danger }}>\n {modal.commentError}\n </span>\n ) : (\n <span style={{ color: TOKENS.muted }}>{modal.comment.length.toLocaleString()} / 4,000</span>\n )}\n </div>\n\n <label htmlFor=\"feedback-kit-priority\" style={{ display: \"block\", marginTop: 10, fontWeight: 700 }}>\n 우선순위\n </label>\n <select\n id=\"feedback-kit-priority\"\n data-fk-focus-id={MODAL_FIELD_PRIORITY}\n value={modal.priority}\n disabled={draftLocked}\n onChange={(event) =>\n kit.widget.modal.setPriority(event.currentTarget.value as FeedbackPriority)\n }\n style={{ ...inputStyle, marginTop: 7 }}\n >\n <option value=\"unset\">선택 안 함</option>\n <option value=\"normal\">보통</option>\n <option value=\"high\">높음</option>\n <option value=\"urgent\">긴급</option>\n </select>\n\n {modal.submitStatus !== \"idle\" || modal.pending > 0 ? (\n <div\n role=\"status\"\n aria-live=\"polite\"\n style={{\n marginTop: 14,\n padding: 11,\n borderRadius: 9,\n background: TOKENS.subtle,\n color: modal.submitStatus === \"pending\" ? TOKENS.warning : TOKENS.ink,\n }}\n >\n {modal.submitStatus === \"sending\" ? \"전송 중…\" : modal.submitMessage}\n {modal.pending > 0 ? ` · 대기 큐 ${modal.pending}건` : \"\"}\n {modal.showRetry ? (\n <button\n type=\"button\"\n data-fk-focus-id={MODAL_ACTION_RETRY}\n onClick={() => void kit.widget.retryReport()}\n style={{ ...baseButton, marginLeft: 10 }}\n >\n 다시 보내기\n </button>\n ) : null}\n </div>\n ) : null}\n\n <div style={{ display: \"flex\", justifyContent: \"flex-end\", gap: 8, marginTop: 18 }}>\n <button\n type=\"button\"\n data-fk-focus-id={MODAL_ACTION_CANCEL}\n onClick={closeReport}\n style={baseButton}\n >\n 취소\n </button>\n <button\n type=\"submit\"\n data-fk-focus-id={MODAL_ACTION_SEND}\n disabled={!modal.canSubmit}\n style={{\n ...primaryButton,\n opacity: modal.canSubmit ? 1 : 0.5,\n cursor: modal.canSubmit ? \"pointer\" : \"not-allowed\",\n }}\n >\n {modal.submitStatus === \"sending\" ? \"전송 중…\" : \"보내기\"}\n </button>\n </div>\n </form>\n\n {modal.closeConfirmVisible ? (\n <div\n style={{\n position: \"fixed\",\n inset: 0,\n zIndex: 2147483604,\n display: \"grid\",\n placeItems: \"center\",\n padding: 20,\n background: \"rgba(23, 32, 42, 0.58)\",\n }}\n >\n <div\n ref={confirmRef}\n role=\"alertdialog\"\n aria-modal=\"true\"\n aria-labelledby=\"feedback-kit-close-title\"\n tabIndex={-1}\n style={{\n width: \"min(360px, 100%)\",\n boxSizing: \"border-box\",\n borderRadius: 12,\n background: TOKENS.surface,\n padding: 20,\n boxShadow: TOKENS.shadow,\n }}\n >\n <h3 id=\"feedback-kit-close-title\" style={{ margin: \"0 0 6px\", fontSize: 17 }}>\n 작성 중인 내용을 닫을까요?\n </h3>\n <p style={{ margin: \"0 0 16px\", color: TOKENS.muted }}>\n 아직 보내지 않은 코멘트가 있습니다.\n </p>\n <div style={{ display: \"flex\", justifyContent: \"flex-end\", gap: 8 }}>\n <button type=\"button\" onClick={() => kit.widget.cancelCloseReport()} style={baseButton}>\n 계속 쓰기\n </button>\n <button type=\"button\" onClick={() => kit.widget.confirmCloseReport()} style={primaryButton}>\n 닫기\n </button>\n </div>\n </div>\n </div>\n ) : null}\n </div>\n </div>\n ) : null}\n\n {screen === \"picking\" && pickingState.active ? (\n <div\n aria-label=\"요소 지목 오버레이\"\n style={{ position: \"fixed\", inset: 0, zIndex: 2147483599, pointerEvents: \"none\" }}\n >\n {hoverBox ? (\n <div\n aria-hidden=\"true\"\n style={{\n position: \"fixed\",\n left: hoverBox.x,\n top: hoverBox.y,\n width: hoverBox.width,\n height: hoverBox.height,\n boxSizing: \"border-box\",\n border: `2px solid ${TOKENS.accent}`,\n background: \"rgba(49, 93, 115, 0.12)\",\n }}\n />\n ) : null}\n\n {/* 지목 중에는 화면 위쪽에 같은 토글이 상시로 떠 있다. 모달을 열지 않고도\n 끌 수 있고, 무엇보다 **모드가 켜져 있다는 사실 자체가 보인다.**\n 옆의 [잠시 멈춤]은 모드를 끄지 않고 페이지를 잠깐 원래대로 쓰게 한다. */}\n <PickBar\n paused={pickingState.paused}\n markerCount={pickingState.markers.length}\n onToggle={togglePicking}\n onTogglePause={() => activeKit.picking.togglePause()}\n onClearMarkers={() => activeKit.picking.clearMarkers()}\n onReportWholeScreen={() => void openReport()}\n devClipCount={devMode ? devClipCount : 0}\n onCopyAll={() => void copyAll()}\n />\n\n {pickingState.markers.map((marker, index) => (\n <div\n key={marker.id}\n role=\"status\"\n aria-label={`주석 ${index + 1}: ${marker.comment}`}\n style={{\n position: \"fixed\",\n left: `${marker.x * 100}%`,\n top: `${marker.y * 100}%`,\n transform: \"translate(-13px, -13px)\",\n display: \"grid\",\n placeItems: \"center\",\n minWidth: 26,\n height: 26,\n padding: \"0 7px\",\n border: `2px solid ${TOKENS.surface}`,\n borderRadius: 16,\n background: TOKENS.ink,\n color: TOKENS.surface,\n boxShadow: \"0 3px 10px rgba(23, 32, 42, 0.3)\",\n pointerEvents: \"none\",\n fontSize: 11,\n fontWeight: 700,\n }}\n >\n <MarkerStatus status={marker.status} />\n </div>\n ))}\n\n {popup ? (\n <form\n aria-label=\"요소 주석\"\n onSubmit={(event) => {\n event.preventDefault();\n if (devMode) {\n void copyOne();\n return;\n }\n void kit.picking.saveAnnotation();\n }}\n onPaste={(event) => {\n // 이미지를 붙였으면 우리가 가져간다. 텍스트 붙여넣기는 건드리지 않는다.\n if (!firstImageOf(event.clipboardData)) return;\n event.preventDefault();\n void attachToPopup(event.clipboardData);\n }}\n onDragOver={(event) => {\n if (firstImageOf(event.dataTransfer)) event.preventDefault();\n }}\n onDrop={(event) => {\n if (!firstImageOf(event.dataTransfer)) return;\n event.preventDefault();\n void attachToPopup(event.dataTransfer);\n }}\n style={{\n position: \"fixed\",\n // 클릭한 지점 바로 옆에 띄운다. 화면 밖으로 나가지 않게 양쪽을 물린다.\n left: `clamp(8px, calc(${popup.point.x * 100}vw + 14px), calc(100vw - 340px))`,\n top: `clamp(8px, calc(${popup.point.y * 100}vh + 14px), calc(100vh - 200px))`,\n width: \"min(332px, calc(100vw - 16px))\",\n boxSizing: \"border-box\",\n border: `1px solid ${TOKENS.line}`,\n borderRadius: 12,\n background: TOKENS.surface,\n padding: 12,\n boxShadow: TOKENS.shadow,\n pointerEvents: \"auto\",\n }}\n >\n {/* 무엇을 가리켰는지 한 줄. 라벨을 따로 두면 입력창이 아래로 밀려\n 클릭 지점에서 멀어진다 — 이 팝업의 존재 이유가 \"그 자리\" 다. */}\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: 6,\n marginBottom: 7,\n color: TOKENS.muted,\n fontSize: 12,\n }}\n >\n <span aria-hidden=\"true\">◎</span>\n <span\n style={{\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n }}\n >\n {popup.element?.text?.trim() || popup.element?.selector || \"이 지점\"}\n </span>\n </div>\n\n {popup.hintsAvailable ? (\n <HintChips\n hints={popup.hints}\n hintsExpanded={popup.hintsExpanded}\n usedHintIds={popup.usedHintIds}\n disabled={popup.saving}\n onApply={(hintId) => kit.picking.applyHint(hintId)}\n onToggle={() => kit.picking.toggleHints()}\n />\n ) : null}\n\n <label htmlFor=\"feedback-kit-annotation\" style={visuallyHidden}>\n 이 요소에 남길 의견\n </label>\n <textarea\n id=\"feedback-kit-annotation\"\n autoFocus\n value={popup.comment}\n disabled={popup.saving}\n onChange={(event) => kit.picking.setAnnotationComment(event.currentTarget.value)}\n onKeyDown={(event) => {\n // Enter 로 바로 보낸다. 줄바꿈이 필요하면 Shift+Enter.\n // 한글 조합 중의 Enter 는 글자를 확정하는 키다 — 여기서 보내면 글자를 잃는다.\n if (event.key !== \"Enter\" || event.shiftKey) return;\n // React 의 합성 이벤트 타입에는 isComposing 이 없다 — 네이티브에서 읽는다.\n const native = event.nativeEvent as unknown as { isComposing?: boolean };\n if (native.isComposing) return;\n event.preventDefault();\n void kit.picking.saveAnnotation();\n }}\n rows={2}\n placeholder=\"무엇이 문제인가요? (Enter 로 보내기)\"\n style={{ ...inputStyle, resize: \"vertical\", minHeight: 58 }}\n />\n {popup.comment.length > COMMENT_MAX_CHARS ? (\n <div role=\"alert\" style={{ marginTop: 4, color: TOKENS.danger }}>\n 4,000자 이하로 입력해주세요\n </div>\n ) : null}\n\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: 8,\n marginTop: 8,\n color: TOKENS.muted,\n fontSize: 12,\n }}\n >\n {popup.screenshot ? (\n <>\n <img\n src={screenshotSource(popup.screenshot)}\n alt=\"이 주석에 첨부된 화면\"\n style={{\n width: 40,\n height: 26,\n objectFit: \"cover\",\n borderRadius: 4,\n border: `1px solid ${TOKENS.line}`,\n }}\n />\n <span style={{ flex: 1 }}>화면 첨부됨 · 붙여넣기로 교체</span>\n <button\n type=\"button\"\n disabled={popup.saving}\n onClick={() => kit.picking.removeAnnotationImage()}\n style={{ ...baseButton, minHeight: 26, padding: \"2px 8px\", fontSize: 12 }}\n >\n 제거\n </button>\n </>\n ) : (\n <span role=\"status\" style={{ flex: 1 }}>\n {popup.screenshotStatus === \"capturing\"\n ? \"화면 캡처 중…\"\n : popup.screenshotStatus === \"failed\"\n ? \"캡처 실패 — 이미지를 붙여넣어 주세요\"\n : \"이미지를 붙여넣거나 끌어다 놓을 수 있어요\"}\n </span>\n )}\n </div>\n\n <div style={{ display: \"flex\", justifyContent: \"flex-end\", gap: 8, marginTop: 10 }}>\n <button\n type=\"button\"\n disabled={popup.saving}\n onClick={() => kit.picking.cancelAnnotation()}\n style={{ ...baseButton, minHeight: 32, padding: \"5px 10px\" }}\n >\n 취소\n </button>\n <button\n type=\"submit\"\n disabled={!popup.canSave || popup.saving}\n style={{\n ...primaryButton,\n minHeight: 32,\n padding: \"5px 12px\",\n opacity: popup.canSave && !popup.saving ? 1 : 0.5,\n cursor: popup.canSave && !popup.saving ? \"pointer\" : \"not-allowed\",\n }}\n >\n {popup.saving ? \"보내는 중…\" : devMode ? \"복사\" : \"보내기\"}\n </button>\n </div>\n </form>\n ) : null}\n </div>\n ) : null}\n </div>\n );\n}\n","// 지금 도는 위젯이 몇 버전인지.\n//\n// 왜 화면에 띄우나: 패키지를 올려도 **쓰는 쪽 lockfile 이 안 따라오면 옛 코드가 계속\n// 돈다**(0.x 에서 `^0.2.0` 은 `0.2.1` 을 안 잡아준다). 그때 겉으로는 아무 차이가 없어서\n// \"올렸는데 왜 그대로지\"를 매번 lockfile 을 뒤져 확인해야 했다. 모달에 한 줄 있으면\n// 열어보는 것으로 끝난다.\n//\n// 값은 **빌드 타임에 박는다**(tsup `define`). 런타임에 package.json 을 읽으면 번들러마다\n// 깨지고, 손으로 적은 상수는 릴리스 때마다 잊는다.\n\ndeclare const __FK_VERSION__: string | undefined;\n\n/**\n * 배포된 패키지에서는 실제 버전, 소스로 직접 돌릴 때(테스트·워크스페이스 링크)는 `dev`.\n *\n * `dev` 가 보이면 npm 에서 받은 게 아니라는 뜻이라, 그것도 정보다.\n */\nexport const VERSION: string =\n typeof __FK_VERSION__ === \"string\" && __FK_VERSION__ !== \"\" ? __FK_VERSION__ : \"dev\";\n","// 개발용 모드에서 클립보드로 나가는 텍스트를 만든다.\n//\n// 왜 순수 함수인가: 이 텍스트가 곧 계약이다. 붙여넣는 쪽(에이전트)이 매번 같은 줄 구성을\n// 기대하는데, 조립이 렌더러 안에 흩어져 있으면 \"어떤 경로로 복사했느냐\"에 따라 모양이 달라진다.\n// 한 곳에 모아 두면 검사도 여기 하나로 끝난다.\n\nimport { sourceFromElement, type ElementInfo, type FeedbackPriority } from \"@solhun/feedback-kit-core\";\n\n/** 복사 한 건의 재료. 화면에서 모은 것을 그대로 넘긴다. */\nexport interface DevClipItem {\n comment: string;\n element: ElementInfo | null;\n /** 화면 경로(URL pathname 또는 route 이름). */\n screen?: string | null;\n /** 리포트 모달에서 복사할 때만. 퀵 입력창에는 우선순위가 없다. */\n priority?: FeedbackPriority | null;\n}\n\nfunction sourceLine(item: DevClipItem): string | null {\n const mapping = sourceFromElement(item.element ?? undefined);\n if (!mapping.sourceFile) return null;\n return mapping.sourceLine === null\n ? `소스: ${mapping.sourceFile}`\n : `소스: ${mapping.sourceFile}:${mapping.sourceLine}`;\n}\n\nfunction elementLine(element: ElementInfo | null): string | null {\n if (!element) return null;\n const selector = element.selector ?? element.tag;\n const text = element.text?.trim();\n return text ? `요소: ${selector} \"${text}\"` : `요소: ${selector}`;\n}\n\nfunction componentLine(element: ElementInfo | null): string | null {\n // 요소를 담을 때 이미 넣어 둔 값이다(`element-info`). DOM 노드를 다시 들고 다닐 필요가 없다.\n const summary = element?.attributes?.[\"react-components\"];\n return summary ? `컴포넌트: ${summary}` : null;\n}\n\n/**\n * 한 건을 줄 구성으로 만든다.\n *\n * **못 얻은 줄은 넣지 않는다.** 빈 값으로 남기면 붙여넣는 쪽이 \"여긴 왜 비었지\"를 판단해야 하는데,\n * 없는 것과 못 얻은 것을 구분할 방법이 텍스트에는 없다. 아예 빼는 편이 읽기 쉽다.\n */\nexport function renderDevClipItem(item: DevClipItem): string {\n const lines = [`[피드백] ${item.comment.trim()}`];\n const screen = item.screen?.trim();\n if (screen) lines.push(`화면: ${screen}`);\n const element = elementLine(item.element);\n if (element) lines.push(element);\n const component = componentLine(item.element);\n if (component) lines.push(component);\n const source = sourceLine(item);\n if (source) lines.push(source);\n // 퀵 입력창에는 우선순위가 없다. 모달에서 골랐을 때만 줄이 하나 붙는다.\n if (item.priority && item.priority !== \"unset\") lines.push(`우선순위: ${item.priority}`);\n return lines.join(\"\\n\");\n}\n\n/**\n * 여러 건을 번호 매긴 목록 하나로 만든다.\n *\n * **각 항목이 한 건 복사와 같은 줄 구성을 갖는다.** 요약하지 않는 이유는 붙여넣는 쪽에서\n * 세 번째 항목만 골라 쓸 수 있어야 하기 때문이다 — 줄이 빠지면 그 항목만 못 쓴다.\n */\nexport function renderDevClipList(items: readonly DevClipItem[]): string {\n if (items.length === 0) return \"\";\n if (items.length === 1) return renderDevClipItem(items[0]!);\n return items\n .map((item, index) => `${index + 1}. ${renderDevClipItem(item).split(\"\\n\").join(\"\\n \")}`)\n .join(\"\\n\\n\");\n}\n\n/**\n * 클립보드에 쓴다. **못 쓰면 false 를 돌려준다** — 조용히 실패하면 사용자는 복사된 줄 알고\n * 붙여넣기를 시도한다. 부르는 쪽이 그때 같은 텍스트를 화면에 띄운다.\n *\n * 실패하는 경우가 실제로 있다: 권한 거부, 비보안 컨텍스트(http), 클립보드 API 자체가 없는 환경.\n */\nexport async function writeToClipboard(text: string): Promise<boolean> {\n const clipboard = (globalThis as { navigator?: { clipboard?: { writeText?: (value: string) => Promise<void> } } })\n .navigator?.clipboard;\n if (!clipboard?.writeText) return false;\n try {\n await clipboard.writeText(text);\n return true;\n } catch {\n return false;\n }\n}\n","// 웹 기본 컨텍스트 제공자.\n//\n// 왜 패키지가 주나: `getUrl` 을 호스트가 깜빡하면 제보의 url 이 빈 문자열로 나가고,\n// 수집 서버는 그걸 400 으로 거절한다. 400 은 재시도 불가라 **제보가 조용히 버려진다.**\n// 실제로 e-fm 첫 배선에서 이 일이 났다 — 화면에는 \"보내지 못했습니다\"만 뜨고,\n// 원인(빈 url)은 네트워크를 까 봐야 알 수 있었다.\n//\n// 기본값을 패키지가 쥐고 있으면 붙이는 쪽이 잊을 수 없다. 필요하면 덮어쓰면 된다.\n\nimport type {\n ContextProviders,\n DiagnosticsInstallOpts,\n WebContextInput,\n} from \"@solhun/feedback-kit-core\";\nimport { webRouteStorage, webRouteWatcher } from \"./route-watcher.js\";\n\n/**\n * 브라우저 전역에서 뽑을 수 있는 기본 제공자들.\n *\n * SSR·비브라우저에서 불려도 던지지 않는다(`null` 을 돌려준다) — 위젯이 마운트되기 전\n * 서버 렌더 단계에서 실행될 수 있기 때문이다.\n */\nexport function webContextProviders(): ContextProviders {\n return {\n getUrl: () => {\n const loc = (globalThis as { location?: { href?: string } }).location;\n return typeof loc?.href === \"string\" && loc.href.length > 0 ? loc.href : null;\n },\n getWebContext: (): WebContextInput | null => {\n const g = globalThis as {\n location?: unknown;\n navigator?: { userAgent?: string };\n innerWidth?: number;\n innerHeight?: number;\n devicePixelRatio?: number;\n };\n if (!g.location) return null;\n return {\n userAgent: g.navigator?.userAgent ?? null,\n viewport: {\n width: typeof g.innerWidth === \"number\" ? g.innerWidth : null,\n height: typeof g.innerHeight === \"number\" ? g.innerHeight : null,\n devicePixelRatio: typeof g.devicePixelRatio === \"number\" ? g.devicePixelRatio : null,\n },\n };\n },\n };\n}\n\n/**\n * 웹 진단 수집의 기본 배선.\n *\n * `sharedDiagnostics.install(webDiagnosticsOptions({ ... }))` 로 쓴다. 넘긴 값이 기본값을\n * 덮으므로 감시자·저장소를 직접 갈아 끼울 수도 있다.\n *\n * 왜 패키지가 주나: 감시자를 호스트가 깜빡하면 **궤적만 조용히 빈다.** 네트워크·로그는\n * 그대로 모이니 화면상 아무 문제가 없어 보이고, \"이 제보엔 이동이 없었나 보다\"로 읽힌다 —\n * 빠진 것과 없는 것이 구분되지 않는 실패다.\n *\n * 진단을 끄면(`captureDiagnostics: false`) install 자체가 안 불리므로 궤적도 같이 꺼진다.\n * 궤적만 따로 켜고 끄는 스위치는 만들지 않는다.\n */\nexport function webDiagnosticsOptions(opts: DiagnosticsInstallOpts = {}): DiagnosticsInstallOpts {\n return {\n watchRoutes: webRouteWatcher,\n routeStorage: webRouteStorage(),\n ...opts,\n };\n}\n"],"mappings":"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","names":["isOwnUi","import_feedback_kit_core","isOwnUi","html2canvas","import_feedback_kit_core","defaultPathname","import_feedback_kit_core","import_feedback_kit_core","modal","screen"]}