@synod-ai/extension-feedback 0.1.2 → 0.1.3
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.
- package/README.md +1 -1
- package/dist/index.js +21 -4
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +21 -4
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/client/types.ts","../src/utils/retry.ts","../src/utils/config.ts","../src/client/SynodFeedbackClient.ts","../src/lifecycle/FeedbackManager.ts","../src/utils/overlay-css.ts","../src/ui/FeedbackOverlay.ts","../src/utils/logo.ts","../src/interaction/Highlighter.ts","../src/interaction/SelectionExtractor.ts","../src/interaction/ElementPicker.ts","../src/interaction/KeyComboDetector.ts","../src/ui/FloatingLogo.ts","../src/utils/viewport.ts","../src/utils/screenshot.ts","../src/ui/IssueForm.ts","../src/utils/title.ts","../src/utils/clipboard.ts","../src/ui/QueuePanel.ts","../src/ui/ChargeIndicator.ts","../src/ui/LogoContextMenu.ts","../src/interaction/setup.ts"],"sourcesContent":["// ── @synod-ai/extension-feedback — Public API ────────────────────────────\n//\n// Framework-agnostic entry point. Zero React imports.\n// React wrapper available at '@synod-ai/extension-feedback/react'.\n\nimport { FeedbackManager } from './lifecycle/FeedbackManager';\nimport { registerInteractionLayer, __resetInteractionLayer } from './interaction/setup';\n\n// ── Module-level singleton ─────────────────────────────────────────────\n\nlet manager = new FeedbackManager();\n\n// ── Register interaction layer (SDG-294) ────────────────────────────────\n\nregisterInteractionLayer(manager);\n\n// ── Functional facade ──────────────────────────────────────────────────\n\nexport function init(config: import('./client/types').FeedbackConfig): void {\n manager.init(config);\n // Auto-enable: the SDK activates fully on init(). The user does not need\n // to call enable() separately. The visible UI (FloatingLogo + picker) is\n // still lazy-mounted on the first successful hotkey charge — see\n // ensureVisibleUI() in interaction/setup.ts — so the host page sees no\n // SDK chrome at all until the user actually invokes the hotkey.\n // enable() is idempotent so a separate explicit call is a no-op.\n manager.enable();\n}\n\nexport function enable(): void {\n manager.enable();\n}\n\nexport function disable(): void {\n manager.disable();\n}\n\nexport function onEnable(handler: () => void): () => void {\n return manager.onEnable(handler);\n}\n\nexport function onDisable(handler: () => void): () => void {\n return manager.onDisable(handler);\n}\n\nexport async function sendBatch(): Promise<import('./client/types').BatchResult> {\n return manager.sendBatch();\n}\n\nexport function queue(finding: import('./client/types').FindingPayload): void {\n manager.queue(finding);\n}\n\nexport function clearQueue(): void {\n manager.clearQueue();\n}\n\nexport function replaceQueue(items: import('./client/types').FindingPayload[]): void {\n manager.replaceQueue(items);\n}\n\nexport function isEnabled(): boolean {\n return manager.enabled;\n}\n\nexport function getQueueSnapshot(): import('./client/types').FindingPayload[] {\n return manager.getQueueSnapshot();\n}\n\nexport function getQueueLength(): number {\n return manager.queueLength;\n}\n\n// ── Direct access (advanced / testing) ──────────────────────────────────\n\nexport { SynodFeedbackClient } from './client/SynodFeedbackClient';\nexport { FeedbackManager } from './lifecycle/FeedbackManager';\n\n// ── Clipboard transfer (offline finding import/export) ──────────────────\n//\n// Lets a user copy findings from the extension queue on a machine that\n// can't reach the gateway, then paste them into the Findings panel on a\n// machine that can. Screenshots are stripped on encode.\n\nexport {\n encodeFindingsToClipboard,\n decodeFindingsFromClipboard,\n} from './utils/clipboard';\n\n// ── Type exports ────────────────────────────────────────────────────────\n\nexport type {\n FeedbackConfig,\n FindingPayload,\n FindingSeverity,\n FindingContextJson,\n BatchResult,\n BatchResponse,\n CreatedFinding,\n FindingError,\n ExternalFindingsRequest,\n ClientError,\n ClientErrorCode,\n} from './client/types';\n\n// ── Test-only: reset singleton state ────────────────────────────────────\n\n/**\n * @internal Reset singleton state. Intended for test isolation only.\n * Creates a fresh manager and re-registers interaction layer hooks.\n */\nexport function __resetForTesting(): void {\n __resetInteractionLayer();\n manager.destroy();\n manager = new FeedbackManager();\n registerInteractionLayer(manager);\n}\n","// ── Types: Gateway Contract (matches SDG-295) ──────────────────────────\n\nexport type FindingSeverity = 'low' | 'medium' | 'high';\n\nexport interface FindingContextJson {\n url?: string;\n /** SPA route or pathname (best-effort). Helps the agent know which \"screen\" the user was on. */\n route?: string;\n /** Document title — gives the agent a human-readable label for the page. */\n page_title?: string;\n element_text?: string;\n /** outerHTML of the selected element, truncated at ~1500 chars. */\n element_html?: string;\n /** Filtered attribute map of the selected element. */\n element_attributes?: Record<string, string>;\n /**\n * Root-first parent chain, filtered to ancestors with an id or class.\n * Generic wrappers are skipped on the client. Each entry is\n * {tag, id?, className?}.\n */\n parent_chain?: Array<{ tag: string; id?: string; className?: string }>;\n viewport?: { width: number; height: number };\n user_agent?: string;\n screenshot_data_url?: string;\n batch_id?: string;\n extension_version?: string;\n}\n\nexport interface FindingPayload {\n title: string;\n description: string;\n severity: FindingSeverity;\n suggested_action?: 'create_story';\n context_json: FindingContextJson;\n}\n\n// POST /v2/findings/external body\nexport interface ExternalFindingsRequest {\n project_id: string;\n findings: FindingPayload[];\n}\n\n// ── Response types (gateway → client) ──────────────────────────────────\n\nexport interface CreatedFinding {\n id: string;\n title: string;\n severity: FindingSeverity;\n}\n\nexport interface FindingError {\n index: number;\n error: string;\n}\n\nexport interface BatchResponse {\n created: CreatedFinding[];\n errors: FindingError[];\n}\n\n// ── Client-side result wrapper ─────────────────────────────────────────\n\nexport interface BatchResult {\n created: CreatedFinding[];\n errors: FindingError[];\n}\n\n// ── Error types ────────────────────────────────────────────────────────\n\nexport type ClientErrorCode =\n | 'AUTH_ERROR'\n | 'RATE_LIMITED'\n | 'NETWORK_ERROR'\n | 'TIMEOUT'\n | 'SERVER_ERROR'\n | 'CLIENT_ERROR'\n | 'CONTEXT_TOO_LARGE';\n\nexport interface ClientError extends Error {\n code: ClientErrorCode;\n statusCode?: number;\n retryable: boolean;\n}\n\nexport function createClientError(\n code: ClientErrorCode,\n message: string,\n statusCode?: number,\n retryable = false,\n): ClientError {\n const err = new Error(message) as ClientError;\n err.name = 'ClientError';\n err.code = code;\n err.statusCode = statusCode;\n err.retryable = retryable;\n return err;\n}\n\n// ── Configuration types ────────────────────────────────────────────────\n\nexport interface FeedbackConfig {\n gatewayUrl: string;\n projectId: string;\n token: string;\n keyCombo?: string;\n timeout?: number;\n maxRetries?: number;\n}\n","// ── Retry Policy: Pure functions, unit-testable without fetch ──────────\n\n/**\n * Default exponential backoff schedule in ms.\n * Applied before attempts 2, 3, and 4 respectively (initial attempt has no delay).\n *\n * Fixed schedule (not jittered) — documented tradeoff (architecture §10):\n * Single-user extension, low request volume. Simpler and deterministic.\n */\nexport const BACKOFF_DELAYS_MS = [1000, 2000, 4000];\n\n/**\n * Get the backoff delay before the given retry attempt.\n *\n * @param attempt - Zero-indexed retry attempt (0 = first retry, 1 = second retry, etc.)\n * @returns Delay in ms, or undefined if attempt exceeds max retries\n */\nexport function getBackoffDelay(attempt: number): number | undefined {\n if (attempt < 0 || attempt >= BACKOFF_DELAYS_MS.length) {\n return undefined;\n }\n return BACKOFF_DELAYS_MS[attempt];\n}\n\n/**\n * Determine whether a failure should be retried.\n *\n * Retryable:\n * - Network errors (TypeError from fetch)\n * - AbortError / timeout (DOMException with name 'AbortError')\n * - HTTP 5xx (server errors)\n * - HTTP 429 (rate limited)\n *\n * NOT retryable:\n * - HTTP 401 (invalid/expired token — won't recover)\n * - HTTP 4xx (except 429) — deterministic validation errors\n * - HTTP 2xx/3xx (success)\n */\nexport function isRetryable(status: number | undefined, error: unknown): boolean {\n // Network failure: fetch throws TypeError\n if (error instanceof TypeError) return true;\n\n // AbortError from AbortController (timeout)\n if (error instanceof DOMException && error.name === 'AbortError') return true;\n\n // Check for AbortError-like objects (jsdom compatibility)\n if (error && typeof error === 'object' && 'name' in error && (error as { name: string }).name === 'AbortError') {\n return true;\n }\n\n // No response (timeout/DNS before headers)\n if (status === undefined) return true;\n\n // Server errors\n if (status >= 500 && status < 600) return true;\n\n // Rate limited\n if (status === 429) return true;\n\n // Everything else (4xx except 429, 2xx, 3xx)\n return false;\n}\n\n/**\n * Get the HTTP status from a fetch Response or error.\n * Returns undefined if no response was received (network error, timeout).\n */\nexport function getStatusFromError(error: unknown): number | undefined {\n if (error && typeof error === 'object' && 'status' in error) {\n const status = (error as { status: number }).status;\n if (typeof status === 'number') return status;\n }\n return undefined;\n}\n","import type { FeedbackConfig } from '../client/types';\n\nconst DEFAULT_GATEWAY_URL = 'http://localhost:8787';\nconst DEFAULT_TIMEOUT = 15_000;\nconst DEFAULT_MAX_RETRIES = 3;\n// Default: Ctrl+Shift+E — NOT Ctrl+Shift+F (conflicts with browser fullscreen)\nconst DEFAULT_KEY_COMBO = 'Ctrl+Shift+E';\n\nexport const DEFAULTS = {\n gatewayUrl: DEFAULT_GATEWAY_URL,\n timeout: DEFAULT_TIMEOUT,\n maxRetries: DEFAULT_MAX_RETRIES,\n keyCombo: DEFAULT_KEY_COMBO,\n} as const;\n\n/**\n * Validate and normalize configuration input.\n * Throws on missing required fields with clear error messages.\n *\n * Required fields: projectId, token\n * Optional (with defaults): gatewayUrl, timeout, maxRetries, keyCombo\n */\nexport function validateConfig(input: Partial<FeedbackConfig>): FeedbackConfig {\n const missing: string[] = [];\n\n if (!input.projectId || input.projectId.trim() === '') {\n missing.push('projectId');\n }\n\n if (!input.token || input.token.trim() === '') {\n missing.push('token');\n }\n\n if (missing.length > 0) {\n throw new Error(\n `[SynodFeedback] Missing required config field(s): ${missing.join(', ')}. ` +\n `Please provide these in init() or <FeedbackProvider config={...}>.`\n );\n }\n\n const gatewayUrl = input.gatewayUrl ?? DEFAULT_GATEWAY_URL;\n try {\n new URL(gatewayUrl);\n } catch {\n throw new Error(\n `[SynodFeedback] Invalid gatewayUrl: \"${gatewayUrl}\". Must be a valid URL.`\n );\n }\n\n return {\n gatewayUrl,\n projectId: input.projectId!,\n token: input.token!,\n keyCombo: input.keyCombo ?? DEFAULT_KEY_COMBO,\n timeout: input.timeout ?? DEFAULT_TIMEOUT,\n maxRetries: input.maxRetries ?? DEFAULT_MAX_RETRIES,\n };\n}\n\n/**\n * Maximum serialized context_json size in bytes (10KB gateway limit).\n */\nexport const MAX_CONTEXT_JSON_BYTES = 10_240;\n\n/**\n * Pre-validate that serialized context_json fits within the gateway's 10KB limit.\n * Returns true if valid, false if oversized.\n */\nexport function isContextJsonValid(contextJson: unknown): boolean {\n const serialized = JSON.stringify(contextJson);\n return new TextEncoder().encode(serialized).length <= MAX_CONTEXT_JSON_BYTES;\n}\n","import type {\n FeedbackConfig,\n FindingPayload,\n ExternalFindingsRequest,\n BatchResponse,\n BatchResult,\n ClientError,\n} from './types';\nimport { createClientError } from './types';\nimport { isRetryable, getBackoffDelay } from '../utils/retry';\nimport { isContextJsonValid } from '../utils/config';\n\nconst ENDPOINT_PATH = '/v2/findings/external';\n\nexport class SynodFeedbackClient {\n private readonly config: FeedbackConfig;\n private _queue: FindingPayload[] = [];\n private _abortControllers: AbortController[] = [];\n\n constructor(config: FeedbackConfig) {\n this.config = config;\n }\n\n // ── Queue Management ────────────────────────────────────────────────\n\n /**\n * Add a finding to the local batch queue.\n * Pre-validates context_json size to prevent oversized payloads.\n */\n queue(finding: FindingPayload): void {\n if (!isContextJsonValid(finding.context_json)) {\n throw createClientError(\n 'CONTEXT_TOO_LARGE',\n 'context_json exceeds 10KB limit (serialized). Compress or remove screenshot data.',\n undefined,\n false,\n );\n }\n this._queue.push(finding);\n }\n\n /**\n * Get the current queue length.\n */\n get queueLength(): number {\n return this._queue.length;\n }\n\n /**\n * Get a shallow copy of the current queue.\n */\n getQueueSnapshot(): FindingPayload[] {\n return [...this._queue];\n }\n\n /**\n * Clear all items from the queue.\n */\n clearQueue(): void {\n this._queue = [];\n }\n\n // ── Batch Send ──────────────────────────────────────────────────────\n\n /**\n * Send all queued findings as a batch to the gateway.\n * On success (201): created items are dequeued; errored items remain.\n * On full failure (after retries): entire batch remains in queue.\n *\n * @returns BatchResult with created and errors arrays\n * @throws ClientError on full failure after retries, or on non-retryable error\n */\n async sendBatch(): Promise<BatchResult> {\n if (this._queue.length === 0) {\n return { created: [], errors: [] };\n }\n\n const findingsToSend = [...this._queue];\n const requestBody: ExternalFindingsRequest = {\n project_id: this.config.projectId,\n findings: findingsToSend,\n };\n\n const response = await this.postWithRetry(requestBody);\n const batchResult = this.processResponse(response, findingsToSend);\n\n return batchResult;\n }\n\n // ── HTTP Transport with Retry ───────────────────────────────────────\n\n /**\n * POST to the gateway with retry/backoff logic.\n * Retries up to maxRetries times on retryable failures (5xx, 429, network, timeout).\n * Does NOT retry on 401 or other 4xx (except 429).\n *\n * Worst-case wall time: ~1s + timeout + 2s + timeout + 4s + timeout.\n */\n private async postWithRetry(body: ExternalFindingsRequest): Promise<BatchResponse> {\n const maxRetries = this.config.maxRetries ?? 3;\n let lastError: ClientError | null = null;\n\n // Initial attempt + retries = total attempts = maxRetries + 1\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n // Apply backoff before retries (not before the initial attempt)\n if (attempt > 0) {\n const delay = getBackoffDelay(attempt - 1);\n if (delay !== undefined) {\n await this.sleep(delay);\n }\n }\n\n // Fresh AbortController per attempt\n const controller = new AbortController();\n this._abortControllers.push(controller);\n const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);\n\n try {\n const res = await fetch(`${this.config.gatewayUrl}${ENDPOINT_PATH}`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.config.token}`,\n },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n // Success: 200/201\n if (res.ok) {\n const data = await res.json() as BatchResponse;\n return data;\n }\n\n // Non-success status codes\n const clientErr = this.classifyHttpError(res.status);\n\n if (!isRetryable(res.status, null) || attempt >= maxRetries) {\n throw clientErr;\n }\n\n // Retryable — record error and continue loop\n lastError = clientErr;\n } catch (err) {\n clearTimeout(timeoutId);\n\n // Already classified as ClientError (from classifyHttpError)\n if (isClientError(err)) {\n if (!err.retryable || attempt >= maxRetries) {\n throw err;\n }\n lastError = err;\n continue;\n }\n\n // Network error or timeout — classify and check retryability\n const classified = this.classifyTransportError(err);\n\n if (!classified.retryable || attempt >= maxRetries) {\n throw classified;\n }\n\n lastError = classified;\n } finally {\n // Clean up AbortController reference\n const idx = this._abortControllers.indexOf(controller);\n if (idx >= 0) {\n this._abortControllers.splice(idx, 1);\n }\n }\n }\n\n // All retries exhausted\n throw lastError ?? createClientError(\n 'NETWORK_ERROR',\n 'All retry attempts exhausted.',\n undefined,\n true,\n );\n }\n\n // ── Response Processing ─────────────────────────────────────────────\n\n /**\n * Process the gateway response: dequeue created items, retain errored items.\n * Findings are matched by their position in the original sent batch.\n */\n private processResponse(response: BatchResponse, sentFindings: FindingPayload[]): BatchResult {\n // Created items: their indices in the sent batch are 0..n\n // The gateway returns created[] with title/severity, and errors[] with index/error.\n // Since the sent findings maintain their order, we use errors[].index to identify failures.\n const errorIndices = new Set(response.errors.map(e => e.index));\n const createdIndices: number[] = [];\n\n // Determine which sent findings were successfully created\n for (let i = 0; i < sentFindings.length; i++) {\n if (!errorIndices.has(i)) {\n createdIndices.push(i);\n }\n }\n\n // Dequeue created items (they were consumed from the front of the queue)\n // We need to remove them from the live queue by matching content+position\n this.dequeueByIndices(createdIndices, sentFindings.length);\n\n return {\n created: response.created,\n errors: response.errors,\n };\n }\n\n /**\n * Remove successfully sent items from the live queue.\n *\n * The sentFindings were a snapshot of the queue at send time. We need to remove\n * the successful ones while keeping the failed ones. Since they were all at the\n * front of the queue (in order), we rebuild: remove createdIndices from [0..sentCount-1].\n */\n private dequeueByIndices(keepIndices: number[], sentCount: number): void {\n // Items at keepIndices within [0..sentCount-1] were successfully created → remove them\n const removeIndices = new Set(keepIndices);\n const remaining: FindingPayload[] = [];\n\n // Items within sentCount: keep only non-created (errored) ones\n for (let i = 0; i < this._queue.length; i++) {\n if (i < sentCount) {\n if (!removeIndices.has(i)) {\n remaining.push(this._queue[i]);\n }\n } else {\n // Items beyond sentCount were queued after sendBatch started — keep them\n remaining.push(this._queue[i]);\n }\n }\n\n this._queue = remaining;\n }\n\n // ── Error Classification ────────────────────────────────────────────\n\n private classifyHttpError(status: number): ClientError {\n switch (true) {\n case status === 401:\n return createClientError(\n 'AUTH_ERROR',\n 'Authentication failed (401). Token is invalid or expired.',\n 401,\n false,\n );\n\n case status === 429:\n return createClientError(\n 'RATE_LIMITED',\n 'Rate limited (429). Too many findings in the time window.',\n 429,\n true,\n );\n\n case status >= 500 && status < 600:\n return createClientError(\n 'SERVER_ERROR',\n `Server error (${status}).`,\n status,\n true,\n );\n\n case status >= 400 && status < 500:\n return createClientError(\n 'CLIENT_ERROR',\n `Client error (${status}). Request rejected.`,\n status,\n false,\n );\n\n default:\n return createClientError(\n 'CLIENT_ERROR',\n `Unexpected status ${status}.`,\n status,\n false,\n );\n }\n }\n\n private classifyTransportError(err: unknown): ClientError {\n // Network failure: fetch throws TypeError\n if (err instanceof TypeError) {\n return createClientError(\n 'NETWORK_ERROR',\n `Network error: ${err.message}`,\n undefined,\n true,\n );\n }\n\n // AbortError from AbortController (timeout)\n if (\n (err instanceof DOMException && err.name === 'AbortError') ||\n (err && typeof err === 'object' && 'name' in err && (err as { name: string }).name === 'AbortError')\n ) {\n return createClientError(\n 'TIMEOUT',\n `Request timed out after ${this.config.timeout}ms.`,\n undefined,\n true,\n );\n }\n\n // Unknown error — treat as non-retryable\n const message = err instanceof Error ? err.message : String(err);\n return createClientError(\n 'NETWORK_ERROR',\n `Unexpected transport error: ${message}`,\n undefined,\n false,\n );\n }\n\n // ── Cleanup ─────────────────────────────────────────────────────────\n\n /**\n * Abort all in-flight requests.\n */\n destroy(): void {\n for (const controller of this._abortControllers) {\n try {\n controller.abort();\n } catch {\n // ignore\n }\n }\n this._abortControllers = [];\n }\n\n // ── Utilities ───────────────────────────────────────────────────────\n\n private sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n }\n}\n\n// ── Helper ──────────────────────────────────────────────────────────────\n\nfunction isClientError(err: unknown): err is ClientError {\n return err instanceof Error && 'code' in err && 'retryable' in err;\n}\n","import type { FeedbackConfig, FindingPayload, BatchResult } from '../client/types';\nimport { SynodFeedbackClient } from '../client/SynodFeedbackClient';\nimport { validateConfig } from '../utils/config';\n\ntype EnableHandler = () => void;\ntype DisableHandler = () => void;\n\n/**\n * Singleton lifecycle manager for the feedback extension.\n *\n * State machine:\n * uninitialized → (init) → initialized (enabled=false)\n * ↕ enable() / disable()\n * initialized (enabled=true/false)\n *\n * The client and queue are ALWAYS available regardless of `enabled` state —\n * `enabled` is a semantic signal for the interaction layer (SDG-294), not a gate on transport.\n */\nexport class FeedbackManager {\n private _initialized = false;\n private _enabled = false;\n private _client: SynodFeedbackClient | null = null;\n private _config: FeedbackConfig | null = null;\n private _enableHandlers: EnableHandler[] = [];\n private _disableHandlers: DisableHandler[] = [];\n\n get initialized(): boolean {\n return this._initialized;\n }\n\n get enabled(): boolean {\n return this._enabled;\n }\n\n /** The active client instance, or null if not initialized. */\n get client(): SynodFeedbackClient | null {\n return this._client;\n }\n\n /** The validated config, or null if not initialized. */\n get config(): FeedbackConfig | null {\n return this._config;\n }\n\n // ── Lifecycle ───────────────────────────────────────────────────────\n\n /**\n * Initialize the feedback system with configuration.\n * Creates the singleton client instance. Re-init replaces existing state.\n */\n init(config: FeedbackConfig): void {\n const validated = validateConfig(config);\n\n // Destroy existing client if re-initializing\n if (this._client) {\n this._client.destroy();\n }\n\n this._client = new SynodFeedbackClient(validated);\n this._config = validated;\n this._initialized = true;\n this._enabled = false;\n }\n\n /**\n * Enable the feedback system. Fires all registered onEnable handlers.\n */\n enable(): void {\n if (!this._initialized) {\n throw new Error('[SynodFeedback] Cannot enable() before init(). Call init(config) first.');\n }\n if (this._enabled) return;\n\n this._enabled = true;\n // Fire handlers in registration order\n for (const handler of [...this._enableHandlers]) {\n handler();\n }\n }\n\n /**\n * Disable the feedback system. Fires all registered onDisable handlers.\n */\n disable(): void {\n if (!this._initialized) return;\n if (!this._enabled) return;\n\n this._enabled = false;\n for (const handler of [...this._disableHandlers]) {\n handler();\n }\n }\n\n // ── Extension Hooks (for SDG-294) ───────────────────────────────────\n\n /**\n * Register a handler to be called when enable() is invoked.\n * If already enabled, the handler fires immediately.\n * @returns unregister function\n */\n onEnable(handler: EnableHandler): () => void {\n this._enableHandlers.push(handler);\n if (this._enabled) {\n handler();\n }\n return () => {\n const idx = this._enableHandlers.indexOf(handler);\n if (idx >= 0) this._enableHandlers.splice(idx, 1);\n };\n }\n\n /**\n * Register a handler to be called when disable() is invoked.\n * @returns unregister function\n */\n onDisable(handler: DisableHandler): () => void {\n this._disableHandlers.push(handler);\n return () => {\n const idx = this._disableHandlers.indexOf(handler);\n if (idx >= 0) this._disableHandlers.splice(idx, 1);\n };\n }\n\n // ── Convenience: delegate to client ─────────────────────────────────\n\n queue(finding: FindingPayload): void {\n this.requireClient();\n this._client!.queue(finding);\n }\n\n async sendBatch(): Promise<BatchResult> {\n this.requireClient();\n return this._client!.sendBatch();\n }\n\n get queueLength(): number {\n return this._client?.queueLength ?? 0;\n }\n\n getQueueSnapshot(): FindingPayload[] {\n return this._client?.getQueueSnapshot() ?? [];\n }\n\n /**\n * Clear all items from the queue.\n */\n clearQueue(): void {\n this._client?.clearQueue();\n }\n\n /**\n * Replace the entire queue with the given items.\n * Used by the interaction layer for remove/edit operations.\n */\n replaceQueue(items: FindingPayload[]): void {\n if (!this._client) return;\n this._client.clearQueue();\n for (const item of items) {\n this._client.queue(item);\n }\n }\n\n // ── Teardown ────────────────────────────────────────────────────────\n\n /**\n * Reset the manager to uninitialized state.\n * Destroys client, clears handlers.\n */\n destroy(): void {\n this._client?.destroy();\n this._client = null;\n this._initialized = false;\n this._enabled = false;\n this._enableHandlers = [];\n this._disableHandlers = [];\n }\n\n private requireClient(): void {\n if (!this._initialized || !this._client) {\n throw new Error('[SynodFeedback] Not initialized. Call init(config) first.');\n }\n }\n}\n","// ── Shadow DOM Internal CSS ──────────────────────────────────────────────\n//\n// All styles are injected as a <style> element inside the closed shadow root.\n// No CSS custom properties are read from the host page — all values are hardcoded.\n// prefers-reduced-motion is respected.\n\nexport const OVERLAY_CSS = `\n:host {\n all: initial;\n}\n\n*, *::before, *::after {\n box-sizing: border-box;\n font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n}\n\n/* ── Shared variables (internal only) ──────────────────────────────────── */\n.synod-fb {\n --fb-bg-panel: #1e293b;\n --fb-bg-elevated: #334155;\n --fb-bg-input: #0f172a;\n --fb-bg-hover: #475569;\n --fb-border: #475569;\n --fb-border-subtle: #334155;\n --fb-text-primary: #f1f5f9;\n --fb-text-secondary: #94a3b8;\n --fb-text-muted: #64748b;\n --fb-accent: #0d9488;\n --fb-accent-hover: #0f766e;\n --fb-highlight: #2DAFAF;\n --fb-highlight-fill: rgba(45, 175, 175, 0.08);\n --fb-sev-low: #64748b;\n --fb-sev-medium: #d97706;\n --fb-sev-high: #dc2626;\n --fb-success: #059669;\n --fb-error: #dc2626;\n --fb-radius-sm: 4px;\n --fb-radius-md: 8px;\n --fb-radius-lg: 12px;\n --fb-shadow-panel: 0 8px 32px rgba(0,0,0,0.3);\n --fb-shadow-popover: 0 4px 16px rgba(0,0,0,0.2);\n}\n\n/* ── Highlighter ───────────────────────────────────────────────────────── */\n.synod-fb-highlighter {\n position: absolute;\n pointer-events: none;\n border: 2px dashed #2DAFAF;\n background: rgba(45, 175, 175, 0.08);\n border-radius: 2px;\n z-index: 1;\n transition: all 0ms;\n}\n\n.synod-fb-inspector {\n position: absolute;\n pointer-events: none;\n background: #1F8F8F;\n color: #ffffff;\n font-size: 11px;\n font-family: 'SF Mono', 'Cascadia Code', 'Consolas', monospace;\n line-height: 20px;\n height: 20px;\n padding: 0 8px;\n border-radius: 4px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 400px;\n z-index: 2;\n}\n\n.synod-fb-highlighter-logo {\n position: absolute;\n pointer-events: none;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n background: #0f172a;\n border: 1px solid #2DAFAF;\n border-radius: 4px;\n z-index: 3;\n}\n\n/* ── Floating logo ─────────────────────────────────────────────────────── */\n.synod-fb-logo {\n position: absolute;\n width: 36px;\n height: 36px;\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: grab;\n user-select: none;\n pointer-events: auto;\n transition: transform 150ms ease-out, filter 150ms ease-out;\n z-index: 10;\n filter: drop-shadow(0 2px 6px rgba(0,0,0,0.25));\n}\n\n.synod-fb-logo svg {\n width: 32px;\n height: 32px;\n pointer-events: none;\n}\n\n.synod-fb-logo:hover {\n transform: scale(1.08);\n filter: drop-shadow(0 4px 10px rgba(0,0,0,0.35));\n}\n\n.synod-fb-logo:active {\n cursor: grabbing;\n transform: scale(0.96);\n}\n\n.synod-fb-logo-badge {\n position: absolute;\n top: -3px;\n right: -3px;\n min-width: 18px;\n height: 18px;\n border-radius: 9px;\n /* Synod teal — replaces the previous generic red. Reads as brand chrome\n rather than a notifications-style alert. */\n background: #0d9488;\n color: #ffffff;\n font-size: 11px;\n font-weight: 700;\n font-family: 'SF Mono', 'Cascadia Code', 'Consolas', monospace;\n line-height: 18px;\n text-align: center;\n padding: 0 4px;\n letter-spacing: -0.2px;\n /* Double-ring outline: white inner ring guarantees contrast on dark\n backgrounds; the semi-opaque dark outer ring guarantees contrast on\n light backgrounds. Together they pop on any backdrop without needing\n per-site tuning. */\n border: 2px solid #ffffff;\n box-shadow: 0 0 0 1px rgba(15, 23, 42, 0.55), 0 1px 3px rgba(0, 0, 0, 0.25);\n}\n\n.synod-fb-logo-badge-pulse {\n animation: synod-fb-badge-pulse 300ms ease-in-out;\n}\n\n@keyframes synod-fb-badge-pulse {\n 0%, 100% { transform: scale(1); }\n 50% { transform: scale(1.15); }\n}\n\n/* ── Logo context menu ─────────────────────────────────────────────────── */\n.synod-fb-context-menu {\n position: absolute;\n min-width: 160px;\n background: #1e293b;\n border: 1px solid #475569;\n border-radius: 6px;\n box-shadow: 0 8px 24px rgba(0,0,0,0.35);\n padding: 4px;\n z-index: 50;\n pointer-events: auto;\n font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n animation: synod-fb-fade-in 100ms ease-out;\n}\n\n.synod-fb-context-menu-item {\n display: block;\n width: 100%;\n text-align: left;\n padding: 6px 10px;\n background: transparent;\n border: none;\n border-radius: 4px;\n color: #f1f5f9;\n font-size: 12px;\n font-weight: 500;\n cursor: pointer;\n font-family: inherit;\n}\n\n.synod-fb-context-menu-item:hover {\n background: #dc2626;\n color: #fff;\n}\n\n/* ── Charge indicator ──────────────────────────────────────────────────── */\n.synod-fb-charge {\n position: absolute;\n pointer-events: none;\n z-index: 15;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n transform: translate(-50%, -50%);\n transition: transform 120ms ease-out;\n}\n\n.synod-fb-charge-ring {\n position: absolute;\n inset: 0;\n width: 40px;\n height: 40px;\n transform: rotate(-90deg);\n}\n\n.synod-fb-charge-ring-bg {\n fill: none;\n stroke: rgba(45, 175, 175, 0.18);\n stroke-width: 3;\n}\n\n.synod-fb-charge-ring-fg {\n fill: none;\n stroke: #2DAFAF;\n stroke-width: 3;\n stroke-linecap: round;\n transition: stroke-dashoffset 60ms linear, stroke 200ms ease-out;\n}\n\n.synod-fb-charge svg:not(.synod-fb-charge-ring) {\n width: 22px;\n height: 22px;\n filter: drop-shadow(0 2px 6px rgba(0,0,0,0.3));\n}\n\n.synod-fb-charge.charged {\n transform: translate(-50%, -50%) scale(1.15);\n}\n\n.synod-fb-charge.charged .synod-fb-charge-ring-fg {\n stroke: #36D6D6;\n}\n\n/* ── IssueForm ─────────────────────────────────────────────────────────── */\n.synod-fb-form {\n position: absolute;\n width: 340px;\n max-height: 90vh;\n overflow-y: auto;\n background: #1e293b;\n border: 1px solid #475569;\n border-radius: 8px;\n box-shadow: 0 8px 32px rgba(0,0,0,0.3);\n z-index: 20;\n pointer-events: auto;\n color: #f1f5f9;\n font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n animation: synod-fb-fade-in 150ms ease-out;\n}\n\n@keyframes synod-fb-fade-in {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n.synod-fb-form-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 12px;\n background: #334155;\n border-radius: 8px 8px 0 0;\n border-bottom: 1px solid #475569;\n gap: 8px;\n}\n\n.synod-fb-form-header-logo {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n}\n\n.synod-fb-form-header-text {\n flex: 1;\n font-size: 11px;\n font-family: 'SF Mono', 'Cascadia Code', 'Consolas', monospace;\n color: #94a3b8;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.synod-fb-form-close {\n background: none;\n border: none;\n color: #94a3b8;\n cursor: pointer;\n font-size: 16px;\n padding: 0 4px;\n line-height: 1;\n}\n\n.synod-fb-form-close:hover { color: #f1f5f9; }\n\n.synod-fb-form-body { padding: 12px; }\n\n.synod-fb-field { margin-bottom: 12px; }\n\n.synod-fb-label {\n display: block;\n font-size: 11px;\n font-weight: 500;\n color: #94a3b8;\n margin-bottom: 4px;\n}\n\n.synod-fb-input,\n.synod-fb-textarea {\n width: 100%;\n background: #0f172a;\n border: 1px solid #475569;\n border-radius: 4px;\n color: #f1f5f9;\n font-size: 13px;\n font-family: inherit;\n padding: 8px 10px;\n outline: none;\n resize: vertical;\n}\n\n.synod-fb-input:focus,\n.synod-fb-textarea:focus {\n border-color: #0d9488;\n}\n\n.synod-fb-textarea {\n min-height: 48px;\n}\n\n.synod-fb-severity-group {\n display: flex;\n gap: 0;\n border-radius: 4px;\n overflow: hidden;\n border: 1px solid #475569;\n}\n\n.synod-fb-sev-btn {\n flex: 1;\n padding: 6px 0;\n border: none;\n background: transparent;\n color: #94a3b8;\n font-size: 12px;\n font-weight: 500;\n cursor: pointer;\n transition: background 100ms;\n}\n\n.synod-fb-sev-btn:not(:last-child) {\n border-right: 1px solid #475569;\n}\n\n.synod-fb-sev-btn:hover { background: #475569; }\n\n.synod-fb-sev-btn.active[data-sev=\"low\"] { background: #64748b; color: #fff; }\n.synod-fb-sev-btn.active[data-sev=\"medium\"] { background: #d97706; color: #fff; }\n.synod-fb-sev-btn.active[data-sev=\"high\"] { background: #dc2626; color: #fff; }\n\n.synod-fb-screenshot-toggle {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 10px;\n background: #0f172a;\n border: 1px solid #475569;\n border-radius: 4px;\n cursor: pointer;\n font-size: 12px;\n color: #94a3b8;\n}\n\n.synod-fb-screenshot-toggle:hover { border-color: #64748b; }\n\n.synod-fb-screenshot-toggle input { accent-color: #0d9488; }\n\n.synod-fb-screenshot-toggle.disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n\n.synod-fb-screenshot-toggle.unavailable {\n color: #d97706;\n}\n\n.synod-fb-form-actions {\n display: flex;\n gap: 8px;\n margin-top: 4px;\n}\n\n.synod-fb-btn-secondary {\n flex: 1;\n padding: 8px 12px;\n border: 1px solid #0d9488;\n background: transparent;\n color: #0d9488;\n font-size: 12px;\n font-weight: 500;\n border-radius: 4px;\n cursor: pointer;\n font-family: inherit;\n}\n\n.synod-fb-btn-secondary:hover { background: #0d9488; color: #fff; }\n\n.synod-fb-btn-primary {\n flex: 1;\n padding: 8px 12px;\n border: none;\n background: #0d9488;\n color: #fff;\n font-size: 12px;\n font-weight: 500;\n border-radius: 4px;\n cursor: pointer;\n font-family: inherit;\n}\n\n.synod-fb-btn-primary:hover { background: #0f766e; }\n.synod-fb-btn-primary:active { transform: scale(0.98); }\n.synod-fb-btn-primary:disabled { background: #334155; color: #64748b; cursor: not-allowed; }\n\n/* ── QueuePanel ────────────────────────────────────────────────────────── */\n.synod-fb-queue {\n position: absolute;\n top: 16px;\n right: 16px;\n width: 380px;\n max-width: calc(100vw - 32px);\n max-height: min(520px, calc(100vh - 32px));\n background: #1e293b;\n border: 1px solid #475569;\n border-radius: 8px;\n box-shadow: 0 8px 32px rgba(0,0,0,0.3);\n z-index: 30;\n pointer-events: auto;\n display: flex;\n flex-direction: column;\n color: #f1f5f9;\n font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n animation: synod-fb-slide-in 200ms cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n@keyframes synod-fb-slide-in {\n from { transform: translateX(400px); opacity: 0; }\n to { transform: translateX(0); opacity: 1; }\n}\n\n.synod-fb-queue-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 16px;\n border-bottom: 1px solid #334155;\n gap: 8px;\n}\n\n.synod-fb-queue-header-logo {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n}\n\n.synod-fb-queue-title {\n font-size: 14px;\n font-weight: 600;\n color: #f1f5f9;\n}\n\n.synod-fb-queue-body {\n flex: 1;\n overflow-y: auto;\n padding: 8px;\n}\n\n.synod-fb-queue-empty {\n padding: 24px 16px;\n text-align: center;\n color: #64748b;\n font-size: 13px;\n}\n\n.synod-fb-queue-item {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n padding: 8px;\n border-radius: 4px;\n border: 1px solid transparent;\n margin-bottom: 4px;\n}\n\n.synod-fb-queue-item:hover { background: #334155; }\n\n.synod-fb-queue-item-error {\n font-size: 11px;\n color: #dc2626;\n padding: 4px 8px 8px;\n}\n\n.synod-fb-queue-item-thumb {\n width: 44px;\n height: 44px;\n min-width: 44px;\n border-radius: 4px;\n border: 1px dashed #475569;\n display: flex;\n align-items: center;\n justify-content: center;\n overflow: hidden;\n}\n\n.synod-fb-queue-item-thumb img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n}\n\n.synod-fb-queue-item-content {\n flex: 1;\n min-width: 0;\n}\n\n.synod-fb-queue-item-title {\n font-size: 12px;\n color: #f1f5f9;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.synod-fb-queue-item-title.synod-fb-pending {\n color: #64748b;\n font-style: italic;\n}\n\n.synod-fb-queue-item-meta {\n font-size: 10px;\n font-family: 'SF Mono', 'Cascadia Code', 'Consolas', monospace;\n color: #64748b;\n}\n\n.synod-fb-queue-item-actions {\n display: flex;\n gap: 4px;\n}\n\n.synod-fb-icon-btn {\n background: none;\n border: none;\n color: #94a3b8;\n cursor: pointer;\n font-size: 14px;\n padding: 2px;\n line-height: 1;\n}\n\n.synod-fb-icon-btn:hover { color: #f1f5f9; }\n\n.synod-fb-queue-footer {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 12px 16px;\n border-top: 1px solid #334155;\n gap: 8px;\n}\n\n.synod-fb-btn-danger {\n padding: 6px 12px;\n border: 1px solid #475569;\n background: transparent;\n color: #dc2626;\n font-size: 12px;\n font-weight: 500;\n border-radius: 4px;\n cursor: pointer;\n font-family: inherit;\n}\n\n.synod-fb-btn-danger:hover { background: #dc2626; color: #fff; border-color: #dc2626; }\n\n.synod-fb-queue-banner {\n padding: 8px 12px;\n margin: 8px;\n border-radius: 4px;\n font-size: 12px;\n}\n\n.synod-fb-queue-banner.error {\n background: rgba(220, 38, 38, 0.15);\n color: #fca5a5;\n border: 1px solid rgba(220, 38, 38, 0.3);\n}\n\n.synod-fb-queue-banner.info {\n background: rgba(13, 148, 136, 0.15);\n color: #5eead4;\n border: 1px solid rgba(13, 148, 136, 0.3);\n}\n\n/* ── Toasts ────────────────────────────────────────────────────────────── */\n.synod-fb-toast-container {\n position: absolute;\n bottom: 16px;\n left: 50%;\n transform: translateX(-50%);\n display: flex;\n flex-direction: column;\n gap: 8px;\n z-index: 100;\n pointer-events: none;\n}\n\n.synod-fb-toast {\n max-width: 400px;\n min-height: 36px;\n padding: 8px 16px;\n border-radius: 6px;\n font-size: 12px;\n font-weight: 500;\n font-family: inherit;\n display: flex;\n align-items: center;\n gap: 6px;\n animation: synod-fb-toast-in 200ms ease-out;\n white-space: nowrap;\n}\n\n.synod-fb-toast-logo {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n}\n\n@keyframes synod-fb-toast-in {\n from { transform: translateY(20px); opacity: 0; }\n to { transform: translateY(0); opacity: 1; }\n}\n\n.synod-fb-toast.success { background: #065f46; color: #d1fae5; }\n.synod-fb-toast.error { background: #991b1b; color: #fee2e2; }\n.synod-fb-toast.info { background: #1e3a5f; color: #dbeafe; }\n\n/* ── Tooltip ───────────────────────────────────────────────────────────── */\n.synod-fb-tooltip {\n position: absolute;\n padding: 4px 10px;\n background: #0f172a;\n color: #94a3b8;\n font-size: 11px;\n border-radius: 4px;\n border: 1px solid #334155;\n white-space: nowrap;\n z-index: 5;\n pointer-events: none;\n animation: synod-fb-fade-in 150ms ease-out;\n}\n\n/* ── Focus-visible ─────────────────────────────────────────────────────── */\nbutton:focus-visible,\ninput:focus-visible,\ntextarea:focus-visible {\n outline: 2px solid #0d9488;\n outline-offset: 2px;\n}\n\n/* ── Reduced motion ────────────────────────────────────────────────────── */\n@media (prefers-reduced-motion: reduce) {\n *, *::before, *::after {\n animation-duration: 0ms !important;\n animation-delay: 0ms !important;\n transition-duration: 0ms !important;\n }\n .synod-fb-queue {\n animation: none;\n opacity: 1;\n }\n .synod-fb-form {\n animation: none;\n opacity: 1;\n }\n .synod-fb-toast {\n animation: none;\n opacity: 1;\n }\n}\n`;\n","// ── FeedbackOverlay — Shadow DOM host container ──────────────────────────\n//\n// Creates a closed Shadow DOM host element at z-index 2147483647.\n// All extension UI renders inside the shadow root.\n// Provides the shadow root reference to children via getRoot().\n//\n// Pointer-events strategy:\n// Host element: none (transparent passthrough to host page)\n// Visual overlays (highlighter): none\n// Interactive UI (form, queue, logo): auto\n\nimport { OVERLAY_CSS } from '../utils/overlay-css';\n\nconst HOST_ID = 'synod-fb-host';\nconst MAX_Z = 2147483647;\n\nexport class FeedbackOverlay {\n private _host: HTMLElement | null = null;\n private _shadowRoot: ShadowRoot | null = null;\n private _contentWrapper: HTMLDivElement | null = null;\n\n /** Whether the overlay is currently mounted. */\n get isMounted(): boolean {\n return this._host !== null && this._host.isConnected;\n }\n\n /** The shadow root (available only while mounted). */\n get root(): ShadowRoot | null {\n return this._shadowRoot;\n }\n\n /** The host element ID. */\n get hostId(): string {\n return HOST_ID;\n }\n\n /**\n * Get the actual host DOM element (for contains() checks by the picker).\n * Returns null if not mounted.\n */\n getHostElement(): HTMLElement | null {\n return this._host;\n }\n\n /**\n * Mount the overlay host to the given parent element (usually document.body).\n * Creates a closed shadow root, injects CSS, and sets up the content wrapper.\n *\n * Idempotent: if already mounted, returns without re-creating.\n */\n mount(parent: HTMLElement): void {\n if (this.isMounted) return;\n\n const host = document.createElement('div');\n host.id = HOST_ID;\n host.style.cssText = `position: fixed; inset: 0; z-index: ${MAX_Z}; pointer-events: none;`;\n\n // Closed shadow root — host JS cannot access internals\n const shadowRoot = host.attachShadow({ mode: 'closed' });\n\n // Inject CSS\n const style = document.createElement('style');\n style.textContent = OVERLAY_CSS;\n shadowRoot.appendChild(style);\n\n // Content wrapper for all visual elements\n const contentWrapper = document.createElement('div');\n contentWrapper.className = 'synod-fb';\n shadowRoot.appendChild(contentWrapper);\n\n parent.appendChild(host);\n\n this._host = host;\n this._shadowRoot = shadowRoot;\n this._contentWrapper = contentWrapper;\n }\n\n /**\n * Unmount the overlay: remove host element, null references.\n * Safe to call even if not mounted.\n */\n unmount(): void {\n if (this._host && this._host.parentElement) {\n this._host.parentElement.removeChild(this._host);\n }\n this._host = null;\n this._shadowRoot = null;\n this._contentWrapper = null;\n }\n\n /**\n * Get the content wrapper element inside the shadow root.\n * Children append their DOM nodes to this wrapper.\n */\n getContentWrapper(): HTMLDivElement | null {\n return this._contentWrapper;\n }\n\n /**\n * Check whether a given element is inside the overlay host.\n */\n contains(element: Node | null): boolean {\n if (!element || !this._host) return false;\n return this._host === element || this._host.contains(element);\n }\n}\n","// ── Synod brand mark — inlined SVG ────────────────────────────────────────\n//\n// Source: docs/branding/synod-logo.svg (three rotated triangles + center dot).\n// Inlined as a TS constant so the SDK is self-contained — no runtime fetch,\n// no bundler asset-loading config required. Closed-shadow DOM consumers\n// (FeedbackOverlay) get the same SVG inside their shadow root.\n\nexport const SYNOD_LOGO_SVG_MARKUP = `<svg viewBox=\"0 0 100 100\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M50 15 L72 52 H28 Z\" fill=\"#36D6D6\" />\n <path d=\"M50 15 L72 52 H28 Z\" fill=\"#2DAFAF\" transform=\"rotate(120 50 58)\" />\n <path d=\"M50 15 L72 52 H28 Z\" fill=\"#1F8F8F\" transform=\"rotate(240 50 58)\" />\n <circle cx=\"50\" cy=\"58\" r=\"4\" fill=\"white\" />\n</svg>`;\n\n/**\n * Create a sized SVGSVGElement containing the Synod brand mark.\n * The returned element is detached — caller appends it to whatever parent.\n *\n * Uses DOMParser rather than innerHTML so callers can drop the result\n * directly into either a regular DOM node or a shadow root without\n * tripping Content Security Policy `style-src` checks (no inline style attr).\n */\nexport function createSynodLogo(size: number): SVGSVGElement {\n const doc = new DOMParser().parseFromString(SYNOD_LOGO_SVG_MARKUP, 'image/svg+xml');\n const svg = doc.documentElement as unknown as SVGSVGElement;\n svg.setAttribute('width', String(size));\n svg.setAttribute('height', String(size));\n svg.setAttribute('aria-hidden', 'true');\n return svg;\n}\n","// ── Highlighter — Visual overlay for hovered element ─────────────────────\n//\n// Renders outline + inspector chip inside the shadow DOM root.\n// Positioned to match the target element's bounding rect.\n// Does NOT modify the host element's own styles.\n\nimport type { FeedbackOverlay } from '../ui/FeedbackOverlay';\nimport { createSynodLogo } from '../utils/logo';\n\nexport class Highlighter {\n private _overlay: FeedbackOverlay;\n private _highlightEl: HTMLDivElement | null = null;\n private _inspectorEl: HTMLDivElement | null = null;\n private _logoEl: HTMLDivElement | null = null;\n\n constructor(overlay: FeedbackOverlay) {\n this._overlay = overlay;\n }\n\n /**\n * Show highlight at the target element's bounding rect.\n * Updates the inspector chip with tag, id/classes, and dimensions.\n */\n highlight(element: Element): void {\n if (!element.isConnected) {\n this.clear();\n return;\n }\n\n const wrapper = this._overlay.getContentWrapper();\n if (!wrapper) return;\n\n if (!this._highlightEl) {\n this._highlightEl = document.createElement('div');\n this._highlightEl.className = 'synod-fb-highlighter';\n wrapper.appendChild(this._highlightEl);\n }\n\n if (!this._inspectorEl) {\n this._inspectorEl = document.createElement('div');\n this._inspectorEl.className = 'synod-fb-inspector';\n this._inspectorEl.setAttribute('aria-hidden', 'true');\n wrapper.appendChild(this._inspectorEl);\n }\n\n if (!this._logoEl) {\n this._logoEl = document.createElement('div');\n this._logoEl.className = 'synod-fb-highlighter-logo';\n this._logoEl.setAttribute('aria-hidden', 'true');\n this._logoEl.appendChild(createSynodLogo(14));\n wrapper.appendChild(this._logoEl);\n }\n\n const rect = element.getBoundingClientRect();\n\n this._highlightEl!.style.display = 'block';\n this._highlightEl!.style.left = `${rect.left}px`;\n this._highlightEl!.style.top = `${rect.top}px`;\n this._highlightEl!.style.width = `${rect.width}px`;\n this._highlightEl!.style.height = `${rect.height}px`;\n\n const chipText = this.buildInspectorText(element, rect);\n this._inspectorEl!.style.display = 'block';\n this._inspectorEl!.textContent = chipText;\n\n const chipTop = rect.top > 24 ? rect.top - 24 : rect.bottom + 4;\n this._inspectorEl!.style.left = `${rect.left}px`;\n this._inspectorEl!.style.top = `${chipTop}px`;\n\n // Position the small Synod logo badge in the top-left corner of the highlight,\n // sitting just outside the border. Hidden when the element is too small.\n this._logoEl!.style.display = rect.width < 30 || rect.height < 30 ? 'none' : 'block';\n if (this._logoEl!.style.display === 'block') {\n this._logoEl!.style.left = `${rect.left - 18}px`;\n this._logoEl!.style.top = `${rect.top - 18}px`;\n }\n }\n\n /**\n * Clear the highlight overlay and inspector chip.\n */\n clear(): void {\n if (this._highlightEl) {\n this._highlightEl.style.display = 'none';\n }\n if (this._inspectorEl) {\n this._inspectorEl.style.display = 'none';\n }\n if (this._logoEl) {\n this._logoEl.style.display = 'none';\n }\n }\n\n /**\n * Remove all DOM nodes created by this Highlighter.\n */\n destroy(): void {\n this._highlightEl?.remove();\n this._inspectorEl?.remove();\n this._logoEl?.remove();\n this._highlightEl = null;\n this._inspectorEl = null;\n this._logoEl = null;\n }\n\n /**\n * Build the inspector chip text: tagname + id/classes + dimensions.\n */\n private buildInspectorText(element: Element, rect: DOMRect): string {\n const tag = element.tagName.toLowerCase();\n let id = element.id ? `#${element.id}` : '';\n\n let classStr = '';\n if (element.classList.length > 0) {\n classStr = '.' + Array.from(element.classList).join('.');\n if (classStr.length > 40) {\n classStr = classStr.substring(0, 37) + '…';\n }\n }\n\n const identifier = (id || classStr) ? `${id}${classStr}` : tag;\n const dimensions = `${Math.round(rect.width)}×${Math.round(rect.height)}`;\n\n return `${identifier} · ${dimensions}`;\n }\n}\n","// ── SelectionExtractor — Pure extraction, no DOM side effects ─────────────\n//\n// Captures element metadata that's useful for an LLM agent trying to\n// understand what the user was looking at. We deliberately do NOT generate\n// a CSS selector or XPath: nth-of-type chains and absolute XPaths are\n// noise that an agent can't act on. The agent gets far more value from\n// the element's tag + attributes (especially class/id) and a filtered\n// parent chain showing the meaningful ancestor layout.\n//\n// Unit-testable independently of the picker.\n\nimport type { SelectionContext } from './types';\n\n/**\n * Truncate text to max length with ellipsis.\n */\nfunction truncateText(text: string, max: number): string {\n const trimmed = text.trim().replace(/\\s+/g, ' ');\n if (trimmed.length <= max) return trimmed;\n return trimmed.substring(0, max) + '…';\n}\n\n// Attributes that bloat the payload without aiding diagnosis. Dropped from\n// elementAttributes. (Live event handlers, image resolution hints, etc.)\nconst NOISY_ATTRS = new Set([\n 'srcset',\n 'data-srcset',\n 'integrity',\n 'onload',\n 'onerror',\n 'onclick',\n 'onmouseover',\n 'onsubmit',\n]);\n\n/**\n * Build a filtered attribute map for an element.\n * Drops event-handler attributes and any value larger than 200 chars\n * (e.g. inline data URLs, inlined SVGs) — those would push context_json\n * over the gateway's 10KB cap without helping diagnosis.\n */\nfunction extractAttributes(element: Element): Record<string, string> {\n const out: Record<string, string> = {};\n // Iterate the NamedNodeMap directly — element.attributes is live and\n // we want a snapshot.\n for (let i = 0; i < element.attributes.length; i++) {\n const attr = element.attributes[i];\n const name = attr.name.toLowerCase();\n if (NOISY_ATTRS.has(name)) continue;\n const value = attr.value;\n if (value.length > 200) continue;\n out[name] = value;\n }\n return out;\n}\n\n/**\n * Walk parentElement up to <html>, returning a chain of ancestors that\n * actually carry identifying information (an id or a non-empty class).\n * Generic `<div>` / `<body>` / `<html>` wrappers are skipped — they bloat\n * the payload without telling the agent anything useful about layout.\n *\n * Output is root-first: outermost meaningful ancestor → nearest.\n * Each entry is {tag, id?, className?}.\n */\nfunction extractParentChain(element: Element): Array<{ tag: string; id?: string; className?: string }> {\n const chain: Array<{ tag: string; id?: string; className?: string }> = [];\n let current: Element | null = element.parentElement;\n while (current && current.nodeType === 1) {\n const id = current.id || undefined;\n // Drop whitespace-only class strings; keep the raw className (not split)\n // so agents see the original authoring intent.\n const cls = (current.getAttribute('class') ?? '').trim() || undefined;\n\n if (id || cls) {\n const entry: { tag: string; id?: string; className?: string } = {\n tag: current.tagName.toLowerCase(),\n };\n if (id) entry.id = id;\n if (cls) entry.className = cls;\n chain.unshift(entry);\n }\n\n if (current.tagName === 'HTML') break;\n current = current.parentElement;\n }\n return chain;\n}\n\n/**\n * Truncate outerHTML at a char boundary, preserving a trailing ellipsis\n * and the closing tag when possible so the snippet still parses visually.\n */\nfunction truncateHtml(html: string, max: number): string {\n if (html.length <= max) return html;\n // Try to keep the closing tag intact so the snippet reads as an open+close pair.\n const slice = html.substring(0, max - 1);\n const lastLt = slice.lastIndexOf('<');\n // If we sliced mid-tag (a '<' within the last 30 chars without a closing '>'),\n // drop that partial tag.\n const cleaned = lastLt > max - 30 && !slice.slice(lastLt).includes('>')\n ? slice.substring(0, lastLt)\n : slice;\n return cleaned + '…';\n}\n\n/**\n * Main extraction function. Pure — no DOM side effects.\n */\nexport function extract(element: Element): SelectionContext {\n const rect = element.getBoundingClientRect();\n\n return {\n tagName: element.tagName.toLowerCase(),\n elementText: truncateText(element.textContent ?? '', 200),\n elementHtml: truncateHtml(element.outerHTML, 1500),\n elementAttributes: extractAttributes(element),\n parentChain: extractParentChain(element),\n boundingRect: {\n left: Math.round(rect.left),\n top: Math.round(rect.top),\n width: Math.round(rect.width),\n height: Math.round(rect.height),\n },\n };\n}\n\nexport type { SelectionContext };\n","// ── ElementPicker — DOM element selection with capture-phase interception ─\n//\n// Attaches mousemove/click/keydown listeners in CAPTURE phase to document.\n// mousemove → Highlighter.highlight (rAF-throttled)\n// click → extract + IssueForm.show (capture-phase interception)\n// keydown → Escape exits selection mode\n// scroll → refresh highlight\n//\n// All listeners registered with AbortSignal; deactivate() = controller.abort().\n\nimport type { Highlighter } from './Highlighter';\nimport { extract } from './SelectionExtractor';\nimport type { SelectionContext } from './types';\n\nexport interface ElementPickerOptions {\n /** The overlay host element — clicks inside this are ignored. */\n overlayHostElement: HTMLElement;\n highlighter: Highlighter;\n onElementClick: (element: Element, context: SelectionContext) => void;\n onEscape: () => void;\n}\n\nexport class ElementPicker {\n private _options: ElementPickerOptions;\n private _controller: AbortController | null = null;\n private _active = false;\n private _rafId: number | null = null;\n private _pendingEvent: MouseEvent | null = null;\n\n constructor(options: ElementPickerOptions) {\n this._options = options;\n }\n\n get isActive(): boolean {\n return this._active;\n }\n\n /**\n * Activate the picker: attach capture-phase listeners to document.\n * No-op if already active (state machine guard).\n */\n activate(): void {\n if (this._active) return;\n\n this._controller = new AbortController();\n this._active = true;\n\n const signal = this._controller.signal;\n\n document.addEventListener('mousemove', this.onMouseMove, { capture: true, signal });\n document.addEventListener('click', this.onClick, { capture: true, signal });\n document.addEventListener('keydown', this.onKeyDown, { capture: true, signal });\n document.addEventListener('scroll', this.onScroll, { capture: true, signal });\n }\n\n /**\n * Deactivate the picker: abort controller removes all listeners atomically.\n * Safe to call even if not active.\n */\n deactivate(): void {\n if (!this._active) return;\n\n this._controller?.abort();\n this._controller = null;\n this._active = false;\n\n if (this._rafId !== null) {\n cancelAnimationFrame(this._rafId);\n this._rafId = null;\n }\n this._pendingEvent = null;\n\n this._options.highlighter.clear();\n }\n\n // ── Event handlers ──────────────────────────────────────────────────\n\n private onMouseMove = (e: MouseEvent): void => {\n this._pendingEvent = e;\n\n if (this._rafId !== null) return;\n\n this._rafId = requestAnimationFrame(() => {\n this._rafId = null;\n const evt = this._pendingEvent;\n if (!evt) return;\n\n const target = evt.target as Element | null;\n if (!target || target.nodeType !== 1) return;\n\n // Filter out overlay host and document-level elements\n if (this._options.overlayHostElement.contains(target)) return;\n if (target === document.body || target === document.documentElement) return;\n\n this._options.highlighter.highlight(target);\n });\n };\n\n private onClick = (e: MouseEvent): void => {\n // CRITICAL: Capture-phase interception prevents host app click handlers\n e.preventDefault();\n e.stopPropagation();\n\n const target = e.target as Element | null;\n\n if (!target || target.nodeType !== 1) return;\n if (this._options.overlayHostElement.contains(target)) return;\n if (!target.isConnected) return;\n\n let context: SelectionContext;\n try {\n context = extract(target);\n } catch {\n return;\n }\n\n this._options.onElementClick(target, context);\n };\n\n private onKeyDown = (e: KeyboardEvent): void => {\n if (e.key === 'Escape') {\n e.preventDefault();\n e.stopPropagation();\n this._options.onEscape();\n }\n };\n\n private onScroll = (): void => {\n if (this._rafId !== null) return;\n\n this._rafId = requestAnimationFrame(() => {\n this._rafId = null;\n const evt = this._pendingEvent;\n if (!evt) return;\n\n const target = evt.target as Element | null;\n if (target && target.isConnected && !this._options.overlayHostElement.contains(target)) {\n this._options.highlighter.highlight(target);\n }\n });\n };\n}\n","// ── KeyComboDetector — Hold-to-charge key-combo detection ────────────────\n//\n// Default combo: Ctrl+Shift+E (NOT Ctrl+Shift+F which conflicts with browser fullscreen).\n// Charge cycle: holding the combo for `holdMs` (default 2000ms) \"charges up\"\n// the activation. Progress is streamed via onChargeProgress so the UI can\n// render a radial indicator next to the cursor — this replaces the older\n// silent 500ms timer that gave users zero feedback during the hold.\n//\n// Lifecycle:\n// keys down → onChargeStart, then onChargeProgress(frac) via rAF until frac=1\n// frac = 1 → onCharged fires once\n// any key up → onChargeCancel (consumer decides what to do with downstream state)\n//\n// Multiple keydown events (browser auto-repeat) are debounced — the charge\n// only starts when the first matching keydown fires.\n\nexport interface KeyComboConfig {\n ctrl?: boolean;\n shift?: boolean;\n alt?: boolean;\n key: string;\n}\n\nexport interface KeyComboCallbacks {\n /** Fires once when the combo keys go down and a charge begins. */\n onChargeStart?: () => void;\n /** Fires on every animation frame while charging. `frac` is 0..1. */\n onChargeProgress?: (frac: number) => void;\n /** Fires once when the hold reaches the threshold. */\n onCharged?: () => void;\n /**\n * Fires when any combo key is released.\n * Also fires once after onCharged so the consumer can tear down any\n * post-charge state (e.g. exit selector mode) — distinct from the\n * mid-charge cancel case, which the consumer identifies by checking\n * its own state.\n */\n onChargeCancel?: () => void;\n}\n\nconst DEFAULT_HOLD_MS = 2000;\n\nconst DEFAULT_COMBO: KeyComboConfig = {\n ctrl: true,\n shift: true,\n key: 'e',\n};\n\nfunction parseCombo(comboStr?: string): KeyComboConfig {\n if (!comboStr) return DEFAULT_COMBO;\n\n const lower = comboStr.toLowerCase();\n const parts = lower.split('+').map((p) => p.trim());\n\n const config: KeyComboConfig = { key: '' };\n\n for (const part of parts) {\n switch (part) {\n case 'ctrl':\n case 'control':\n config.ctrl = true;\n break;\n case 'shift':\n config.shift = true;\n break;\n case 'alt':\n config.alt = true;\n break;\n default:\n config.key = part;\n }\n }\n\n if (!config.key) return DEFAULT_COMBO;\n\n return config;\n}\n\nexport class KeyComboDetector {\n private _combo: KeyComboConfig;\n private _holdMs: number;\n private _callbacks: KeyComboCallbacks;\n private _controller: AbortController | null = null;\n\n // Charge state\n private _chargeStart = 0;\n private _charging = false;\n private _charged = false;\n private _rafId: number | null = null;\n\n constructor(comboStr: string | undefined, callbacks: KeyComboCallbacks, holdMs: number = DEFAULT_HOLD_MS) {\n this._combo = parseCombo(comboStr);\n this._holdMs = holdMs;\n this._callbacks = callbacks;\n }\n\n /** Start listening for the key combo. */\n start(): void {\n if (this._controller) return;\n\n this._controller = new AbortController();\n const signal = this._controller.signal;\n\n document.addEventListener('keydown', this.onKeyDown, { capture: true, signal });\n document.addEventListener('keyup', this.onKeyUp, { capture: true, signal });\n }\n\n /** Stop listening. Cancels any in-flight charge. */\n stop(): void {\n this._controller?.abort();\n this._controller = null;\n this.cancelRaf();\n this._charging = false;\n this._charged = false;\n this._chargeStart = 0;\n }\n\n /** Check if a KeyboardEvent matches the configured combo. */\n matchesCombo(e: KeyboardEvent): boolean {\n if (this._combo.ctrl && !e.ctrlKey) return false;\n if (this._combo.shift && !e.shiftKey) return false;\n if (this._combo.alt && !e.altKey) return false;\n return e.key.toLowerCase() === this._combo.key;\n }\n\n // ── Handlers ────────────────────────────────────────────────────────\n\n private onKeyDown = (e: KeyboardEvent): void => {\n if (!this.matchesCombo(e)) return;\n // Browser auto-repeat fires keydown repeatedly while held. Only start\n // the charge on the initial press.\n if (this._charging || this._charged) return;\n\n this._charging = true;\n this._charged = false;\n this._chargeStart = performance.now();\n this._callbacks.onChargeStart?.();\n this.tick();\n };\n\n private onKeyUp = (e: KeyboardEvent): void => {\n if (!this.isComboKey(e.key.toLowerCase())) return;\n\n // Capture the mode at the moment of release so we can tell the consumer\n // whether this was a mid-charge cancel or a post-charge release.\n const wasCharging = this._charging;\n const wasCharged = this._charged;\n\n this.cancelRaf();\n this._charging = false;\n this._charged = false;\n this._chargeStart = 0;\n\n // Always fire onChargeCancel — the consumer branches on its own state\n // (e.g. setup.ts treats 'charging' and 'selecting' differently).\n // Only fire if a charge cycle actually started.\n if (wasCharging || wasCharged) {\n this._callbacks.onChargeCancel?.();\n }\n };\n\n private tick = (): void => {\n if (!this._charging) return;\n\n const elapsed = performance.now() - this._chargeStart;\n const frac = Math.min(1, elapsed / this._holdMs);\n this._callbacks.onChargeProgress?.(frac);\n\n if (frac >= 1) {\n this._charging = false;\n this._charged = true;\n this.cancelRaf();\n this._callbacks.onCharged?.();\n return;\n }\n\n this._rafId = requestAnimationFrame(this.tick);\n };\n\n private cancelRaf(): void {\n if (this._rafId !== null) {\n cancelAnimationFrame(this._rafId);\n this._rafId = null;\n }\n }\n\n private isComboKey(key: string): boolean {\n if (key === this._combo.key) return true;\n if (this._combo.ctrl && key === 'control') return true;\n if (this._combo.shift && key === 'shift') return true;\n if (this._combo.alt && key === 'alt') return true;\n return false;\n }\n}\n","// ── FloatingLogo — Draggable Synod logo with localStorage persistence ────\n//\n// Pointer Events API: pointerdown → setPointerCapture → drag.\n// Click vs. drag: <5px displacement = click → onLogoClick callback.\n// Position persisted to localStorage key 'synod-feedback-logo-pos'.\n// Badge shows queued findings count.\n\nimport { createSynodLogo } from '../utils/logo';\n\nexport interface FloatingLogoCallbacks {\n onClick: () => void;\n onCountChange?: (count: number) => void;\n /**\n * Fired when the user right-clicks the logo. Receives the viewport\n * coordinates of the click so a context menu can be anchored there.\n * The default browser context menu is suppressed.\n */\n onContextMenu?: (x: number, y: number) => void;\n}\n\nconst STORAGE_KEY = 'synod-feedback-logo-pos';\nconst CLICK_THRESHOLD = 5; // px\n\nexport class FloatingLogo {\n private _logo: HTMLDivElement | null = null;\n private _badge: HTMLDivElement | null = null;\n private _callbacks: FloatingLogoCallbacks;\n\n // Drag state\n private _dragging = false;\n private _startX = 0;\n private _startY = 0;\n private _logoX = 0;\n private _logoY = 0;\n private _moved = false;\n\n constructor(_root: ShadowRoot | null, callbacks: FloatingLogoCallbacks) {\n void _root; // retained for API compatibility\n this._callbacks = callbacks;\n }\n\n /**\n * Mount the logo into the shadow root content wrapper.\n */\n mount(wrapper: HTMLElement): void {\n if (this._logo) return;\n\n const logo = document.createElement('div');\n logo.className = 'synod-fb-logo';\n logo.appendChild(createSynodLogo(28));\n logo.setAttribute('role', 'button');\n logo.setAttribute('aria-label', 'Synod feedback — click to open queue');\n logo.setAttribute('tabindex', '0');\n\n // Restore position from localStorage or use default (bottom-right)\n const pos = this.loadPosition();\n logo.style.left = `${pos.x}px`;\n logo.style.top = `${pos.y}px`;\n\n this._logoX = pos.x;\n this._logoY = pos.y;\n\n // Badge (initially hidden)\n const badge = document.createElement('div');\n badge.className = 'synod-fb-badge';\n badge.style.display = 'none';\n badge.setAttribute('aria-live', 'polite');\n logo.appendChild(badge);\n\n // Pointer events for drag\n logo.addEventListener('pointerdown', this.onPointerDown);\n logo.addEventListener('keydown', this.onKeyDown);\n // Right-click → custom context menu (suppress browser default)\n logo.addEventListener('contextmenu', this.onContextMenu);\n\n wrapper.appendChild(logo);\n\n this._logo = logo;\n this._badge = badge;\n }\n\n /**\n * Remove the logo from the DOM.\n */\n unmount(): void {\n this._logo?.removeEventListener('pointerdown', this.onPointerDown);\n this._logo?.removeEventListener('keydown', this.onKeyDown);\n this._logo?.removeEventListener('contextmenu', this.onContextMenu);\n this._logo?.remove();\n this._logo = null;\n this._badge = null;\n }\n\n /**\n * Update the badge count.\n */\n updateCount(count: number): void {\n if (!this._badge || !this._logo) return;\n\n if (count > 0) {\n this._badge.style.display = 'flex';\n this._badge.textContent = String(count);\n this._badge.setAttribute('aria-label', `${count} ${count === 1 ? 'finding' : 'findings'} queued`);\n\n this._badge.classList.remove('synod-fb-logo-badge-pulse');\n void this._badge.offsetWidth;\n this._badge.classList.add('synod-fb-logo-badge-pulse');\n } else {\n this._badge.style.display = 'none';\n }\n\n this._callbacks.onCountChange?.(count);\n }\n\n // ── Drag handlers ───────────────────────────────────────────────────\n\n private onPointerDown = (e: PointerEvent): void => {\n if (!this._logo) return;\n\n e.preventDefault();\n e.stopPropagation();\n\n this._dragging = true;\n this._moved = false;\n this._startX = e.clientX;\n this._startY = e.clientY;\n\n try {\n this._logo.setPointerCapture(e.pointerId);\n } catch {\n // jsdom or older browsers may not support setPointerCapture\n }\n this._logo.addEventListener('pointermove', this.onPointerMove);\n this._logo.addEventListener('pointerup', this.onPointerUp);\n this._logo.addEventListener('pointercancel', this.onPointerUp);\n };\n\n private onPointerMove = (e: PointerEvent): void => {\n if (!this._dragging || !this._logo) return;\n\n const dx = e.clientX - this._startX;\n const dy = e.clientY - this._startY;\n\n if (!this._moved && Math.abs(dx) + Math.abs(dy) > CLICK_THRESHOLD) {\n this._moved = true;\n }\n\n if (!this._moved) return;\n\n const newX = this._logoX + dx;\n const newY = this._logoY + dy;\n\n const clampedX = Math.max(8, Math.min(newX, window.innerWidth - 56));\n const clampedY = Math.max(8, Math.min(newY, window.innerHeight - 56));\n\n this._logo.style.left = `${clampedX}px`;\n this._logo.style.top = `${clampedY}px`;\n };\n\n private onPointerUp = (e: PointerEvent): void => {\n if (!this._logo) return;\n\n this._dragging = false;\n\n try {\n this._logo.releasePointerCapture(e.pointerId);\n } catch {\n // ignore if not captured\n }\n\n this._logo.removeEventListener('pointermove', this.onPointerMove);\n this._logo.removeEventListener('pointerup', this.onPointerUp);\n this._logo.removeEventListener('pointercancel', this.onPointerUp);\n\n if (this._moved) {\n const left = parseFloat(this._logo.style.left || '0');\n const top = parseFloat(this._logo.style.top || '0');\n this._logoX = left;\n this._logoY = top;\n this.savePosition(left, top);\n } else {\n this._callbacks.onClick();\n }\n };\n\n private onKeyDown = (e: KeyboardEvent): void => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n this._callbacks.onClick();\n }\n };\n\n private onContextMenu = (e: MouseEvent): void => {\n // Always suppress the browser's native context menu so the host page\n // doesn't show \"Save image / Inspect / ...\" on top of our logo. The\n // consumer's onContextMenu callback (if any) decides whether to show\n // our custom menu.\n e.preventDefault();\n e.stopPropagation();\n this._callbacks.onContextMenu?.(e.clientX, e.clientY);\n };\n\n // ── localStorage ────────────────────────────────────────────────────\n\n private loadPosition(): { x: number; y: number } {\n try {\n const raw = localStorage.getItem(STORAGE_KEY);\n if (raw) {\n const pos = JSON.parse(raw);\n if (typeof pos.x === 'number' && typeof pos.y === 'number') {\n return pos;\n }\n }\n } catch {\n // ignore\n }\n\n return {\n x: Math.max(8, window.innerWidth - 64),\n y: Math.max(8, window.innerHeight - 64),\n };\n }\n\n private savePosition(x: number, y: number): void {\n try {\n localStorage.setItem(STORAGE_KEY, JSON.stringify({ x, y }));\n } catch {\n // QuotaExceededError — silently fail\n }\n }\n}\n","// ── Viewport flip positioning utility ────────────────────────────────────\n\nexport interface DOMRect {\n left: number;\n top: number;\n width: number;\n height: number;\n}\n\nexport interface ElementSize {\n width: number;\n height: number;\n}\n\nexport interface Viewport {\n innerWidth: number;\n innerHeight: number;\n}\n\nexport interface PositionedRect {\n left: number;\n top: number;\n}\n\nexport const MIN_MARGIN = 8;\n\n/**\n * Calculate the optimal position for a popup relative to a target element rect.\n *\n * Default: position below the element (top = rect.bottom + 8).\n * Vertical flip: if not enough space below, position above.\n * Horizontal clamp: if right edge overflows, shift left.\n * Minimum margin of 8px from viewport edges.\n *\n * @param targetRect Bounding rect of the selected element\n * @param elementSize Width/height of the popup to position\n * @param viewport Viewport dimensions\n * @param margin Gap between element and popup (default 8px)\n */\nexport function calculateFlippedPosition(\n targetRect: DOMRect,\n elementSize: ElementSize,\n viewport: Viewport,\n margin: number = MIN_MARGIN,\n): PositionedRect {\n const rectBottom = targetRect.top + targetRect.height;\n const spaceBelow = viewport.innerHeight - rectBottom;\n\n let top: number;\n\n // Vertical flip: default below, flip above if not enough space\n if (spaceBelow < elementSize.height + margin) {\n // Not enough space below → place above\n top = targetRect.top - elementSize.height - margin;\n } else {\n // Default: below\n top = rectBottom + margin;\n }\n\n // Horizontal: default align to element left\n let left = targetRect.left;\n\n // Horizontal clamp: shift left if right edge overflows\n if (left + elementSize.width + margin > viewport.innerWidth) {\n left = viewport.innerWidth - elementSize.width - margin * 2;\n }\n\n // Clamp to minimum margin\n top = Math.max(top, MIN_MARGIN);\n left = Math.max(left, MIN_MARGIN);\n\n return { left, top };\n}\n","// ── Screenshot capture — lazy-loaded via modern-screenshot ────────────────\n//\n// Optional/best-effort: dynamic import('modern-screenshot'), compressed to\n// ≤8KB thumbnail. Cross-origin tainting is caught gracefully.\n\n/** Maximum acceptable data URL size (8KB, leaving room in 10KB context_json). */\nexport const MAX_SCREENSHOT_BYTES = 8192;\n\n/**\n * Capture a screenshot of an element as a compressed JPEG data URL.\n *\n * Uses dynamic import of modern-screenshot (~40KB, lazy-loaded).\n * Compresses to 200px thumbnail at JPEG quality 0.4.\n *\n * @returns data URL string if successful and ≤8KB, or null on failure/oversize.\n */\nexport async function capture(element: Element): Promise<string | null> {\n try {\n const mod = await import('modern-screenshot');\n const domToJpeg = (mod as any).domToJpeg ?? (mod as any).default?.domToJpeg;\n\n if (typeof domToJpeg !== 'function') {\n console.warn('[synod-feedback] Screenshot: domToJpeg not found in modern-screenshot module');\n return null;\n }\n\n const dataUrl: string = await domToJpeg(element, { quality: 0.4, width: 200 });\n\n if (typeof dataUrl !== 'string' || dataUrl.length === 0) {\n console.warn('[synod-feedback] Screenshot: domToJpeg returned invalid data');\n return null;\n }\n\n // Size gate: omit if > 8KB (no retry, best-effort)\n if (dataUrl.length > MAX_SCREENSHOT_BYTES) {\n console.warn(\n `[synod-feedback] Screenshot: data URL too large (${dataUrl.length} bytes > ${MAX_SCREENSHOT_BYTES} limit), omitting`,\n );\n return null;\n }\n\n return dataUrl;\n } catch (err) {\n console.warn('[synod-feedback] Screenshot capture failed:', err);\n return null;\n }\n}\n","// ── IssueForm — Inline capture panel with viewport flip ──────────────────\n//\n// Positioned relative to selected element using calculateFlippedPosition().\n// Focus trap: Tab cycles within form fields. Esc cancels.\n// Buttons: \"Add to tray\" (queue + return to selecting), \"Send now\" (single send).\n// Screenshot toggle: off by default, triggers lazy screenshot capture.\n\nimport type { FindingSeverity, FindingContextJson } from '../client/types';\nimport type { SelectionContext } from '../interaction/types';\nimport { calculateFlippedPosition } from '../utils/viewport';\nimport { capture as captureScreenshot } from '../utils/screenshot';\nimport { createSynodLogo } from '../utils/logo';\n\nexport interface IssueFormResult {\n description: string;\n severity: FindingSeverity;\n contextJson: FindingContextJson;\n}\n\nexport interface IssueFormCallbacks {\n onAddToTray: (result: IssueFormResult) => void;\n onSendNow: (result: IssueFormResult) => void;\n onCancel: () => void;\n}\n\nconst FORM_WIDTH = 340;\n\nexport class IssueForm {\n private _wrapper: HTMLElement;\n private _callbacks: IssueFormCallbacks;\n private _formEl: HTMLDivElement | null = null;\n private _currentContext: SelectionContext | null = null;\n private _screenshotDataUrl: string | null = null;\n private _screenshotElement: Element | null = null;\n private _previouslyFocused: Element | null = null;\n private _descTextarea: HTMLTextAreaElement | null = null;\n private _severity: FindingSeverity = 'medium';\n private _screenshotToggle: HTMLInputElement | null = null;\n private _screenshotLabel: HTMLLabelElement | null = null;\n\n constructor(wrapper: HTMLElement, callbacks: IssueFormCallbacks) {\n this._wrapper = wrapper;\n this._callbacks = callbacks;\n }\n\n show(context: SelectionContext, element?: Element): void {\n this.hide();\n\n this._currentContext = context;\n this._screenshotElement = element ?? null;\n this._screenshotDataUrl = null;\n this._severity = 'medium';\n\n this._previouslyFocused = document.activeElement;\n\n const form = this.buildForm();\n this._formEl = form;\n\n const estimatedHeight = 380;\n const pos = calculateFlippedPosition(\n context.boundingRect,\n { width: FORM_WIDTH, height: estimatedHeight },\n { innerWidth: window.innerWidth, innerHeight: window.innerHeight },\n );\n\n form.style.left = `${pos.left}px`;\n form.style.top = `${pos.top}px`;\n\n this._wrapper.appendChild(form);\n\n requestAnimationFrame(() => {\n if (!this._formEl) return;\n const actualHeight = this._formEl.offsetHeight;\n if (Math.abs(actualHeight - estimatedHeight) > 20) {\n const adjustedPos = calculateFlippedPosition(\n context.boundingRect,\n { width: FORM_WIDTH, height: actualHeight },\n { innerWidth: window.innerWidth, innerHeight: window.innerHeight },\n );\n this._formEl.style.left = `${adjustedPos.left}px`;\n this._formEl.style.top = `${adjustedPos.top}px`;\n }\n });\n\n requestAnimationFrame(() => {\n this._descTextarea?.focus();\n });\n\n form.addEventListener('keydown', this.onKeyDown);\n }\n\n hide(): void {\n this._formEl?.removeEventListener('keydown', this.onKeyDown);\n this._formEl?.remove();\n this._formEl = null;\n this._currentContext = null;\n this._screenshotDataUrl = null;\n this._screenshotElement = null;\n\n if (this._previouslyFocused && this._previouslyFocused.isConnected) {\n (this._previouslyFocused as HTMLElement).focus?.();\n }\n this._previouslyFocused = null;\n }\n\n get isVisible(): boolean {\n return this._formEl !== null && this._formEl.isConnected;\n }\n\n private buildForm(): HTMLDivElement {\n const form = document.createElement('div');\n form.className = 'synod-fb-form';\n form.setAttribute('role', 'dialog');\n form.setAttribute('aria-modal', 'true');\n form.setAttribute('aria-label', 'Describe issue');\n\n // Header\n const header = document.createElement('div');\n header.className = 'synod-fb-form-header';\n\n const headerLogo = document.createElement('div');\n headerLogo.className = 'synod-fb-form-header-logo';\n headerLogo.appendChild(createSynodLogo(16));\n\n const headerText = document.createElement('div');\n headerText.className = 'synod-fb-form-header-text';\n // Build a compact tag#id.class descriptor for the header subtitle — the\n // previous CSS-selector snippet was nth-of-type noise that wasn't useful\n // even as a visual identifier.\n const ctx = this._currentContext!;\n const id = ctx.elementAttributes?.['id'];\n const cls = ctx.elementAttributes?.['class']?.trim();\n let descriptor = ctx.tagName;\n if (id) descriptor += `#${id}`;\n if (cls) descriptor += '.' + cls.split(/\\s+/).join('.');\n if (descriptor.length > 50) descriptor = descriptor.substring(0, 49) + '…';\n headerText.textContent = `${descriptor} · ${ctx.boundingRect.width}×${ctx.boundingRect.height}`;\n\n const closeBtn = document.createElement('button');\n closeBtn.className = 'synod-fb-form-close';\n closeBtn.textContent = '×';\n closeBtn.setAttribute('aria-label', 'Cancel');\n closeBtn.addEventListener('click', () => this._callbacks.onCancel());\n\n header.appendChild(headerLogo);\n header.appendChild(headerText);\n header.appendChild(closeBtn);\n\n // Body\n const body = document.createElement('div');\n body.className = 'synod-fb-form-body';\n\n // Description field (title is AI-generated from this — see setup.ts)\n const descField = document.createElement('div');\n descField.className = 'synod-fb-field';\n const descLabel = document.createElement('label');\n descLabel.className = 'synod-fb-label';\n descLabel.textContent = 'Description *';\n descLabel.setAttribute('for', 'synod-fb-desc');\n const descTextarea = document.createElement('textarea');\n descTextarea.className = 'synod-fb-textarea';\n descTextarea.id = 'synod-fb-desc';\n descTextarea.rows = 3;\n descTextarea.maxLength = 2000;\n descTextarea.setAttribute('aria-label', 'Issue description');\n descTextarea.setAttribute('placeholder', 'Describe what’s wrong…');\n descTextarea.setAttribute('required', '');\n this._descTextarea = descTextarea;\n descField.appendChild(descLabel);\n descField.appendChild(descTextarea);\n\n // Severity segmented control\n const sevField = document.createElement('div');\n sevField.className = 'synod-fb-field';\n const sevLabel = document.createElement('div');\n sevLabel.className = 'synod-fb-label';\n sevLabel.textContent = 'Severity';\n const sevGroup = document.createElement('div');\n sevGroup.className = 'synod-fb-severity-group';\n sevGroup.setAttribute('role', 'group');\n sevGroup.setAttribute('aria-label', 'Severity');\n\n const severities: FindingSeverity[] = ['low', 'medium', 'high'];\n for (const sev of severities) {\n const btn = document.createElement('button');\n btn.className = 'synod-fb-sev-btn';\n btn.dataset.sev = sev;\n btn.textContent = sev.charAt(0).toUpperCase() + sev.slice(1);\n btn.setAttribute('aria-pressed', sev === this._severity ? 'true' : 'false');\n if (sev === this._severity) btn.classList.add('active');\n btn.addEventListener('click', (e) => {\n e.preventDefault();\n this._severity = sev;\n sevGroup.querySelectorAll('.synod-fb-sev-btn').forEach((b) => {\n const btnSev = (b as HTMLButtonElement).dataset.sev;\n b.setAttribute('aria-pressed', btnSev === sev ? 'true' : 'false');\n b.classList.toggle('active', btnSev === sev);\n });\n });\n btn.addEventListener('keydown', (e) => {\n if (e.key === 'ArrowRight' || e.key === 'ArrowLeft') {\n e.preventDefault();\n const btns = Array.from(sevGroup.querySelectorAll('.synod-fb-sev-btn')) as HTMLButtonElement[];\n const idx = btns.indexOf(e.currentTarget as HTMLButtonElement);\n const nextIdx = e.key === 'ArrowRight'\n ? (idx + 1) % btns.length\n : (idx - 1 + btns.length) % btns.length;\n btns[nextIdx].click();\n btns[nextIdx].focus();\n }\n });\n sevGroup.appendChild(btn);\n }\n sevField.appendChild(sevLabel);\n sevField.appendChild(sevGroup);\n\n // Screenshot toggle\n const ssField = document.createElement('div');\n ssField.className = 'synod-fb-field';\n const ssToggle = document.createElement('label');\n ssToggle.className = 'synod-fb-screenshot-toggle';\n const ssCheckbox = document.createElement('input');\n ssCheckbox.type = 'checkbox';\n ssCheckbox.addEventListener('change', () => this.onScreenshotToggle(ssCheckbox.checked));\n this._screenshotToggle = ssCheckbox;\n const ssLabel = document.createElement('span');\n ssLabel.textContent = '📷 Include screenshot';\n this._screenshotLabel = ssToggle;\n ssToggle.appendChild(ssCheckbox);\n ssToggle.appendChild(ssLabel);\n ssField.appendChild(ssToggle);\n\n // Action buttons\n const actions = document.createElement('div');\n actions.className = 'synod-fb-form-actions';\n const addBtn = document.createElement('button');\n addBtn.className = 'synod-fb-btn-secondary';\n addBtn.textContent = '+ Add to tray';\n addBtn.addEventListener('click', (e) => {\n e.preventDefault();\n this.handleSubmit('add');\n });\n\n const sendBtn = document.createElement('button');\n sendBtn.className = 'synod-fb-btn-primary';\n sendBtn.textContent = 'Send now';\n sendBtn.addEventListener('click', (e) => {\n e.preventDefault();\n this.handleSubmit('send');\n });\n\n actions.appendChild(addBtn);\n actions.appendChild(sendBtn);\n\n body.appendChild(descField);\n body.appendChild(sevField);\n body.appendChild(ssField);\n body.appendChild(actions);\n\n form.appendChild(header);\n form.appendChild(body);\n\n return form;\n }\n\n private async onScreenshotToggle(checked: boolean): Promise<void> {\n if (!checked) {\n this._screenshotDataUrl = null;\n return;\n }\n\n if (!this._screenshotElement) {\n this.markScreenshotUnavailable();\n return;\n }\n\n if (this._screenshotLabel) {\n const labelSpan = this._screenshotLabel.querySelector('span');\n if (labelSpan) labelSpan.textContent = '📷 Capturing…';\n }\n\n const dataUrl = await captureScreenshot(this._screenshotElement);\n\n if (dataUrl) {\n this._screenshotDataUrl = dataUrl;\n if (this._screenshotLabel) {\n const labelSpan = this._screenshotLabel.querySelector('span');\n if (labelSpan) labelSpan.textContent = '📷 Screenshot captured ✓';\n }\n } else {\n this.markScreenshotUnavailable();\n }\n }\n\n private markScreenshotUnavailable(): void {\n this._screenshotDataUrl = null;\n if (this._screenshotToggle) {\n this._screenshotToggle.checked = false;\n this._screenshotToggle.disabled = true;\n }\n if (this._screenshotLabel) {\n this._screenshotLabel.classList.add('disabled', 'unavailable');\n const labelSpan = this._screenshotLabel.querySelector('span');\n if (labelSpan) labelSpan.textContent = '⚠ Screenshot unavailable';\n }\n }\n\n private handleSubmit(action: 'add' | 'send'): void {\n if (!this._currentContext || !this._descTextarea) return;\n\n const description = this._descTextarea.value.trim();\n if (!description) return;\n\n const contextJson: FindingContextJson = {\n element_text: this._currentContext.elementText,\n element_html: this._currentContext.elementHtml,\n element_attributes: this._currentContext.elementAttributes,\n parent_chain: this._currentContext.parentChain,\n url: typeof window !== 'undefined' ? window.location.href : undefined,\n // Best-effort route — pathname + hash + search captures the SPA \"screen\"\n // without re-sending the origin (already in url).\n route: typeof window !== 'undefined'\n ? window.location.pathname + window.location.search + window.location.hash\n : undefined,\n page_title: typeof document !== 'undefined' ? document.title : undefined,\n viewport: typeof window !== 'undefined'\n ? { width: window.innerWidth, height: window.innerHeight }\n : undefined,\n user_agent: typeof navigator !== 'undefined' ? navigator.userAgent : undefined,\n ...(this._screenshotDataUrl ? { screenshot_data_url: this._screenshotDataUrl } : {}),\n };\n\n const result: IssueFormResult = {\n description,\n severity: this._severity,\n contextJson,\n };\n\n if (action === 'add') {\n this._callbacks.onAddToTray(result);\n } else {\n this._callbacks.onSendNow(result);\n }\n }\n\n private onKeyDown = (e: KeyboardEvent): void => {\n if (e.key === 'Escape') {\n e.preventDefault();\n e.stopPropagation();\n this._callbacks.onCancel();\n return;\n }\n\n if (e.key === 'Tab' && this._formEl) {\n const focusable = this.getFocusableElements();\n if (focusable.length === 0) return;\n\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n\n if (e.shiftKey) {\n if (document.activeElement === first) {\n e.preventDefault();\n last.focus();\n }\n } else {\n if (document.activeElement === last) {\n e.preventDefault();\n first.focus();\n }\n }\n }\n\n if (e.ctrlKey && e.key === 'Enter') {\n e.preventDefault();\n this.handleSubmit(e.shiftKey ? 'send' : 'add');\n }\n };\n\n private getFocusableElements(): HTMLElement[] {\n if (!this._formEl) return [];\n return Array.from(\n this._formEl.querySelectorAll<HTMLElement>(\n 'input:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex]:not([tabindex=\"-1\"])',\n ),\n );\n }\n}\n","// ── Title helpers — pending-state + queue display utilities ──────────────\n//\n// The browser SDK no longer generates titles itself — that happens\n// server-side on finding receive. These helpers just let the queue UI\n// distinguish \"title pending\" (empty placeholder) and present a readable\n// snippet of the description where the title would normally appear.\n\n/**\n * Sentinel value used in FindingPayload.title while no title has been\n * assigned yet. The gateway replaces this with a real title on receive.\n */\nexport const PENDING_TITLE = '';\n\n/**\n * True when the finding's title is still awaiting server-side generation.\n */\nexport function isTitlePending(title: string | undefined | null): boolean {\n return !title || title.trim() === '';\n}\n\n/**\n * Short single-line snippet of a description, suitable for queue row labels\n * when no title exists yet. Collapses whitespace, truncates at ~60 chars\n * on a word boundary with an ellipsis.\n */\nexport function truncDesc(description: string): string {\n const trimmed = (description ?? '').trim().replace(/\\s+/g, ' ');\n if (trimmed.length <= 60) return trimmed;\n\n const slice = trimmed.slice(0, 57);\n const lastSpace = slice.lastIndexOf(' ');\n const head = lastSpace > 25 ? slice.slice(0, lastSpace) : slice;\n return head + '…';\n}\n","// ── Clipboard codec for offline finding transfer ────────────────────────\n//\n// Used by the extension's \"Copy to clipboard\" queue button and by the\n// Synod Desktop Findings panel \"Paste from clipboard\" icon. Lets a user\n// capture findings on a machine that can't reach the gateway (e.g. a\n// stakeholder's laptop at a customer site) and bring them back to the\n// desktop app for ingestion.\n//\n// Format: a single string prefixed `synod-fb-v1:` followed by the URL-safe\n// base64 encoding of a UTF-8 JSON payload. We don't ship raw JSON because\n// (a) it's friendlier for the user if they accidentally paste it somewhere,\n// (b) base64 survives clipboard managers / rich-text editors that mangle\n// whitespace and quote characters, and (c) the prefix lets the desktop\n// validate the blob cheaply before attempting to decode.\n//\n// Screenshots are stripped on encode — they are too large for the\n// clipboard and don't survive the round-trip reliably.\n\nimport type { FindingPayload, FindingSeverity } from '../client/types';\n\nconst PREFIX = 'synod-fb-v1:';\nconst FORMAT_VERSION = 1;\n\ninterface ClipboardPayload {\n v: number;\n findings: FindingPayload[];\n}\n\n/**\n * Strip the screenshot_data_url field from a copy of the context_json.\n * Returns the original object if there's no screenshot (no copy needed).\n */\nfunction withoutScreenshot(payload: FindingPayload): FindingPayload {\n if (!payload.context_json?.screenshot_data_url) return payload;\n const { screenshot_data_url: _drop, ...rest } = payload.context_json;\n return { ...payload, context_json: rest };\n}\n\n/**\n * URL-safe base64 encoder that handles Unicode (UTF-8 → binary string → base64).\n * Browser-only: the desktop UI has its own mirror (findingsClipboard.js)\n * that uses the same TextEncoder/TextDecoder + atob/btoa pattern.\n */\nfunction encodeBase64(unicodeStr: string): string {\n const utf8 = new TextEncoder().encode(unicodeStr);\n // Convert bytes to a binary string for btoa()\n let binary = '';\n for (const b of utf8) binary += String.fromCharCode(b);\n const standard = btoa(binary);\n // URL-safe variants for clipboard resilience\n return standard.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\nfunction decodeBase64(urlSafe: string): string {\n const standard = urlSafe.replace(/-/g, '+').replace(/_/g, '/');\n const padded = standard + '='.repeat((4 - (standard.length % 4)) % 4);\n const binary = atob(padded);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);\n return new TextDecoder().decode(bytes);\n}\n\n/**\n * Encode an array of findings into the clipboard transfer format.\n * Screenshots are stripped from every finding's context_json.\n *\n * Empty arrays still encode (returns the prefix + empty-list base64)\n * so consumers can distinguish \"Synod clipboard blob with no items\" from\n * \"not a Synod clipboard blob at all\".\n */\nexport function encodeFindingsToClipboard(findings: FindingPayload[]): string {\n const stripped = findings.map(withoutScreenshot);\n const payload: ClipboardPayload = { v: FORMAT_VERSION, findings: stripped };\n const json = JSON.stringify(payload);\n return PREFIX + encodeBase64(json);\n}\n\n/**\n * Decode a clipboard string back into findings.\n *\n * Returns `{ ok: true, findings }` on success, or `{ ok: false, error }`\n * if the input is not a Synod findings blob, the JSON is malformed, the\n * version is unsupported, or any finding has an invalid shape.\n *\n * On any decode error the consumer should fall back to plain-text paste\n * behaviour (or just show an \"invalid clipboard\" toast).\n */\nexport function decodeFindingsFromClipboard(\n raw: string,\n): { ok: true; findings: FindingPayload[] } | { ok: false; error: string } {\n if (typeof raw !== 'string' || raw.length === 0) {\n return { ok: false, error: 'Empty clipboard' };\n }\n const trimmed = raw.trim();\n if (!trimmed.startsWith(PREFIX)) {\n return { ok: false, error: 'Not a Synod findings blob (missing prefix)' };\n }\n const b64 = trimmed.slice(PREFIX.length);\n\n let json: string;\n try {\n json = decodeBase64(b64);\n } catch {\n return { ok: false, error: 'Malformed base64' };\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(json);\n } catch {\n return { ok: false, error: 'Malformed JSON payload' };\n }\n\n if (!parsed || typeof parsed !== 'object') {\n return { ok: false, error: 'Payload is not an object' };\n }\n const obj = parsed as Partial<ClipboardPayload>;\n if (obj.v !== FORMAT_VERSION) {\n return { ok: false, error: `Unsupported format version ${obj.v}` };\n }\n if (!Array.isArray(obj.findings)) {\n return { ok: false, error: 'Payload.findings is not an array' };\n }\n\n const validSeverities: FindingSeverity[] = ['low', 'medium', 'high'];\n const findings: FindingPayload[] = [];\n for (let i = 0; i < obj.findings.length; i++) {\n const f = obj.findings[i] as Partial<FindingPayload>;\n if (typeof f?.description !== 'string' || f.description.trim() === '') {\n return { ok: false, error: `Finding[${i}] missing description` };\n }\n if (typeof f.severity !== 'string' || !validSeverities.includes(f.severity as FindingSeverity)) {\n return { ok: false, error: `Finding[${i}] has invalid severity \"${f.severity}\"` };\n }\n if (!f.context_json || typeof f.context_json !== 'object') {\n return { ok: false, error: `Finding[${i}] missing context_json` };\n }\n findings.push({\n title: typeof f.title === 'string' ? f.title : '',\n description: f.description,\n severity: f.severity as FindingSeverity,\n context_json: f.context_json,\n ...(f.suggested_action ? { suggested_action: f.suggested_action } : {}),\n });\n }\n\n return { ok: true, findings };\n}\n","// ── QueuePanel — Batch review drawer (list/edit/delete/send) ─────────────\n//\n// Shows pending findings in a sliding drawer.\n// Per-item: thumbnail, title, selector+severity, edit, delete.\n// Send batch → sendBatch() → partial success surfacing.\n// Toast notifications for results.\n\nimport type {\n FindingPayload,\n FindingSeverity,\n BatchResult,\n ClientError,\n} from '../client/types';\nimport { createSynodLogo } from '../utils/logo';\nimport { isTitlePending, truncDesc } from '../utils/title';\nimport { encodeFindingsToClipboard } from '../utils/clipboard';\n\nexport interface QueuePanelCallbacks {\n getQueueSnapshot: () => FindingPayload[];\n onSendBatch: () => Promise<BatchResult>;\n onClearAll: () => void;\n onRemoveItem: (index: number) => void;\n onEditItem: (index: number, title: string, description: string, severity: FindingSeverity) => void;\n /**\n * Optional: copy all queued findings to the clipboard in the Synod\n * findings transfer format. If omitted, the panel uses the default\n * navigator.clipboard.writeText implementation.\n */\n onCopyToClipboard?: (findings: FindingPayload[]) => Promise<boolean>;\n}\n\nexport class QueuePanel {\n private _wrapper: HTMLElement;\n private _callbacks: QueuePanelCallbacks;\n private _panelEl: HTMLDivElement | null = null;\n private _bodyEl: HTMLDivElement | null = null;\n private _bannerEl: HTMLDivElement | null = null;\n private _sendBtn: HTMLButtonElement | null = null;\n private _isOpen = false;\n private _toastContainer: HTMLDivElement | null = null;\n\n constructor(wrapper: HTMLElement, callbacks: QueuePanelCallbacks) {\n this._wrapper = wrapper;\n this._callbacks = callbacks;\n }\n\n get isOpen(): boolean {\n return this._isOpen;\n }\n\n /**\n * Open the queue panel drawer.\n */\n show(): void {\n if (this._isOpen) return;\n\n const panel = this.buildPanel();\n this._panelEl = panel;\n this._wrapper.appendChild(panel);\n this._isOpen = true;\n\n this.renderItems();\n }\n\n /**\n * Close the queue panel drawer.\n */\n hide(): void {\n this._panelEl?.remove();\n this._panelEl = null;\n this._bodyEl = null;\n this._bannerEl = null;\n this._sendBtn = null;\n this._isOpen = false;\n }\n\n /**\n * Refresh item list (call after queue changes).\n */\n refresh(): void {\n if (!this._isOpen) return;\n this.renderItems();\n }\n\n // ── Build DOM ───────────────────────────────────────────────────────\n\n private buildPanel(): HTMLDivElement {\n const panel = document.createElement('div');\n panel.className = 'synod-fb-queue';\n panel.setAttribute('role', 'dialog');\n panel.setAttribute('aria-modal', 'true');\n panel.setAttribute('aria-label', 'Pending findings review');\n\n // Header\n const header = document.createElement('div');\n header.className = 'synod-fb-queue-header';\n const headerLogo = document.createElement('div');\n headerLogo.className = 'synod-fb-queue-header-logo';\n headerLogo.appendChild(createSynodLogo(16));\n const title = document.createElement('div');\n title.className = 'synod-fb-queue-title';\n title.textContent = 'Pending Findings';\n const closeBtn = document.createElement('button');\n closeBtn.className = 'synod-fb-icon-btn';\n closeBtn.textContent = '×';\n closeBtn.setAttribute('aria-label', 'Close queue panel');\n closeBtn.addEventListener('click', () => this.hide());\n header.appendChild(headerLogo);\n header.appendChild(title);\n header.appendChild(closeBtn);\n\n // Banner (for errors/info)\n const banner = document.createElement('div');\n banner.className = 'synod-fb-queue-banner';\n banner.style.display = 'none';\n this._bannerEl = banner;\n\n // Body (item list)\n const body = document.createElement('div');\n body.className = 'synod-fb-queue-body';\n this._bodyEl = body;\n\n // Footer\n const footer = document.createElement('div');\n footer.className = 'synod-fb-queue-footer';\n\n const clearBtn = document.createElement('button');\n clearBtn.className = 'synod-fb-btn-danger';\n clearBtn.textContent = 'Clear all';\n clearBtn.addEventListener('click', () => this.handleClearAll());\n\n const copyBtn = document.createElement('button');\n copyBtn.className = 'synod-fb-icon-btn';\n copyBtn.setAttribute('aria-label', 'Copy all findings to clipboard');\n copyBtn.title = 'Copy to clipboard (offline transfer)';\n copyBtn.textContent = '⧉';\n copyBtn.addEventListener('click', () => this.handleCopyToClipboard());\n\n const sendBtn = document.createElement('button');\n sendBtn.className = 'synod-fb-btn-primary';\n sendBtn.textContent = 'Send batch';\n sendBtn.addEventListener('click', () => this.handleSendBatch());\n this._sendBtn = sendBtn;\n\n footer.appendChild(clearBtn);\n footer.appendChild(copyBtn);\n footer.appendChild(sendBtn);\n\n panel.appendChild(header);\n panel.appendChild(banner);\n panel.appendChild(body);\n panel.appendChild(footer);\n\n // Esc handler\n panel.addEventListener('keydown', (e) => {\n if (e.key === 'Escape') {\n e.preventDefault();\n this.hide();\n }\n });\n\n return panel;\n }\n\n private renderItems(): void {\n if (!this._bodyEl) return;\n\n const items = this._callbacks.getQueueSnapshot();\n\n // Clear body\n this._bodyEl.innerHTML = '';\n\n if (items.length === 0) {\n const empty = document.createElement('div');\n empty.className = 'synod-fb-queue-empty';\n empty.textContent = 'No findings queued yet. Click an element to start capturing.';\n this._bodyEl.appendChild(empty);\n\n if (this._sendBtn) {\n this._sendBtn.textContent = 'Send batch';\n this._sendBtn.disabled = true;\n }\n return;\n }\n\n if (this._sendBtn) {\n this._sendBtn.textContent = `Send batch (${items.length})`;\n this._sendBtn.disabled = false;\n }\n\n items.forEach((item, index) => {\n const row = this.buildItemRow(item, index);\n this._bodyEl!.appendChild(row);\n });\n }\n\n private buildItemRow(item: FindingPayload, index: number): HTMLDivElement {\n const row = document.createElement('div');\n row.className = 'synod-fb-queue-item';\n row.dataset.index = String(index);\n\n // Thumbnail\n const thumb = document.createElement('div');\n thumb.className = 'synod-fb-queue-item-thumb';\n if (item.context_json.screenshot_data_url) {\n const img = document.createElement('img');\n img.src = item.context_json.screenshot_data_url;\n img.alt = 'Screenshot';\n thumb.appendChild(img);\n }\n\n // Content\n const content = document.createElement('div');\n content.className = 'synod-fb-queue-item-content';\n\n const titleEl = document.createElement('div');\n titleEl.className = 'synod-fb-queue-item-title';\n if (isTitlePending(item.title)) {\n // Title is generated server-side on receive — until then show a\n // snippet of the description so the row is readable in the queue.\n titleEl.classList.add('synod-fb-pending');\n titleEl.textContent = truncDesc(item.description) || '(untitled)';\n } else {\n titleEl.textContent = item.title;\n }\n\n const metaEl = document.createElement('div');\n metaEl.className = 'synod-fb-queue-item-meta';\n const selectorSnippet = (item.context_json.css_selector || '').substring(0, 40);\n metaEl.textContent = `${selectorSnippet} · ${item.severity}`;\n\n content.appendChild(titleEl);\n content.appendChild(metaEl);\n\n // Actions\n const actions = document.createElement('div');\n actions.className = 'synod-fb-queue-item-actions';\n\n const editBtn = document.createElement('button');\n editBtn.className = 'synod-fb-icon-btn';\n editBtn.textContent = '✎';\n editBtn.setAttribute('aria-label', `Edit finding ${index + 1}`);\n editBtn.addEventListener('click', (e) => {\n e.stopPropagation();\n this.toggleEditRow(row, item, index);\n });\n\n const deleteBtn = document.createElement('button');\n deleteBtn.className = 'synod-fb-icon-btn';\n deleteBtn.textContent = '✕';\n deleteBtn.setAttribute('aria-label', `Delete finding ${index + 1}`);\n deleteBtn.addEventListener('click', (e) => {\n e.stopPropagation();\n this._callbacks.onRemoveItem(index);\n this.refresh();\n });\n\n actions.appendChild(editBtn);\n actions.appendChild(deleteBtn);\n\n row.appendChild(thumb);\n row.appendChild(content);\n row.appendChild(actions);\n\n return row;\n }\n\n private toggleEditRow(row: HTMLDivElement, item: FindingPayload, index: number): void {\n // Toggle: if already editing, collapse; otherwise expand\n const existingEdit = row.querySelector('.synod-fb-edit-form');\n if (existingEdit) {\n existingEdit.remove();\n return;\n }\n\n const editForm = document.createElement('div');\n editForm.className = 'synod-fb-edit-form';\n editForm.style.padding = '8px 0 4px';\n\n const titleInput = document.createElement('input');\n titleInput.className = 'synod-fb-input';\n titleInput.value = item.title;\n titleInput.style.marginBottom = '4px';\n\n const descInput = document.createElement('textarea');\n descInput.className = 'synod-fb-textarea';\n descInput.value = item.description;\n descInput.rows = 2;\n descInput.style.marginBottom = '4px';\n\n const sevGroup = document.createElement('div');\n sevGroup.className = 'synod-fb-severity-group';\n sevGroup.setAttribute('role', 'group');\n\n let editSeverity: FindingSeverity = item.severity;\n const severities: FindingSeverity[] = ['low', 'medium', 'high'];\n for (const sev of severities) {\n const btn = document.createElement('button');\n btn.className = 'synod-fb-sev-btn';\n btn.dataset.sev = sev;\n btn.textContent = sev.charAt(0).toUpperCase() + sev.slice(1);\n btn.setAttribute('aria-pressed', sev === editSeverity ? 'true' : 'false');\n if (sev === editSeverity) btn.classList.add('active');\n btn.addEventListener('click', (e) => {\n e.preventDefault();\n editSeverity = sev;\n sevGroup.querySelectorAll('.synod-fb-sev-btn').forEach((b) => {\n const bSev = (b as HTMLButtonElement).dataset.sev;\n b.setAttribute('aria-pressed', bSev === sev ? 'true' : 'false');\n b.classList.toggle('active', bSev === sev);\n });\n });\n sevGroup.appendChild(btn);\n }\n\n const saveBtn = document.createElement('button');\n saveBtn.className = 'synod-fb-btn-primary';\n saveBtn.textContent = 'Save';\n saveBtn.style.marginTop = '4px';\n saveBtn.addEventListener('click', (e) => {\n e.preventDefault();\n this._callbacks.onEditItem(index, titleInput.value.trim(), descInput.value.trim(), editSeverity);\n this.refresh();\n });\n\n editForm.appendChild(titleInput);\n editForm.appendChild(descInput);\n editForm.appendChild(sevGroup);\n editForm.appendChild(saveBtn);\n\n row.appendChild(editForm);\n titleInput.focus();\n }\n\n // ── Actions ─────────────────────────────────────────────────────────\n\n private async handleSendBatch(): Promise<void> {\n if (!this._sendBtn) return;\n\n const originalText = this._sendBtn.textContent;\n this._sendBtn.disabled = true;\n this._sendBtn.textContent = 'Sending…';\n this.hideBanner();\n\n // Dim items during send\n if (this._bodyEl) {\n this._bodyEl.style.opacity = '0.5';\n this._bodyEl.style.pointerEvents = 'none';\n }\n\n try {\n const result = await this._callbacks.onSendBatch();\n this.handleSendResult(result);\n } catch (err) {\n this.handleSendError(err as ClientError | Error);\n } finally {\n if (this._bodyEl) {\n this._bodyEl.style.opacity = '';\n this._bodyEl.style.pointerEvents = '';\n }\n if (this._sendBtn) {\n this._sendBtn.disabled = false;\n this._sendBtn.textContent = originalText || 'Send batch';\n }\n }\n }\n\n private handleSendResult(result: BatchResult): void {\n const total = result.created.length + result.errors.length;\n\n if (result.errors.length === 0 && result.created.length > 0) {\n // Full success\n this.showToast('success', `✓ ${result.created.length} ${result.created.length === 1 ? 'finding' : 'findings'} sent to Synod`);\n // Close drawer after delay\n setTimeout(() => this.hide(), 1000);\n } else if (result.created.length > 0 && result.errors.length > 0) {\n // Partial success\n this.showBanner('info', `${result.created.length} of ${total} findings sent. ${result.errors.length} failed.`);\n this.showToast('info', `${result.created.length} of ${total} findings sent`);\n } else if (result.errors.length > 0 && result.created.length === 0) {\n // All errors but not an exception\n const errMsg = result.errors.map(e => e.error).join('; ');\n this.showBanner('error', `⚠ Send failed: ${errMsg}. Your findings are saved — try again.`);\n }\n\n this.refresh();\n }\n\n private handleSendError(err: ClientError | Error): void {\n const isClientErr = 'code' in err;\n const code = isClientErr ? (err as ClientError).code : null;\n\n if (code === 'AUTH_ERROR') {\n this.showBanner(\n 'error',\n '⚠ Token invalid or expired. Regenerate the token in Synod Desktop Settings → Extensions.',\n );\n } else {\n const msg = err.message || 'Unknown error';\n this.showBanner('error', `⚠ Send failed: ${msg}. Your findings are saved — try again.`);\n }\n }\n\n private async handleCopyToClipboard(): Promise<void> {\n const items = this._callbacks.getQueueSnapshot();\n if (items.length === 0) {\n this.showToast('info', 'No findings to copy');\n return;\n }\n\n const encoded = encodeFindingsToClipboard(items);\n\n try {\n let ok = false;\n if (this._callbacks.onCopyToClipboard) {\n ok = await this._callbacks.onCopyToClipboard(items);\n } else if (typeof navigator !== 'undefined' && navigator.clipboard) {\n await navigator.clipboard.writeText(encoded);\n ok = true;\n }\n\n if (ok) {\n this.showToast(\n 'success',\n `✓ Copied ${items.length} ${items.length === 1 ? 'finding' : 'findings'} (screenshots stripped)`,\n );\n } else {\n this.showToast('error', '⚠ Clipboard unavailable in this browser');\n }\n } catch {\n this.showToast('error', '⚠ Copy failed — try Send batch instead');\n }\n }\n\n private handleClearAll(): void {\n const items = this._callbacks.getQueueSnapshot();\n if (items.length > 3) {\n // Confirmation for large batches\n if (!window.confirm(`Clear all ${items.length} findings?`)) return;\n }\n this._callbacks.onClearAll();\n this.refresh();\n }\n\n // ── Banner ──────────────────────────────────────────────────────────\n\n private showBanner(type: 'error' | 'info', message: string): void {\n if (!this._bannerEl) return;\n this._bannerEl.className = `synod-fb-queue-banner ${type}`;\n this._bannerEl.textContent = message;\n this._bannerEl.style.display = 'block';\n }\n\n private hideBanner(): void {\n if (!this._bannerEl) return;\n this._bannerEl.style.display = 'none';\n }\n\n // ── Toast ───────────────────────────────────────────────────────────\n\n private showToast(variant: 'success' | 'error' | 'info', message: string): void {\n // Ensure toast container exists\n if (!this._toastContainer) {\n this._toastContainer = document.createElement('div');\n this._toastContainer.className = 'synod-fb-toast-container';\n this._wrapper.appendChild(this._toastContainer);\n }\n\n const toast = document.createElement('div');\n toast.className = `synod-fb-toast ${variant}`;\n toast.setAttribute('role', variant === 'error' ? 'alert' : 'status');\n toast.setAttribute('aria-live', variant === 'error' ? 'assertive' : 'polite');\n const toastLogo = document.createElement('span');\n toastLogo.className = 'synod-fb-toast-logo';\n toastLogo.appendChild(createSynodLogo(14));\n const toastMsg = document.createElement('span');\n toastMsg.textContent = message;\n toast.appendChild(toastLogo);\n toast.appendChild(toastMsg);\n\n this._toastContainer.appendChild(toast);\n\n // Limit to 3 visible\n while (this._toastContainer.children.length > 3) {\n this._toastContainer.removeChild(this._toastContainer.firstChild!);\n }\n\n // Auto-dismiss\n const duration = variant === 'error' ? 6000 : variant === 'success' ? 4000 : 3000;\n setTimeout(() => {\n toast.remove();\n }, duration);\n }\n\n // ── Cleanup ─────────────────────────────────────────────────────────\n\n destroy(): void {\n this.hide();\n this._toastContainer?.remove();\n this._toastContainer = null;\n }\n}\n","// ── ChargeIndicator — cursor-tracking radial progress + Synod mark ───────\n//\n// Shown while the user holds the activation key combo. Renders a small\n// Synod logo at the cursor with a circular stroke-dasharray progress ring\n// around it. The ring fills clockwise from 12 o'clock as the hold progresses.\n// When the hold completes (fraction = 1), the .charged class is added so\n// the ring color shifts from #2DAFAF to #36D6D6 (matching the brightest\n// triangle in the Synod mark).\n//\n// Lives inside the closed shadow root of FeedbackOverlay. pointer-events:none\n// so it never blocks the host page's own pointer events.\n\nimport { createSynodLogo } from '../utils/logo';\n\nconst RING_RADIUS = 18; // px — matches the SVG circle below\nconst RING_CIRCUMFERENCE = 2 * Math.PI * RING_RADIUS; // ~113.1\n\nexport class ChargeIndicator {\n private _root: HTMLElement | null = null;\n private _circle: SVGCircleElement | null = null;\n private _mounted = false;\n\n /**\n * Build the indicator's DOM once and append it to the given wrapper.\n * The indicator starts hidden; call showAt() to make it visible.\n */\n mount(wrapper: HTMLElement): void {\n if (this._mounted) return;\n\n const root = document.createElement('div');\n root.className = 'synod-fb-charge';\n root.setAttribute('aria-hidden', 'true');\n root.style.display = 'none';\n\n // SVG ring — rotated -90deg via CSS so stroke starts at the top.\n const svgNS = 'http://www.w3.org/2000/svg';\n const svg = document.createElementNS(svgNS, 'svg');\n svg.setAttribute('class', 'synod-fb-charge-ring');\n svg.setAttribute('viewBox', '0 0 40 40');\n svg.setAttribute('width', '40');\n svg.setAttribute('height', '40');\n\n const bgCircle = document.createElementNS(svgNS, 'circle');\n bgCircle.setAttribute('cx', '20');\n bgCircle.setAttribute('cy', '20');\n bgCircle.setAttribute('r', String(RING_RADIUS));\n bgCircle.setAttribute('class', 'synod-fb-charge-ring-bg');\n\n const fgCircle = document.createElementNS(svgNS, 'circle');\n fgCircle.setAttribute('cx', '20');\n fgCircle.setAttribute('cy', '20');\n fgCircle.setAttribute('r', String(RING_RADIUS));\n fgCircle.setAttribute('class', 'synod-fb-charge-ring-fg');\n fgCircle.setAttribute('stroke-dasharray', String(RING_CIRCUMFERENCE));\n // Start fully empty (dashoffset = full circumference).\n fgCircle.setAttribute('stroke-dashoffset', String(RING_CIRCUMFERENCE));\n\n svg.appendChild(bgCircle);\n svg.appendChild(fgCircle);\n root.appendChild(svg);\n\n // Center logo mark\n root.appendChild(createSynodLogo(22));\n\n wrapper.appendChild(root);\n\n this._root = root;\n this._circle = fgCircle;\n this._mounted = true;\n }\n\n /** Whether mount() has been called and the indicator is in the DOM. */\n get isMounted(): boolean {\n return this._mounted && this._root !== null && this._root.isConnected;\n }\n\n /** Display the indicator at viewport-relative coordinates. */\n showAt(x: number, y: number): void {\n if (!this._root) return;\n this._root.style.left = `${x}px`;\n this._root.style.top = `${y}px`;\n this._root.style.display = 'flex';\n }\n\n /**\n * Update the ring's progress.\n * @param fraction 0..1 — clamped. At 1 the .charged class is added.\n */\n setProgress(fraction: number): void {\n if (!this._root || !this._circle) return;\n const clamped = Math.max(0, Math.min(1, fraction));\n const offset = RING_CIRCUMFERENCE * (1 - clamped);\n this._circle.setAttribute('stroke-dashoffset', String(offset));\n if (clamped >= 1) {\n this._root.classList.add('charged');\n } else {\n this._root.classList.remove('charged');\n }\n }\n\n /** Hide the indicator. Safe to call when already hidden. */\n hide(): void {\n if (!this._root) return;\n this._root.style.display = 'none';\n this._root.classList.remove('charged');\n // Reset progress so the next show starts empty.\n if (this._circle) {\n this._circle.setAttribute(\n 'stroke-dashoffset',\n String(RING_CIRCUMFERENCE),\n );\n }\n }\n\n /** Remove the indicator from the DOM and release references. */\n destroy(): void {\n if (this._root && this._root.parentElement) {\n this._root.parentElement.removeChild(this._root);\n }\n this._root = null;\n this._circle = null;\n this._mounted = false;\n }\n}\n","// ── LogoContextMenu — right-click menu on the FloatingLogo ────────────────\n//\n// Small fixed-position menu shown when the user right-clicks the FloatingLogo.\n// Currently exposes one action: \"Hide Synod overlay\" which unmounts the logo\n// (and any visible picker / form / queue) but leaves the hotkey listener\n// armed so the user can re-activate by charging the combo again.\n//\n// Dismiss behaviors:\n// - Click outside the menu\n// - Escape key\n// - Scroll / resize on the host page\n// - Any action button click (after firing the callback)\n\nexport interface LogoContextMenuCallbacks {\n onHide: () => void;\n}\n\nexport class LogoContextMenu {\n private _root: HTMLDivElement | null = null;\n private _callbacks: LogoContextMenuCallbacks;\n private _outsideClickListener: ((e: MouseEvent) => void) | null = null;\n private _escapeListener: ((e: KeyboardEvent) => void) | null = null;\n private _dismissListener: (() => void) | null = null;\n\n constructor(callbacks: LogoContextMenuCallbacks) {\n this._callbacks = callbacks;\n }\n\n get isOpen(): boolean {\n return this._root !== null && this._root.isConnected;\n }\n\n /**\n * Show the menu at the given viewport coordinates.\n * If already open, the existing menu is closed first.\n */\n showAt(wrapper: HTMLElement, x: number, y: number): void {\n this.hide();\n\n const root = document.createElement('div');\n root.className = 'synod-fb-context-menu';\n root.setAttribute('role', 'menu');\n root.setAttribute('aria-label', 'Synod overlay options');\n\n // Clamp to viewport so the menu never overflows the right/bottom edge.\n const menuWidth = 180;\n const menuHeight = 40;\n const clampedX = Math.min(x, window.innerWidth - menuWidth - 8);\n const clampedY = Math.min(y, window.innerHeight - menuHeight - 8);\n root.style.left = `${Math.max(8, clampedX)}px`;\n root.style.top = `${Math.max(8, clampedY)}px`;\n\n const hideItem = document.createElement('button');\n hideItem.type = 'button';\n hideItem.className = 'synod-fb-context-menu-item';\n hideItem.setAttribute('role', 'menuitem');\n hideItem.textContent = 'Hide Synod overlay';\n hideItem.addEventListener('click', () => {\n this.hide();\n this._callbacks.onHide();\n });\n\n root.appendChild(hideItem);\n wrapper.appendChild(root);\n this._root = root;\n\n // Dismiss on outside click. The listener is attached to document so it\n // catches clicks anywhere on the host page (the overlay host has\n // pointer-events:none except for interactive children).\n this._outsideClickListener = (e: MouseEvent) => {\n if (!this._root) return;\n // Don't dismiss if the click landed inside the menu itself.\n if (e.target instanceof Node && this._root.contains(e.target)) return;\n this.hide();\n };\n // Defer attaching so the same contextmenu event that opened us doesn't\n // immediately close us.\n setTimeout(() => {\n if (!this._root) return;\n document.addEventListener('click', this._outsideClickListener!, true);\n document.addEventListener('contextmenu', this._outsideClickListener!, true);\n }, 0);\n\n this._escapeListener = (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n e.stopPropagation();\n this.hide();\n }\n };\n document.addEventListener('keydown', this._escapeListener, true);\n\n // Dismiss on scroll/resize — the menu is positioned in viewport coords\n // and would otherwise drift away from the cursor anchor.\n this._dismissListener = () => this.hide();\n window.addEventListener('scroll', this._dismissListener, true);\n window.addEventListener('resize', this._dismissListener, true);\n }\n\n hide(): void {\n if (this._outsideClickListener) {\n document.removeEventListener('click', this._outsideClickListener, true);\n document.removeEventListener('contextmenu', this._outsideClickListener, true);\n this._outsideClickListener = null;\n }\n if (this._escapeListener) {\n document.removeEventListener('keydown', this._escapeListener, true);\n this._escapeListener = null;\n }\n if (this._dismissListener) {\n window.removeEventListener('scroll', this._dismissListener, true);\n window.removeEventListener('resize', this._dismissListener, true);\n this._dismissListener = null;\n }\n this._root?.remove();\n this._root = null;\n }\n\n destroy(): void {\n this.hide();\n }\n}\n","// ── setup.ts — Integration wiring for the interaction layer ──────────────\n//\n// Registers onEnable/onDisable hooks with the FeedbackManager.\n// On enable: mounts FeedbackOverlay, creates picker, logo, form, queue.\n// On disable: unmounts everything, zero DOM pollution.\n//\n// This is the single integration point where SDG-294 touches SDG-298's API.\n// NOTE: We import FeedbackManager and use a passed-in instance to avoid\n// circular dependencies with index.ts.\n\nimport type { FeedbackManager } from '../lifecycle/FeedbackManager';\nimport type { FindingPayload, FindingSeverity } from '../client/types';\nimport { FeedbackOverlay } from '../ui/FeedbackOverlay';\nimport { Highlighter } from '../interaction/Highlighter';\nimport { ElementPicker } from '../interaction/ElementPicker';\nimport { KeyComboDetector } from './KeyComboDetector';\nimport { FloatingLogo } from '../ui/FloatingLogo';\nimport { IssueForm } from '../ui/IssueForm';\nimport { QueuePanel } from '../ui/QueuePanel';\nimport { ChargeIndicator } from '../ui/ChargeIndicator';\nimport { LogoContextMenu } from '../ui/LogoContextMenu';\nimport { extract } from './SelectionExtractor';\nimport { truncDesc } from '../utils/title';\nimport type { OverlayState, SelectionContext } from './types';\n\n// Internal state\nlet manager: FeedbackManager | null = null;\nlet overlay: FeedbackOverlay | null = null;\nlet highlighter: Highlighter | null = null;\nlet picker: ElementPicker | null = null;\nlet comboDetector: KeyComboDetector | null = null;\nlet logo: FloatingLogo | null = null;\nlet issueForm: IssueForm | null = null;\nlet queuePanel: QueuePanel | null = null;\nlet chargeIndicator: ChargeIndicator | null = null;\nlet contextMenu: LogoContextMenu | null = null;\nlet state: OverlayState = 'inactive';\nlet unregisterEnable: (() => void) | null = null;\nlet unregisterDisable: (() => void) | null = null;\nlet lastSelectionContext: SelectionContext | null = null;\nlet lastMouseX = 0;\nlet lastMouseY = 0;\nlet mouseMoveListener: ((e: MouseEvent) => void) | null = null;\n\n/**\n * Reset the interaction layer state.\n * Called internally by __resetForTesting to allow fresh registration.\n * @internal\n */\nexport function __resetInteractionLayer(): void {\n if (unregisterEnable) unregisterEnable();\n if (unregisterDisable) unregisterDisable();\n unregisterEnable = null;\n unregisterDisable = null;\n manager = null;\n overlay = null;\n highlighter = null;\n picker = null;\n comboDetector = null;\n logo = null;\n issueForm = null;\n queuePanel = null;\n chargeIndicator = null;\n contextMenu = null;\n state = 'inactive';\n lastSelectionContext = null;\n lastMouseX = 0;\n lastMouseY = 0;\n mouseMoveListener = null;\n}\n\n/**\n * Build a FindingPayload from form data and selection context.\n *\n * The title is left empty (`PENDING_TITLE`). Title generation happens\n * server-side — the gateway detects empty titles on receive and either\n * asks the AI helper or applies a truncated-description fallback. This\n * keeps the browser SDK free of AI coupling and works for the offline\n * clipboard-transfer path too.\n *\n * The `source` field is intentionally omitted — the gateway hardcodes it.\n */\nexport function buildFinding(\n description: string,\n severity: FindingSeverity,\n context: SelectionContext,\n screenshotDataUrl: string | null,\n): FindingPayload {\n return {\n title: '',\n description,\n severity,\n // Gateway handler (crates/core/src/handlers/external_findings.rs:42)\n // requires suggested_action as a non-optional String and validates it\n // against [\"create_story\", \"create_research\", \"create_hotfix\"]. Omitting\n // it makes the whole batch return 422.\n suggested_action: 'create_story',\n context_json: {\n element_text: context.elementText,\n element_html: context.elementHtml,\n element_attributes: context.elementAttributes,\n parent_chain: context.parentChain,\n url: typeof window !== 'undefined' ? window.location.href : undefined,\n // Best-effort route — pathname + hash + search captures the SPA \"screen\"\n // without re-sending the origin (already in url).\n route: typeof window !== 'undefined'\n ? window.location.pathname + window.location.search + window.location.hash\n : undefined,\n page_title: typeof document !== 'undefined' ? document.title : undefined,\n viewport: typeof window !== 'undefined'\n ? { width: window.innerWidth, height: window.innerHeight }\n : undefined,\n user_agent: typeof navigator !== 'undefined' ? navigator.userAgent : undefined,\n ...(screenshotDataUrl ? { screenshot_data_url: screenshotDataUrl } : {}),\n },\n };\n}\n\n/**\n * Register the interaction layer with a FeedbackManager instance.\n * Call this once at package initialization.\n */\nexport function registerInteractionLayer(fm: FeedbackManager): void {\n if (unregisterEnable) return; // Already registered\n\n manager = fm;\n\n unregisterEnable = manager.onEnable(() => {\n if (state !== 'inactive') return;\n\n overlay = new FeedbackOverlay();\n overlay.mount(document.body);\n\n const wrapper = overlay.getContentWrapper()!;\n\n highlighter = new Highlighter(overlay);\n\n state = 'idle';\n\n // Charge indicator (cursor-tracking radial progress during key hold)\n chargeIndicator = new ChargeIndicator();\n chargeIndicator.mount(wrapper);\n\n // Right-click context menu (Hide action). Created lazily; only shown\n // when the user right-clicks the FloatingLogo.\n contextMenu = new LogoContextMenu({\n onHide: () => {\n hideVisibleUI();\n },\n });\n\n // Track latest cursor position so the charge indicator can anchor to it\n // when the combo goes down. Registered on document so we get positions\n // even when hovering over the (pointer-events:none) overlay host.\n mouseMoveListener = (e: MouseEvent) => {\n lastMouseX = e.clientX;\n lastMouseY = e.clientY;\n };\n document.addEventListener('mousemove', mouseMoveListener);\n\n // Key-combo detector — drives the charge → activate → release cycle.\n comboDetector = new KeyComboDetector(manager.config?.keyCombo, {\n onChargeStart: () => {\n if (state !== 'idle') return;\n state = 'charging';\n chargeIndicator?.showAt(lastMouseX, lastMouseY);\n },\n onChargeProgress: (frac) => {\n if (state !== 'charging') return;\n chargeIndicator?.setProgress(frac);\n // Re-anchor in case the cursor moved during the hold.\n chargeIndicator?.showAt(lastMouseX, lastMouseY);\n },\n onCharged: () => {\n if (state !== 'charging') return;\n chargeIndicator?.hide();\n // Lazy-mount the visible UI (logo + form + queue) the first time\n // the user successfully charges the combo. After this the logo\n // stays mounted until the user explicitly hides it via the\n // right-click menu. Subsequent charges just activate the picker.\n ensureVisibleUI();\n activateSelection();\n },\n onChargeCancel: () => {\n if (state === 'charging') {\n chargeIndicator?.hide();\n state = 'idle';\n }\n // 'selecting' is intentionally untouched: once the charge completes,\n // the picker stays active until the user clicks an element or presses\n // Escape — releasing the keys mid-pick should NOT dismiss.\n // 'capturing' (form open) is also untouched: ESC or the form's own\n // buttons close it; releasing keys mid-form is normal.\n },\n });\n comboDetector.start();\n });\n\n unregisterDisable = manager.onDisable(() => {\n picker?.deactivate();\n comboDetector?.stop();\n queuePanel?.destroy();\n issueForm?.hide();\n highlighter?.destroy();\n logo?.unmount();\n chargeIndicator?.destroy();\n contextMenu?.destroy();\n if (mouseMoveListener) {\n document.removeEventListener('mousemove', mouseMoveListener);\n }\n overlay?.unmount();\n\n overlay = null;\n highlighter = null;\n picker = null;\n comboDetector = null;\n logo = null;\n issueForm = null;\n queuePanel = null;\n chargeIndicator = null;\n contextMenu = null;\n mouseMoveListener = null;\n state = 'inactive';\n lastSelectionContext = null;\n lastMouseX = 0;\n lastMouseY = 0;\n });\n}\n\n/**\n * Lazy-mount the visible UI surface: FloatingLogo + IssueForm + QueuePanel.\n *\n * Called from the KeyComboDetector's onCharged callback. After the first\n * call, the logo (and form/queue scaffolding) stays mounted across picker\n * sessions. The user can unmount them again via the right-click → Hide\n * action on the logo (see {@link hideVisibleUI}).\n *\n * This split exists so the host page sees no SDK chrome at all until the\n * user has actually invoked the hotkey — auto-enable on init() does not\n * paint anything.\n */\nfunction ensureVisibleUI(): void {\n if (!overlay || !manager) return;\n const wrapper = overlay.getContentWrapper();\n if (!wrapper) return;\n\n if (!logo) {\n logo = new FloatingLogo(overlay.root, {\n onClick: () => {\n if (state === 'idle' || state === 'selecting') {\n toggleQueuePanel();\n }\n },\n onContextMenu: (x, y) => {\n showLogoContextMenu(x, y);\n },\n });\n logo.mount(wrapper);\n }\n\n if (!issueForm) {\n issueForm = new IssueForm(wrapper, {\n onAddToTray: (result) => {\n const ctx = lastSelectionContext;\n if (!ctx || !manager) return;\n const finding = buildFinding(\n result.description,\n result.severity,\n ctx,\n result.contextJson.screenshot_data_url ?? null,\n );\n manager.queue(finding);\n updateBadgeCount();\n issueForm?.hide();\n deactivateSelection();\n },\n onSendNow: async (result) => {\n const ctx = lastSelectionContext;\n if (!ctx || !manager) return;\n const finding = buildFinding(\n result.description,\n result.severity,\n ctx,\n result.contextJson.screenshot_data_url ?? null,\n );\n manager.queue(finding);\n issueForm?.hide();\n try {\n await manager.sendBatch();\n updateBadgeCount();\n deactivateSelection();\n } catch {\n updateBadgeCount();\n toggleQueuePanel();\n }\n },\n onCancel: () => {\n issueForm?.hide();\n deactivateSelection();\n },\n });\n }\n\n if (!queuePanel) {\n queuePanel = new QueuePanel(wrapper, {\n getQueueSnapshot: () => manager?.getQueueSnapshot() ?? [],\n onSendBatch: () => manager!.sendBatch(),\n onClearAll: () => {\n manager?.clearQueue();\n updateBadgeCount();\n },\n onRemoveItem: (index) => {\n if (!manager) return;\n const snapshot = manager.getQueueSnapshot();\n const remaining = snapshot.filter((_, i) => i !== index);\n manager.replaceQueue(remaining);\n updateBadgeCount();\n },\n onEditItem: (index, title, description, severity) => {\n if (!manager) return;\n const snapshot = manager.getQueueSnapshot();\n if (index >= 0 && index < snapshot.length) {\n snapshot[index] = { ...snapshot[index], title, description, severity };\n manager.replaceQueue(snapshot);\n }\n },\n });\n }\n\n // Sync the badge with the current queue in case findings were queued\n // while the logo was hidden.\n updateBadgeCount();\n}\n\n/**\n * Tear down the visible UI surface (logo + form + queue + active picker).\n *\n * The hotkey listener and overlay host stay alive so the user can\n * re-activate by charging the combo again. This is the action behind the\n * right-click → \"Hide Synod overlay\" menu item.\n */\nfunction hideVisibleUI(): void {\n if (state === 'charging') {\n chargeIndicator?.hide();\n }\n picker?.deactivate();\n picker = null;\n highlighter?.clear();\n issueForm?.hide();\n queuePanel?.destroy();\n logo?.unmount();\n\n queuePanel = null;\n logo = null;\n // Note: issueForm is left mounted (it's hidden) — keeping it avoids\n // re-wiring form handlers on every hide/show cycle. The next\n // ensureVisibleUI() call will only re-create the logo + queue.\n state = 'idle';\n}\n\n/**\n * Show the right-click context menu at the given viewport coordinates.\n */\nfunction showLogoContextMenu(x: number, y: number): void {\n if (!overlay || !contextMenu) return;\n const wrapper = overlay.getContentWrapper();\n if (!wrapper) return;\n contextMenu.showAt(wrapper, x, y);\n}\n\n// ── Internal state machine ──────────────────────────────────────────────\n\nfunction activateSelection(): void {\n if ((state !== 'idle' && state !== 'charging') || !overlay || !highlighter) return;\n\n const hostEl = overlay.getHostElement();\n if (!hostEl) return;\n\n if (picker) {\n picker.deactivate();\n }\n\n picker = new ElementPicker({\n overlayHostElement: hostEl,\n highlighter,\n onElementClick: (element, context) => {\n if (state !== 'selecting') return;\n lastSelectionContext = context;\n state = 'capturing';\n picker?.deactivate();\n issueForm?.show(context, element);\n },\n onEscape: () => {\n if (state === 'selecting') {\n deactivateSelection();\n } else if (state === 'capturing') {\n issueForm?.hide();\n deactivateSelection();\n }\n },\n });\n\n picker.activate();\n state = 'selecting';\n}\n\nfunction deactivateSelection(): void {\n picker?.deactivate();\n highlighter?.clear();\n state = 'idle';\n}\n\nfunction toggleQueuePanel(): void {\n if (!queuePanel) return;\n if (queuePanel.isOpen) {\n queuePanel.hide();\n } else {\n queuePanel.show();\n queuePanel.refresh();\n }\n}\n\nfunction updateBadgeCount(): void {\n logo?.updateCount(manager?.getQueueSnapshot().length ?? 0);\n}\n\n// Re-export extract for testing convenience\nexport { extract };\n"],"mappings":"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AA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA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","names":["overlay","logo","manager"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/client/types.ts","../src/utils/retry.ts","../src/utils/config.ts","../src/client/SynodFeedbackClient.ts","../src/lifecycle/FeedbackManager.ts","../src/utils/overlay-css.ts","../src/ui/FeedbackOverlay.ts","../src/utils/logo.ts","../src/interaction/Highlighter.ts","../src/interaction/SelectionExtractor.ts","../src/interaction/ElementPicker.ts","../src/interaction/KeyComboDetector.ts","../src/ui/FloatingLogo.ts","../src/utils/viewport.ts","../src/utils/screenshot.ts","../src/ui/IssueForm.ts","../src/utils/title.ts","../src/utils/clipboard.ts","../src/ui/QueuePanel.ts","../src/ui/ChargeIndicator.ts","../src/ui/LogoContextMenu.ts","../src/interaction/setup.ts"],"sourcesContent":["// ── @synod-ai/extension-feedback — Public API ────────────────────────────\n//\n// Framework-agnostic entry point. Zero React imports.\n// React wrapper available at '@synod-ai/extension-feedback/react'.\n\nimport { FeedbackManager } from './lifecycle/FeedbackManager';\nimport { registerInteractionLayer, __resetInteractionLayer } from './interaction/setup';\n\n// ── Module-level singleton ─────────────────────────────────────────────\n\nlet manager = new FeedbackManager();\n\n// ── Register interaction layer (SDG-294) ────────────────────────────────\n\nregisterInteractionLayer(manager);\n\n// ── Functional facade ──────────────────────────────────────────────────\n\nexport function init(config: import('./client/types').FeedbackConfig): void {\n manager.init(config);\n // Auto-enable: the SDK activates fully on init(). The user does not need\n // to call enable() separately. The visible UI (FloatingLogo + picker) is\n // still lazy-mounted on the first successful hotkey charge — see\n // ensureVisibleUI() in interaction/setup.ts — so the host page sees no\n // SDK chrome at all until the user actually invokes the hotkey.\n // enable() is idempotent so a separate explicit call is a no-op.\n manager.enable();\n}\n\nexport function enable(): void {\n manager.enable();\n}\n\nexport function disable(): void {\n manager.disable();\n}\n\nexport function onEnable(handler: () => void): () => void {\n return manager.onEnable(handler);\n}\n\nexport function onDisable(handler: () => void): () => void {\n return manager.onDisable(handler);\n}\n\nexport async function sendBatch(): Promise<import('./client/types').BatchResult> {\n return manager.sendBatch();\n}\n\nexport function queue(finding: import('./client/types').FindingPayload): void {\n manager.queue(finding);\n}\n\nexport function clearQueue(): void {\n manager.clearQueue();\n}\n\nexport function replaceQueue(items: import('./client/types').FindingPayload[]): void {\n manager.replaceQueue(items);\n}\n\nexport function isEnabled(): boolean {\n return manager.enabled;\n}\n\nexport function getQueueSnapshot(): import('./client/types').FindingPayload[] {\n return manager.getQueueSnapshot();\n}\n\nexport function getQueueLength(): number {\n return manager.queueLength;\n}\n\n// ── Direct access (advanced / testing) ──────────────────────────────────\n\nexport { SynodFeedbackClient } from './client/SynodFeedbackClient';\nexport { FeedbackManager } from './lifecycle/FeedbackManager';\n\n// ── Clipboard transfer (offline finding import/export) ──────────────────\n//\n// Lets a user copy findings from the extension queue on a machine that\n// can't reach the gateway, then paste them into the Findings panel on a\n// machine that can. Screenshots are stripped on encode.\n\nexport {\n encodeFindingsToClipboard,\n decodeFindingsFromClipboard,\n} from './utils/clipboard';\n\n// ── Type exports ────────────────────────────────────────────────────────\n\nexport type {\n FeedbackConfig,\n FindingPayload,\n FindingSeverity,\n FindingContextJson,\n BatchResult,\n BatchResponse,\n CreatedFinding,\n FindingError,\n ExternalFindingsRequest,\n ClientError,\n ClientErrorCode,\n} from './client/types';\n\n// ── Test-only: reset singleton state ────────────────────────────────────\n\n/**\n * @internal Reset singleton state. Intended for test isolation only.\n * Creates a fresh manager and re-registers interaction layer hooks.\n */\nexport function __resetForTesting(): void {\n __resetInteractionLayer();\n manager.destroy();\n manager = new FeedbackManager();\n registerInteractionLayer(manager);\n}\n","// ── Types: Gateway Contract (matches SDG-295) ──────────────────────────\n\nexport type FindingSeverity = 'low' | 'medium' | 'high';\n\nexport interface FindingContextJson {\n url?: string;\n /** SPA route or pathname (best-effort). Helps the agent know which \"screen\" the user was on. */\n route?: string;\n /** Document title — gives the agent a human-readable label for the page. */\n page_title?: string;\n element_text?: string;\n /** outerHTML of the selected element, truncated at ~1500 chars. */\n element_html?: string;\n /** Filtered attribute map of the selected element. */\n element_attributes?: Record<string, string>;\n /**\n * Root-first parent chain, filtered to ancestors with an id or class.\n * Generic wrappers are skipped on the client. Each entry is\n * {tag, id?, className?}.\n */\n parent_chain?: Array<{ tag: string; id?: string; className?: string }>;\n viewport?: { width: number; height: number };\n user_agent?: string;\n screenshot_data_url?: string;\n batch_id?: string;\n extension_version?: string;\n}\n\nexport interface FindingPayload {\n title: string;\n description: string;\n severity: FindingSeverity;\n suggested_action?: 'create_story';\n context_json: FindingContextJson;\n}\n\n// POST /v2/findings/external body\nexport interface ExternalFindingsRequest {\n project_id: string;\n findings: FindingPayload[];\n}\n\n// ── Response types (gateway → client) ──────────────────────────────────\n\nexport interface CreatedFinding {\n id: string;\n title: string;\n severity: FindingSeverity;\n}\n\nexport interface FindingError {\n index: number;\n error: string;\n}\n\nexport interface BatchResponse {\n created: CreatedFinding[];\n errors: FindingError[];\n}\n\n// ── Client-side result wrapper ─────────────────────────────────────────\n\nexport interface BatchResult {\n created: CreatedFinding[];\n errors: FindingError[];\n}\n\n// ── Error types ────────────────────────────────────────────────────────\n\nexport type ClientErrorCode =\n | 'AUTH_ERROR'\n | 'RATE_LIMITED'\n | 'NETWORK_ERROR'\n | 'TIMEOUT'\n | 'SERVER_ERROR'\n | 'CLIENT_ERROR'\n | 'CONTEXT_TOO_LARGE';\n\nexport interface ClientError extends Error {\n code: ClientErrorCode;\n statusCode?: number;\n retryable: boolean;\n}\n\nexport function createClientError(\n code: ClientErrorCode,\n message: string,\n statusCode?: number,\n retryable = false,\n): ClientError {\n const err = new Error(message) as ClientError;\n err.name = 'ClientError';\n err.code = code;\n err.statusCode = statusCode;\n err.retryable = retryable;\n return err;\n}\n\n// ── Configuration types ────────────────────────────────────────────────\n\nexport interface FeedbackConfig {\n gatewayUrl: string;\n projectId: string;\n token: string;\n keyCombo?: string;\n timeout?: number;\n maxRetries?: number;\n}\n","// ── Retry Policy: Pure functions, unit-testable without fetch ──────────\n\n/**\n * Default exponential backoff schedule in ms.\n * Applied before attempts 2, 3, and 4 respectively (initial attempt has no delay).\n *\n * Fixed schedule (not jittered) — documented tradeoff (architecture §10):\n * Single-user extension, low request volume. Simpler and deterministic.\n */\nexport const BACKOFF_DELAYS_MS = [1000, 2000, 4000];\n\n/**\n * Get the backoff delay before the given retry attempt.\n *\n * @param attempt - Zero-indexed retry attempt (0 = first retry, 1 = second retry, etc.)\n * @returns Delay in ms, or undefined if attempt exceeds max retries\n */\nexport function getBackoffDelay(attempt: number): number | undefined {\n if (attempt < 0 || attempt >= BACKOFF_DELAYS_MS.length) {\n return undefined;\n }\n return BACKOFF_DELAYS_MS[attempt];\n}\n\n/**\n * Determine whether a failure should be retried.\n *\n * Retryable:\n * - Network errors (TypeError from fetch)\n * - AbortError / timeout (DOMException with name 'AbortError')\n * - HTTP 5xx (server errors)\n * - HTTP 429 (rate limited)\n *\n * NOT retryable:\n * - HTTP 401 (invalid/expired token — won't recover)\n * - HTTP 4xx (except 429) — deterministic validation errors\n * - HTTP 2xx/3xx (success)\n */\nexport function isRetryable(status: number | undefined, error: unknown): boolean {\n // Network failure: fetch throws TypeError\n if (error instanceof TypeError) return true;\n\n // AbortError from AbortController (timeout)\n if (error instanceof DOMException && error.name === 'AbortError') return true;\n\n // Check for AbortError-like objects (jsdom compatibility)\n if (error && typeof error === 'object' && 'name' in error && (error as { name: string }).name === 'AbortError') {\n return true;\n }\n\n // No response (timeout/DNS before headers)\n if (status === undefined) return true;\n\n // Server errors\n if (status >= 500 && status < 600) return true;\n\n // Rate limited\n if (status === 429) return true;\n\n // Everything else (4xx except 429, 2xx, 3xx)\n return false;\n}\n\n/**\n * Get the HTTP status from a fetch Response or error.\n * Returns undefined if no response was received (network error, timeout).\n */\nexport function getStatusFromError(error: unknown): number | undefined {\n if (error && typeof error === 'object' && 'status' in error) {\n const status = (error as { status: number }).status;\n if (typeof status === 'number') return status;\n }\n return undefined;\n}\n","import type { FeedbackConfig } from '../client/types';\n\nconst DEFAULT_GATEWAY_URL = 'http://localhost:8787';\nconst DEFAULT_TIMEOUT = 15_000;\nconst DEFAULT_MAX_RETRIES = 3;\n// Default: Ctrl+Shift+E — NOT Ctrl+Shift+F (conflicts with browser fullscreen)\nconst DEFAULT_KEY_COMBO = 'Ctrl+Shift+E';\n\nexport const DEFAULTS = {\n gatewayUrl: DEFAULT_GATEWAY_URL,\n timeout: DEFAULT_TIMEOUT,\n maxRetries: DEFAULT_MAX_RETRIES,\n keyCombo: DEFAULT_KEY_COMBO,\n} as const;\n\n/**\n * Validate and normalize configuration input.\n * Throws on missing required fields with clear error messages.\n *\n * Required fields: projectId, token\n * Optional (with defaults): gatewayUrl, timeout, maxRetries, keyCombo\n */\nexport function validateConfig(input: Partial<FeedbackConfig>): FeedbackConfig {\n const missing: string[] = [];\n\n if (!input.projectId || input.projectId.trim() === '') {\n missing.push('projectId');\n }\n\n if (!input.token || input.token.trim() === '') {\n missing.push('token');\n }\n\n if (missing.length > 0) {\n throw new Error(\n `[SynodFeedback] Missing required config field(s): ${missing.join(', ')}. ` +\n `Please provide these in init() or <FeedbackProvider config={...}>.`\n );\n }\n\n const gatewayUrl = input.gatewayUrl ?? DEFAULT_GATEWAY_URL;\n try {\n new URL(gatewayUrl);\n } catch {\n throw new Error(\n `[SynodFeedback] Invalid gatewayUrl: \"${gatewayUrl}\". Must be a valid URL.`\n );\n }\n\n return {\n gatewayUrl,\n projectId: input.projectId!,\n token: input.token!,\n keyCombo: input.keyCombo ?? DEFAULT_KEY_COMBO,\n timeout: input.timeout ?? DEFAULT_TIMEOUT,\n maxRetries: input.maxRetries ?? DEFAULT_MAX_RETRIES,\n };\n}\n\n/**\n * Maximum serialized context_json size in bytes (64KB).\n *\n * Sized to comfortably fit a 32KB screenshot data URL plus the selector /\n * html_context / url metadata fields. The previous 10KB ceiling was set for\n * the old local-findings gateway flow; the current routing paths (local\n * findings storage and self-feedback → synod-web) both tolerate much larger\n * payloads.\n */\nexport const MAX_CONTEXT_JSON_BYTES = 65_536;\n\n/**\n * Pre-validate that serialized context_json fits within the size limit.\n * Returns true if valid, false if oversized.\n */\nexport function isContextJsonValid(contextJson: unknown): boolean {\n const serialized = JSON.stringify(contextJson);\n return new TextEncoder().encode(serialized).length <= MAX_CONTEXT_JSON_BYTES;\n}\n","import type {\n FeedbackConfig,\n FindingPayload,\n ExternalFindingsRequest,\n BatchResponse,\n BatchResult,\n ClientError,\n} from './types';\nimport { createClientError } from './types';\nimport { isRetryable, getBackoffDelay } from '../utils/retry';\nimport { isContextJsonValid } from '../utils/config';\n\nconst ENDPOINT_PATH = '/v2/findings/external';\n\nexport class SynodFeedbackClient {\n private readonly config: FeedbackConfig;\n private _queue: FindingPayload[] = [];\n private _abortControllers: AbortController[] = [];\n\n constructor(config: FeedbackConfig) {\n this.config = config;\n }\n\n // ── Queue Management ────────────────────────────────────────────────\n\n /**\n * Add a finding to the local batch queue.\n * Pre-validates context_json size to prevent oversized payloads.\n */\n queue(finding: FindingPayload): void {\n if (!isContextJsonValid(finding.context_json)) {\n throw createClientError(\n 'CONTEXT_TOO_LARGE',\n 'context_json exceeds 10KB limit (serialized). Compress or remove screenshot data.',\n undefined,\n false,\n );\n }\n this._queue.push(finding);\n }\n\n /**\n * Get the current queue length.\n */\n get queueLength(): number {\n return this._queue.length;\n }\n\n /**\n * Get a shallow copy of the current queue.\n */\n getQueueSnapshot(): FindingPayload[] {\n return [...this._queue];\n }\n\n /**\n * Clear all items from the queue.\n */\n clearQueue(): void {\n this._queue = [];\n }\n\n // ── Batch Send ──────────────────────────────────────────────────────\n\n /**\n * Send all queued findings as a batch to the gateway.\n * On success (201): created items are dequeued; errored items remain.\n * On full failure (after retries): entire batch remains in queue.\n *\n * @returns BatchResult with created and errors arrays\n * @throws ClientError on full failure after retries, or on non-retryable error\n */\n async sendBatch(): Promise<BatchResult> {\n if (this._queue.length === 0) {\n return { created: [], errors: [] };\n }\n\n const findingsToSend = [...this._queue];\n const requestBody: ExternalFindingsRequest = {\n project_id: this.config.projectId,\n findings: findingsToSend,\n };\n\n const response = await this.postWithRetry(requestBody);\n const batchResult = this.processResponse(response, findingsToSend);\n\n return batchResult;\n }\n\n // ── HTTP Transport with Retry ───────────────────────────────────────\n\n /**\n * POST to the gateway with retry/backoff logic.\n * Retries up to maxRetries times on retryable failures (5xx, 429, network, timeout).\n * Does NOT retry on 401 or other 4xx (except 429).\n *\n * Worst-case wall time: ~1s + timeout + 2s + timeout + 4s + timeout.\n */\n private async postWithRetry(body: ExternalFindingsRequest): Promise<BatchResponse> {\n const maxRetries = this.config.maxRetries ?? 3;\n let lastError: ClientError | null = null;\n\n // Initial attempt + retries = total attempts = maxRetries + 1\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n // Apply backoff before retries (not before the initial attempt)\n if (attempt > 0) {\n const delay = getBackoffDelay(attempt - 1);\n if (delay !== undefined) {\n await this.sleep(delay);\n }\n }\n\n // Fresh AbortController per attempt\n const controller = new AbortController();\n this._abortControllers.push(controller);\n const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);\n\n try {\n const res = await fetch(`${this.config.gatewayUrl}${ENDPOINT_PATH}`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.config.token}`,\n },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n // Success: 200/201\n if (res.ok) {\n const data = await res.json() as BatchResponse;\n return data;\n }\n\n // Non-success status codes\n const clientErr = this.classifyHttpError(res.status);\n\n if (!isRetryable(res.status, null) || attempt >= maxRetries) {\n throw clientErr;\n }\n\n // Retryable — record error and continue loop\n lastError = clientErr;\n } catch (err) {\n clearTimeout(timeoutId);\n\n // Already classified as ClientError (from classifyHttpError)\n if (isClientError(err)) {\n if (!err.retryable || attempt >= maxRetries) {\n throw err;\n }\n lastError = err;\n continue;\n }\n\n // Network error or timeout — classify and check retryability\n const classified = this.classifyTransportError(err);\n\n if (!classified.retryable || attempt >= maxRetries) {\n throw classified;\n }\n\n lastError = classified;\n } finally {\n // Clean up AbortController reference\n const idx = this._abortControllers.indexOf(controller);\n if (idx >= 0) {\n this._abortControllers.splice(idx, 1);\n }\n }\n }\n\n // All retries exhausted\n throw lastError ?? createClientError(\n 'NETWORK_ERROR',\n 'All retry attempts exhausted.',\n undefined,\n true,\n );\n }\n\n // ── Response Processing ─────────────────────────────────────────────\n\n /**\n * Process the gateway response: dequeue created items, retain errored items.\n * Findings are matched by their position in the original sent batch.\n */\n private processResponse(response: BatchResponse, sentFindings: FindingPayload[]): BatchResult {\n // Created items: their indices in the sent batch are 0..n\n // The gateway returns created[] with title/severity, and errors[] with index/error.\n // Since the sent findings maintain their order, we use errors[].index to identify failures.\n const errorIndices = new Set(response.errors.map(e => e.index));\n const createdIndices: number[] = [];\n\n // Determine which sent findings were successfully created\n for (let i = 0; i < sentFindings.length; i++) {\n if (!errorIndices.has(i)) {\n createdIndices.push(i);\n }\n }\n\n // Dequeue created items (they were consumed from the front of the queue)\n // We need to remove them from the live queue by matching content+position\n this.dequeueByIndices(createdIndices, sentFindings.length);\n\n return {\n created: response.created,\n errors: response.errors,\n };\n }\n\n /**\n * Remove successfully sent items from the live queue.\n *\n * The sentFindings were a snapshot of the queue at send time. We need to remove\n * the successful ones while keeping the failed ones. Since they were all at the\n * front of the queue (in order), we rebuild: remove createdIndices from [0..sentCount-1].\n */\n private dequeueByIndices(keepIndices: number[], sentCount: number): void {\n // Items at keepIndices within [0..sentCount-1] were successfully created → remove them\n const removeIndices = new Set(keepIndices);\n const remaining: FindingPayload[] = [];\n\n // Items within sentCount: keep only non-created (errored) ones\n for (let i = 0; i < this._queue.length; i++) {\n if (i < sentCount) {\n if (!removeIndices.has(i)) {\n remaining.push(this._queue[i]);\n }\n } else {\n // Items beyond sentCount were queued after sendBatch started — keep them\n remaining.push(this._queue[i]);\n }\n }\n\n this._queue = remaining;\n }\n\n // ── Error Classification ────────────────────────────────────────────\n\n private classifyHttpError(status: number): ClientError {\n switch (true) {\n case status === 401:\n return createClientError(\n 'AUTH_ERROR',\n 'Authentication failed (401). Token is invalid or expired.',\n 401,\n false,\n );\n\n case status === 429:\n return createClientError(\n 'RATE_LIMITED',\n 'Rate limited (429). Too many findings in the time window.',\n 429,\n true,\n );\n\n case status >= 500 && status < 600:\n return createClientError(\n 'SERVER_ERROR',\n `Server error (${status}).`,\n status,\n true,\n );\n\n case status >= 400 && status < 500:\n return createClientError(\n 'CLIENT_ERROR',\n `Client error (${status}). Request rejected.`,\n status,\n false,\n );\n\n default:\n return createClientError(\n 'CLIENT_ERROR',\n `Unexpected status ${status}.`,\n status,\n false,\n );\n }\n }\n\n private classifyTransportError(err: unknown): ClientError {\n // Network failure: fetch throws TypeError\n if (err instanceof TypeError) {\n return createClientError(\n 'NETWORK_ERROR',\n `Network error: ${err.message}`,\n undefined,\n true,\n );\n }\n\n // AbortError from AbortController (timeout)\n if (\n (err instanceof DOMException && err.name === 'AbortError') ||\n (err && typeof err === 'object' && 'name' in err && (err as { name: string }).name === 'AbortError')\n ) {\n return createClientError(\n 'TIMEOUT',\n `Request timed out after ${this.config.timeout}ms.`,\n undefined,\n true,\n );\n }\n\n // Unknown error — treat as non-retryable\n const message = err instanceof Error ? err.message : String(err);\n return createClientError(\n 'NETWORK_ERROR',\n `Unexpected transport error: ${message}`,\n undefined,\n false,\n );\n }\n\n // ── Cleanup ─────────────────────────────────────────────────────────\n\n /**\n * Abort all in-flight requests.\n */\n destroy(): void {\n for (const controller of this._abortControllers) {\n try {\n controller.abort();\n } catch {\n // ignore\n }\n }\n this._abortControllers = [];\n }\n\n // ── Utilities ───────────────────────────────────────────────────────\n\n private sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n }\n}\n\n// ── Helper ──────────────────────────────────────────────────────────────\n\nfunction isClientError(err: unknown): err is ClientError {\n return err instanceof Error && 'code' in err && 'retryable' in err;\n}\n","import type { FeedbackConfig, FindingPayload, BatchResult } from '../client/types';\nimport { SynodFeedbackClient } from '../client/SynodFeedbackClient';\nimport { validateConfig } from '../utils/config';\n\ntype EnableHandler = () => void;\ntype DisableHandler = () => void;\n\n/**\n * Singleton lifecycle manager for the feedback extension.\n *\n * State machine:\n * uninitialized → (init) → initialized (enabled=false)\n * ↕ enable() / disable()\n * initialized (enabled=true/false)\n *\n * The client and queue are ALWAYS available regardless of `enabled` state —\n * `enabled` is a semantic signal for the interaction layer (SDG-294), not a gate on transport.\n */\nexport class FeedbackManager {\n private _initialized = false;\n private _enabled = false;\n private _client: SynodFeedbackClient | null = null;\n private _config: FeedbackConfig | null = null;\n private _enableHandlers: EnableHandler[] = [];\n private _disableHandlers: DisableHandler[] = [];\n\n get initialized(): boolean {\n return this._initialized;\n }\n\n get enabled(): boolean {\n return this._enabled;\n }\n\n /** The active client instance, or null if not initialized. */\n get client(): SynodFeedbackClient | null {\n return this._client;\n }\n\n /** The validated config, or null if not initialized. */\n get config(): FeedbackConfig | null {\n return this._config;\n }\n\n // ── Lifecycle ───────────────────────────────────────────────────────\n\n /**\n * Initialize the feedback system with configuration.\n * Creates the singleton client instance. Re-init replaces existing state.\n */\n init(config: FeedbackConfig): void {\n const validated = validateConfig(config);\n\n // Destroy existing client if re-initializing\n if (this._client) {\n this._client.destroy();\n }\n\n this._client = new SynodFeedbackClient(validated);\n this._config = validated;\n this._initialized = true;\n this._enabled = false;\n }\n\n /**\n * Enable the feedback system. Fires all registered onEnable handlers.\n */\n enable(): void {\n if (!this._initialized) {\n throw new Error('[SynodFeedback] Cannot enable() before init(). Call init(config) first.');\n }\n if (this._enabled) return;\n\n this._enabled = true;\n // Fire handlers in registration order\n for (const handler of [...this._enableHandlers]) {\n handler();\n }\n }\n\n /**\n * Disable the feedback system. Fires all registered onDisable handlers.\n */\n disable(): void {\n if (!this._initialized) return;\n if (!this._enabled) return;\n\n this._enabled = false;\n for (const handler of [...this._disableHandlers]) {\n handler();\n }\n }\n\n // ── Extension Hooks (for SDG-294) ───────────────────────────────────\n\n /**\n * Register a handler to be called when enable() is invoked.\n * If already enabled, the handler fires immediately.\n * @returns unregister function\n */\n onEnable(handler: EnableHandler): () => void {\n this._enableHandlers.push(handler);\n if (this._enabled) {\n handler();\n }\n return () => {\n const idx = this._enableHandlers.indexOf(handler);\n if (idx >= 0) this._enableHandlers.splice(idx, 1);\n };\n }\n\n /**\n * Register a handler to be called when disable() is invoked.\n * @returns unregister function\n */\n onDisable(handler: DisableHandler): () => void {\n this._disableHandlers.push(handler);\n return () => {\n const idx = this._disableHandlers.indexOf(handler);\n if (idx >= 0) this._disableHandlers.splice(idx, 1);\n };\n }\n\n // ── Convenience: delegate to client ─────────────────────────────────\n\n queue(finding: FindingPayload): void {\n this.requireClient();\n this._client!.queue(finding);\n }\n\n async sendBatch(): Promise<BatchResult> {\n this.requireClient();\n return this._client!.sendBatch();\n }\n\n get queueLength(): number {\n return this._client?.queueLength ?? 0;\n }\n\n getQueueSnapshot(): FindingPayload[] {\n return this._client?.getQueueSnapshot() ?? [];\n }\n\n /**\n * Clear all items from the queue.\n */\n clearQueue(): void {\n this._client?.clearQueue();\n }\n\n /**\n * Replace the entire queue with the given items.\n * Used by the interaction layer for remove/edit operations.\n */\n replaceQueue(items: FindingPayload[]): void {\n if (!this._client) return;\n this._client.clearQueue();\n for (const item of items) {\n this._client.queue(item);\n }\n }\n\n // ── Teardown ────────────────────────────────────────────────────────\n\n /**\n * Reset the manager to uninitialized state.\n * Destroys client, clears handlers.\n */\n destroy(): void {\n this._client?.destroy();\n this._client = null;\n this._initialized = false;\n this._enabled = false;\n this._enableHandlers = [];\n this._disableHandlers = [];\n }\n\n private requireClient(): void {\n if (!this._initialized || !this._client) {\n throw new Error('[SynodFeedback] Not initialized. Call init(config) first.');\n }\n }\n}\n","// ── Shadow DOM Internal CSS ──────────────────────────────────────────────\n//\n// All styles are injected as a <style> element inside the closed shadow root.\n// No CSS custom properties are read from the host page — all values are hardcoded.\n// prefers-reduced-motion is respected.\n\nexport const OVERLAY_CSS = `\n:host {\n all: initial;\n}\n\n*, *::before, *::after {\n box-sizing: border-box;\n font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n}\n\n/* ── Shared variables (internal only) ──────────────────────────────────── */\n.synod-fb {\n --fb-bg-panel: #1e293b;\n --fb-bg-elevated: #334155;\n --fb-bg-input: #0f172a;\n --fb-bg-hover: #475569;\n --fb-border: #475569;\n --fb-border-subtle: #334155;\n --fb-text-primary: #f1f5f9;\n --fb-text-secondary: #94a3b8;\n --fb-text-muted: #64748b;\n --fb-accent: #0d9488;\n --fb-accent-hover: #0f766e;\n --fb-highlight: #2DAFAF;\n --fb-highlight-fill: rgba(45, 175, 175, 0.08);\n --fb-sev-low: #64748b;\n --fb-sev-medium: #d97706;\n --fb-sev-high: #dc2626;\n --fb-success: #059669;\n --fb-error: #dc2626;\n --fb-radius-sm: 4px;\n --fb-radius-md: 8px;\n --fb-radius-lg: 12px;\n --fb-shadow-panel: 0 8px 32px rgba(0,0,0,0.3);\n --fb-shadow-popover: 0 4px 16px rgba(0,0,0,0.2);\n}\n\n/* ── Highlighter ───────────────────────────────────────────────────────── */\n.synod-fb-highlighter {\n position: absolute;\n pointer-events: none;\n border: 2px dashed #2DAFAF;\n background: rgba(45, 175, 175, 0.08);\n border-radius: 2px;\n z-index: 1;\n transition: all 0ms;\n}\n\n.synod-fb-inspector {\n position: absolute;\n pointer-events: none;\n background: #1F8F8F;\n color: #ffffff;\n font-size: 11px;\n font-family: 'SF Mono', 'Cascadia Code', 'Consolas', monospace;\n line-height: 20px;\n height: 20px;\n padding: 0 8px;\n border-radius: 4px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 400px;\n z-index: 2;\n}\n\n.synod-fb-highlighter-logo {\n position: absolute;\n pointer-events: none;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n background: #0f172a;\n border: 1px solid #2DAFAF;\n border-radius: 4px;\n z-index: 3;\n}\n\n/* ── Floating logo ─────────────────────────────────────────────────────── */\n.synod-fb-logo {\n position: absolute;\n width: 36px;\n height: 36px;\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: grab;\n user-select: none;\n pointer-events: auto;\n transition: transform 150ms ease-out, filter 150ms ease-out;\n z-index: 10;\n filter: drop-shadow(0 2px 6px rgba(0,0,0,0.25));\n}\n\n.synod-fb-logo svg {\n width: 32px;\n height: 32px;\n pointer-events: none;\n}\n\n.synod-fb-logo:hover {\n transform: scale(1.08);\n filter: drop-shadow(0 4px 10px rgba(0,0,0,0.35));\n}\n\n.synod-fb-logo:active {\n cursor: grabbing;\n transform: scale(0.96);\n}\n\n.synod-fb-logo-badge {\n position: absolute;\n top: -3px;\n right: -3px;\n min-width: 18px;\n height: 18px;\n border-radius: 9px;\n /* Synod teal — replaces the previous generic red. Reads as brand chrome\n rather than a notifications-style alert. */\n background: #0d9488;\n color: #ffffff;\n font-size: 11px;\n font-weight: 700;\n font-family: 'SF Mono', 'Cascadia Code', 'Consolas', monospace;\n line-height: 18px;\n text-align: center;\n padding: 0 4px;\n letter-spacing: -0.2px;\n /* Double-ring outline: white inner ring guarantees contrast on dark\n backgrounds; the semi-opaque dark outer ring guarantees contrast on\n light backgrounds. Together they pop on any backdrop without needing\n per-site tuning. */\n border: 2px solid #ffffff;\n box-shadow: 0 0 0 1px rgba(15, 23, 42, 0.55), 0 1px 3px rgba(0, 0, 0, 0.25);\n}\n\n.synod-fb-logo-badge-pulse {\n animation: synod-fb-badge-pulse 300ms ease-in-out;\n}\n\n@keyframes synod-fb-badge-pulse {\n 0%, 100% { transform: scale(1); }\n 50% { transform: scale(1.15); }\n}\n\n/* ── Logo context menu ─────────────────────────────────────────────────── */\n.synod-fb-context-menu {\n position: absolute;\n min-width: 160px;\n background: #1e293b;\n border: 1px solid #475569;\n border-radius: 6px;\n box-shadow: 0 8px 24px rgba(0,0,0,0.35);\n padding: 4px;\n z-index: 50;\n pointer-events: auto;\n font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n animation: synod-fb-fade-in 100ms ease-out;\n}\n\n.synod-fb-context-menu-item {\n display: block;\n width: 100%;\n text-align: left;\n padding: 6px 10px;\n background: transparent;\n border: none;\n border-radius: 4px;\n color: #f1f5f9;\n font-size: 12px;\n font-weight: 500;\n cursor: pointer;\n font-family: inherit;\n}\n\n.synod-fb-context-menu-item:hover {\n background: #dc2626;\n color: #fff;\n}\n\n/* ── Charge indicator ──────────────────────────────────────────────────── */\n.synod-fb-charge {\n position: absolute;\n pointer-events: none;\n z-index: 15;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n transform: translate(-50%, -50%);\n transition: transform 120ms ease-out;\n}\n\n.synod-fb-charge-ring {\n position: absolute;\n inset: 0;\n width: 40px;\n height: 40px;\n transform: rotate(-90deg);\n}\n\n.synod-fb-charge-ring-bg {\n fill: none;\n stroke: rgba(45, 175, 175, 0.18);\n stroke-width: 3;\n}\n\n.synod-fb-charge-ring-fg {\n fill: none;\n stroke: #2DAFAF;\n stroke-width: 3;\n stroke-linecap: round;\n transition: stroke-dashoffset 60ms linear, stroke 200ms ease-out;\n}\n\n.synod-fb-charge svg:not(.synod-fb-charge-ring) {\n width: 22px;\n height: 22px;\n filter: drop-shadow(0 2px 6px rgba(0,0,0,0.3));\n}\n\n.synod-fb-charge.charged {\n transform: translate(-50%, -50%) scale(1.15);\n}\n\n.synod-fb-charge.charged .synod-fb-charge-ring-fg {\n stroke: #36D6D6;\n}\n\n/* ── IssueForm ─────────────────────────────────────────────────────────── */\n.synod-fb-form {\n position: absolute;\n width: 340px;\n max-height: 90vh;\n overflow-y: auto;\n background: #1e293b;\n border: 1px solid #475569;\n border-radius: 8px;\n box-shadow: 0 8px 32px rgba(0,0,0,0.3);\n z-index: 20;\n pointer-events: auto;\n color: #f1f5f9;\n font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n animation: synod-fb-fade-in 150ms ease-out;\n}\n\n@keyframes synod-fb-fade-in {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n.synod-fb-form-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 12px;\n background: #334155;\n border-radius: 8px 8px 0 0;\n border-bottom: 1px solid #475569;\n gap: 8px;\n}\n\n.synod-fb-form-header-logo {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n}\n\n.synod-fb-form-header-text {\n flex: 1;\n font-size: 11px;\n font-family: 'SF Mono', 'Cascadia Code', 'Consolas', monospace;\n color: #94a3b8;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.synod-fb-form-close {\n background: none;\n border: none;\n color: #94a3b8;\n cursor: pointer;\n font-size: 16px;\n padding: 0 4px;\n line-height: 1;\n}\n\n.synod-fb-form-close:hover { color: #f1f5f9; }\n\n.synod-fb-form-body { padding: 12px; }\n\n.synod-fb-field { margin-bottom: 12px; }\n\n.synod-fb-label {\n display: block;\n font-size: 11px;\n font-weight: 500;\n color: #94a3b8;\n margin-bottom: 4px;\n}\n\n.synod-fb-input,\n.synod-fb-textarea {\n width: 100%;\n background: #0f172a;\n border: 1px solid #475569;\n border-radius: 4px;\n color: #f1f5f9;\n font-size: 13px;\n font-family: inherit;\n padding: 8px 10px;\n outline: none;\n resize: vertical;\n}\n\n.synod-fb-input:focus,\n.synod-fb-textarea:focus {\n border-color: #0d9488;\n}\n\n.synod-fb-textarea {\n min-height: 48px;\n}\n\n.synod-fb-severity-group {\n display: flex;\n gap: 0;\n border-radius: 4px;\n overflow: hidden;\n border: 1px solid #475569;\n}\n\n.synod-fb-sev-btn {\n flex: 1;\n padding: 6px 0;\n border: none;\n background: transparent;\n color: #94a3b8;\n font-size: 12px;\n font-weight: 500;\n cursor: pointer;\n transition: background 100ms;\n}\n\n.synod-fb-sev-btn:not(:last-child) {\n border-right: 1px solid #475569;\n}\n\n.synod-fb-sev-btn:hover { background: #475569; }\n\n.synod-fb-sev-btn.active[data-sev=\"low\"] { background: #64748b; color: #fff; }\n.synod-fb-sev-btn.active[data-sev=\"medium\"] { background: #d97706; color: #fff; }\n.synod-fb-sev-btn.active[data-sev=\"high\"] { background: #dc2626; color: #fff; }\n\n.synod-fb-screenshot-toggle {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 10px;\n background: #0f172a;\n border: 1px solid #475569;\n border-radius: 4px;\n cursor: pointer;\n font-size: 12px;\n color: #94a3b8;\n}\n\n.synod-fb-screenshot-toggle:hover { border-color: #64748b; }\n\n.synod-fb-screenshot-toggle input { accent-color: #0d9488; }\n\n.synod-fb-screenshot-toggle.disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n\n.synod-fb-screenshot-toggle.unavailable {\n color: #d97706;\n}\n\n.synod-fb-form-actions {\n display: flex;\n gap: 8px;\n margin-top: 4px;\n}\n\n.synod-fb-btn-secondary {\n flex: 1;\n padding: 8px 12px;\n border: 1px solid #0d9488;\n background: transparent;\n color: #0d9488;\n font-size: 12px;\n font-weight: 500;\n border-radius: 4px;\n cursor: pointer;\n font-family: inherit;\n}\n\n.synod-fb-btn-secondary:hover { background: #0d9488; color: #fff; }\n\n.synod-fb-btn-primary {\n flex: 1;\n padding: 8px 12px;\n border: none;\n background: #0d9488;\n color: #fff;\n font-size: 12px;\n font-weight: 500;\n border-radius: 4px;\n cursor: pointer;\n font-family: inherit;\n}\n\n.synod-fb-btn-primary:hover { background: #0f766e; }\n.synod-fb-btn-primary:active { transform: scale(0.98); }\n.synod-fb-btn-primary:disabled { background: #334155; color: #64748b; cursor: not-allowed; }\n\n/* ── QueuePanel ────────────────────────────────────────────────────────── */\n.synod-fb-queue {\n position: absolute;\n top: 16px;\n right: 16px;\n width: 380px;\n max-width: calc(100vw - 32px);\n max-height: min(520px, calc(100vh - 32px));\n background: #1e293b;\n border: 1px solid #475569;\n border-radius: 8px;\n box-shadow: 0 8px 32px rgba(0,0,0,0.3);\n z-index: 30;\n pointer-events: auto;\n display: flex;\n flex-direction: column;\n color: #f1f5f9;\n font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n animation: synod-fb-slide-in 200ms cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n@keyframes synod-fb-slide-in {\n from { transform: translateX(400px); opacity: 0; }\n to { transform: translateX(0); opacity: 1; }\n}\n\n.synod-fb-queue-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 16px;\n border-bottom: 1px solid #334155;\n gap: 8px;\n}\n\n.synod-fb-queue-header-logo {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n}\n\n.synod-fb-queue-title {\n font-size: 14px;\n font-weight: 600;\n color: #f1f5f9;\n}\n\n.synod-fb-queue-body {\n flex: 1;\n overflow-y: auto;\n padding: 8px;\n}\n\n.synod-fb-queue-empty {\n padding: 24px 16px;\n text-align: center;\n color: #64748b;\n font-size: 13px;\n}\n\n.synod-fb-queue-item {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n padding: 8px;\n border-radius: 4px;\n border: 1px solid transparent;\n margin-bottom: 4px;\n}\n\n.synod-fb-queue-item:hover { background: #334155; }\n\n.synod-fb-queue-item-error {\n font-size: 11px;\n color: #dc2626;\n padding: 4px 8px 8px;\n}\n\n.synod-fb-queue-item-thumb {\n width: 44px;\n height: 44px;\n min-width: 44px;\n border-radius: 4px;\n border: 1px dashed #475569;\n display: flex;\n align-items: center;\n justify-content: center;\n overflow: hidden;\n}\n\n.synod-fb-queue-item-thumb img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n}\n\n.synod-fb-queue-item-content {\n flex: 1;\n min-width: 0;\n}\n\n.synod-fb-queue-item-title {\n font-size: 12px;\n color: #f1f5f9;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.synod-fb-queue-item-title.synod-fb-pending {\n color: #64748b;\n font-style: italic;\n}\n\n.synod-fb-queue-item-meta {\n font-size: 10px;\n font-family: 'SF Mono', 'Cascadia Code', 'Consolas', monospace;\n color: #64748b;\n}\n\n.synod-fb-queue-item-actions {\n display: flex;\n gap: 4px;\n}\n\n.synod-fb-icon-btn {\n background: none;\n border: none;\n color: #94a3b8;\n cursor: pointer;\n font-size: 14px;\n padding: 2px;\n line-height: 1;\n}\n\n.synod-fb-icon-btn:hover { color: #f1f5f9; }\n\n.synod-fb-queue-footer {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 12px 16px;\n border-top: 1px solid #334155;\n gap: 8px;\n}\n\n.synod-fb-btn-danger {\n padding: 6px 12px;\n border: 1px solid #475569;\n background: transparent;\n color: #dc2626;\n font-size: 12px;\n font-weight: 500;\n border-radius: 4px;\n cursor: pointer;\n font-family: inherit;\n}\n\n.synod-fb-btn-danger:hover { background: #dc2626; color: #fff; border-color: #dc2626; }\n\n.synod-fb-queue-banner {\n padding: 8px 12px;\n margin: 8px;\n border-radius: 4px;\n font-size: 12px;\n}\n\n.synod-fb-queue-banner.error {\n background: rgba(220, 38, 38, 0.15);\n color: #fca5a5;\n border: 1px solid rgba(220, 38, 38, 0.3);\n}\n\n.synod-fb-queue-banner.info {\n background: rgba(13, 148, 136, 0.15);\n color: #5eead4;\n border: 1px solid rgba(13, 148, 136, 0.3);\n}\n\n/* ── Toasts ────────────────────────────────────────────────────────────── */\n.synod-fb-toast-container {\n position: absolute;\n bottom: 16px;\n left: 50%;\n transform: translateX(-50%);\n display: flex;\n flex-direction: column;\n gap: 8px;\n z-index: 100;\n pointer-events: none;\n}\n\n.synod-fb-toast {\n max-width: 400px;\n min-height: 36px;\n padding: 8px 16px;\n border-radius: 6px;\n font-size: 12px;\n font-weight: 500;\n font-family: inherit;\n display: flex;\n align-items: center;\n gap: 6px;\n animation: synod-fb-toast-in 200ms ease-out;\n white-space: nowrap;\n}\n\n.synod-fb-toast-logo {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n}\n\n@keyframes synod-fb-toast-in {\n from { transform: translateY(20px); opacity: 0; }\n to { transform: translateY(0); opacity: 1; }\n}\n\n.synod-fb-toast.success { background: #065f46; color: #d1fae5; }\n.synod-fb-toast.error { background: #991b1b; color: #fee2e2; }\n.synod-fb-toast.info { background: #1e3a5f; color: #dbeafe; }\n\n/* ── Tooltip ───────────────────────────────────────────────────────────── */\n.synod-fb-tooltip {\n position: absolute;\n padding: 4px 10px;\n background: #0f172a;\n color: #94a3b8;\n font-size: 11px;\n border-radius: 4px;\n border: 1px solid #334155;\n white-space: nowrap;\n z-index: 5;\n pointer-events: none;\n animation: synod-fb-fade-in 150ms ease-out;\n}\n\n/* ── Focus-visible ─────────────────────────────────────────────────────── */\nbutton:focus-visible,\ninput:focus-visible,\ntextarea:focus-visible {\n outline: 2px solid #0d9488;\n outline-offset: 2px;\n}\n\n/* ── Reduced motion ────────────────────────────────────────────────────── */\n@media (prefers-reduced-motion: reduce) {\n *, *::before, *::after {\n animation-duration: 0ms !important;\n animation-delay: 0ms !important;\n transition-duration: 0ms !important;\n }\n .synod-fb-queue {\n animation: none;\n opacity: 1;\n }\n .synod-fb-form {\n animation: none;\n opacity: 1;\n }\n .synod-fb-toast {\n animation: none;\n opacity: 1;\n }\n}\n`;\n","// ── FeedbackOverlay — Shadow DOM host container ──────────────────────────\n//\n// Creates a closed Shadow DOM host element at z-index 2147483647.\n// All extension UI renders inside the shadow root.\n// Provides the shadow root reference to children via getRoot().\n//\n// Pointer-events strategy:\n// Host element: none (transparent passthrough to host page)\n// Visual overlays (highlighter): none\n// Interactive UI (form, queue, logo): auto\n\nimport { OVERLAY_CSS } from '../utils/overlay-css';\n\nconst HOST_ID = 'synod-fb-host';\nconst MAX_Z = 2147483647;\n\nexport class FeedbackOverlay {\n private _host: HTMLElement | null = null;\n private _shadowRoot: ShadowRoot | null = null;\n private _contentWrapper: HTMLDivElement | null = null;\n\n /** Whether the overlay is currently mounted. */\n get isMounted(): boolean {\n return this._host !== null && this._host.isConnected;\n }\n\n /** The shadow root (available only while mounted). */\n get root(): ShadowRoot | null {\n return this._shadowRoot;\n }\n\n /** The host element ID. */\n get hostId(): string {\n return HOST_ID;\n }\n\n /**\n * Get the actual host DOM element (for contains() checks by the picker).\n * Returns null if not mounted.\n */\n getHostElement(): HTMLElement | null {\n return this._host;\n }\n\n /**\n * Mount the overlay host to the given parent element (usually document.body).\n * Creates a closed shadow root, injects CSS, and sets up the content wrapper.\n *\n * Idempotent: if already mounted, returns without re-creating.\n */\n mount(parent: HTMLElement): void {\n if (this.isMounted) return;\n\n const host = document.createElement('div');\n host.id = HOST_ID;\n host.style.cssText = `position: fixed; inset: 0; z-index: ${MAX_Z}; pointer-events: none;`;\n\n // Closed shadow root — host JS cannot access internals\n const shadowRoot = host.attachShadow({ mode: 'closed' });\n\n // Inject CSS\n const style = document.createElement('style');\n style.textContent = OVERLAY_CSS;\n shadowRoot.appendChild(style);\n\n // Content wrapper for all visual elements\n const contentWrapper = document.createElement('div');\n contentWrapper.className = 'synod-fb';\n shadowRoot.appendChild(contentWrapper);\n\n parent.appendChild(host);\n\n this._host = host;\n this._shadowRoot = shadowRoot;\n this._contentWrapper = contentWrapper;\n }\n\n /**\n * Unmount the overlay: remove host element, null references.\n * Safe to call even if not mounted.\n */\n unmount(): void {\n if (this._host && this._host.parentElement) {\n this._host.parentElement.removeChild(this._host);\n }\n this._host = null;\n this._shadowRoot = null;\n this._contentWrapper = null;\n }\n\n /**\n * Get the content wrapper element inside the shadow root.\n * Children append their DOM nodes to this wrapper.\n */\n getContentWrapper(): HTMLDivElement | null {\n return this._contentWrapper;\n }\n\n /**\n * Check whether a given element is inside the overlay host.\n */\n contains(element: Node | null): boolean {\n if (!element || !this._host) return false;\n return this._host === element || this._host.contains(element);\n }\n}\n","// ── Synod brand mark — inlined SVG ────────────────────────────────────────\n//\n// Source: docs/branding/synod-logo.svg (three rotated triangles + center dot).\n// Inlined as a TS constant so the SDK is self-contained — no runtime fetch,\n// no bundler asset-loading config required. Closed-shadow DOM consumers\n// (FeedbackOverlay) get the same SVG inside their shadow root.\n\nexport const SYNOD_LOGO_SVG_MARKUP = `<svg viewBox=\"0 0 100 100\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M50 15 L72 52 H28 Z\" fill=\"#36D6D6\" />\n <path d=\"M50 15 L72 52 H28 Z\" fill=\"#2DAFAF\" transform=\"rotate(120 50 58)\" />\n <path d=\"M50 15 L72 52 H28 Z\" fill=\"#1F8F8F\" transform=\"rotate(240 50 58)\" />\n <circle cx=\"50\" cy=\"58\" r=\"4\" fill=\"white\" />\n</svg>`;\n\n/**\n * Create a sized SVGSVGElement containing the Synod brand mark.\n * The returned element is detached — caller appends it to whatever parent.\n *\n * Uses DOMParser rather than innerHTML so callers can drop the result\n * directly into either a regular DOM node or a shadow root without\n * tripping Content Security Policy `style-src` checks (no inline style attr).\n */\nexport function createSynodLogo(size: number): SVGSVGElement {\n const doc = new DOMParser().parseFromString(SYNOD_LOGO_SVG_MARKUP, 'image/svg+xml');\n const svg = doc.documentElement as unknown as SVGSVGElement;\n svg.setAttribute('width', String(size));\n svg.setAttribute('height', String(size));\n svg.setAttribute('aria-hidden', 'true');\n return svg;\n}\n","// ── Highlighter — Visual overlay for hovered element ─────────────────────\n//\n// Renders outline + inspector chip inside the shadow DOM root.\n// Positioned to match the target element's bounding rect.\n// Does NOT modify the host element's own styles.\n\nimport type { FeedbackOverlay } from '../ui/FeedbackOverlay';\nimport { createSynodLogo } from '../utils/logo';\n\nexport class Highlighter {\n private _overlay: FeedbackOverlay;\n private _highlightEl: HTMLDivElement | null = null;\n private _inspectorEl: HTMLDivElement | null = null;\n private _logoEl: HTMLDivElement | null = null;\n\n constructor(overlay: FeedbackOverlay) {\n this._overlay = overlay;\n }\n\n /**\n * Show highlight at the target element's bounding rect.\n * Updates the inspector chip with tag, id/classes, and dimensions.\n */\n highlight(element: Element): void {\n if (!element.isConnected) {\n this.clear();\n return;\n }\n\n const wrapper = this._overlay.getContentWrapper();\n if (!wrapper) return;\n\n if (!this._highlightEl) {\n this._highlightEl = document.createElement('div');\n this._highlightEl.className = 'synod-fb-highlighter';\n wrapper.appendChild(this._highlightEl);\n }\n\n if (!this._inspectorEl) {\n this._inspectorEl = document.createElement('div');\n this._inspectorEl.className = 'synod-fb-inspector';\n this._inspectorEl.setAttribute('aria-hidden', 'true');\n wrapper.appendChild(this._inspectorEl);\n }\n\n if (!this._logoEl) {\n this._logoEl = document.createElement('div');\n this._logoEl.className = 'synod-fb-highlighter-logo';\n this._logoEl.setAttribute('aria-hidden', 'true');\n this._logoEl.appendChild(createSynodLogo(14));\n wrapper.appendChild(this._logoEl);\n }\n\n const rect = element.getBoundingClientRect();\n\n this._highlightEl!.style.display = 'block';\n this._highlightEl!.style.left = `${rect.left}px`;\n this._highlightEl!.style.top = `${rect.top}px`;\n this._highlightEl!.style.width = `${rect.width}px`;\n this._highlightEl!.style.height = `${rect.height}px`;\n\n const chipText = this.buildInspectorText(element, rect);\n this._inspectorEl!.style.display = 'block';\n this._inspectorEl!.textContent = chipText;\n\n const chipTop = rect.top > 24 ? rect.top - 24 : rect.bottom + 4;\n this._inspectorEl!.style.left = `${rect.left}px`;\n this._inspectorEl!.style.top = `${chipTop}px`;\n\n // Position the small Synod logo badge in the top-left corner of the highlight,\n // sitting just outside the border. Hidden when the element is too small.\n this._logoEl!.style.display = rect.width < 30 || rect.height < 30 ? 'none' : 'block';\n if (this._logoEl!.style.display === 'block') {\n this._logoEl!.style.left = `${rect.left - 18}px`;\n this._logoEl!.style.top = `${rect.top - 18}px`;\n }\n }\n\n /**\n * Clear the highlight overlay and inspector chip.\n */\n clear(): void {\n if (this._highlightEl) {\n this._highlightEl.style.display = 'none';\n }\n if (this._inspectorEl) {\n this._inspectorEl.style.display = 'none';\n }\n if (this._logoEl) {\n this._logoEl.style.display = 'none';\n }\n }\n\n /**\n * Remove all DOM nodes created by this Highlighter.\n */\n destroy(): void {\n this._highlightEl?.remove();\n this._inspectorEl?.remove();\n this._logoEl?.remove();\n this._highlightEl = null;\n this._inspectorEl = null;\n this._logoEl = null;\n }\n\n /**\n * Build the inspector chip text: tagname + id/classes + dimensions.\n */\n private buildInspectorText(element: Element, rect: DOMRect): string {\n const tag = element.tagName.toLowerCase();\n let id = element.id ? `#${element.id}` : '';\n\n let classStr = '';\n if (element.classList.length > 0) {\n classStr = '.' + Array.from(element.classList).join('.');\n if (classStr.length > 40) {\n classStr = classStr.substring(0, 37) + '…';\n }\n }\n\n const identifier = (id || classStr) ? `${id}${classStr}` : tag;\n const dimensions = `${Math.round(rect.width)}×${Math.round(rect.height)}`;\n\n return `${identifier} · ${dimensions}`;\n }\n}\n","// ── SelectionExtractor — Pure extraction, no DOM side effects ─────────────\n//\n// Captures element metadata that's useful for an LLM agent trying to\n// understand what the user was looking at. We deliberately do NOT generate\n// a CSS selector or XPath: nth-of-type chains and absolute XPaths are\n// noise that an agent can't act on. The agent gets far more value from\n// the element's tag + attributes (especially class/id) and a filtered\n// parent chain showing the meaningful ancestor layout.\n//\n// Unit-testable independently of the picker.\n\nimport type { SelectionContext } from './types';\n\n/**\n * Truncate text to max length with ellipsis.\n */\nfunction truncateText(text: string, max: number): string {\n const trimmed = text.trim().replace(/\\s+/g, ' ');\n if (trimmed.length <= max) return trimmed;\n return trimmed.substring(0, max) + '…';\n}\n\n// Attributes that bloat the payload without aiding diagnosis. Dropped from\n// elementAttributes. (Live event handlers, image resolution hints, etc.)\nconst NOISY_ATTRS = new Set([\n 'srcset',\n 'data-srcset',\n 'integrity',\n 'onload',\n 'onerror',\n 'onclick',\n 'onmouseover',\n 'onsubmit',\n]);\n\n/**\n * Build a filtered attribute map for an element.\n * Drops event-handler attributes and any value larger than 200 chars\n * (e.g. inline data URLs, inlined SVGs) — those would push context_json\n * over the gateway's 10KB cap without helping diagnosis.\n */\nfunction extractAttributes(element: Element): Record<string, string> {\n const out: Record<string, string> = {};\n // Iterate the NamedNodeMap directly — element.attributes is live and\n // we want a snapshot.\n for (let i = 0; i < element.attributes.length; i++) {\n const attr = element.attributes[i];\n const name = attr.name.toLowerCase();\n if (NOISY_ATTRS.has(name)) continue;\n const value = attr.value;\n if (value.length > 200) continue;\n out[name] = value;\n }\n return out;\n}\n\n/**\n * Walk parentElement up to <html>, returning a chain of ancestors that\n * actually carry identifying information (an id or a non-empty class).\n * Generic `<div>` / `<body>` / `<html>` wrappers are skipped — they bloat\n * the payload without telling the agent anything useful about layout.\n *\n * Output is root-first: outermost meaningful ancestor → nearest.\n * Each entry is {tag, id?, className?}.\n */\nfunction extractParentChain(element: Element): Array<{ tag: string; id?: string; className?: string }> {\n const chain: Array<{ tag: string; id?: string; className?: string }> = [];\n let current: Element | null = element.parentElement;\n while (current && current.nodeType === 1) {\n const id = current.id || undefined;\n // Drop whitespace-only class strings; keep the raw className (not split)\n // so agents see the original authoring intent.\n const cls = (current.getAttribute('class') ?? '').trim() || undefined;\n\n if (id || cls) {\n const entry: { tag: string; id?: string; className?: string } = {\n tag: current.tagName.toLowerCase(),\n };\n if (id) entry.id = id;\n if (cls) entry.className = cls;\n chain.unshift(entry);\n }\n\n if (current.tagName === 'HTML') break;\n current = current.parentElement;\n }\n return chain;\n}\n\n/**\n * Truncate outerHTML at a char boundary, preserving a trailing ellipsis\n * and the closing tag when possible so the snippet still parses visually.\n */\nfunction truncateHtml(html: string, max: number): string {\n if (html.length <= max) return html;\n // Try to keep the closing tag intact so the snippet reads as an open+close pair.\n const slice = html.substring(0, max - 1);\n const lastLt = slice.lastIndexOf('<');\n // If we sliced mid-tag (a '<' within the last 30 chars without a closing '>'),\n // drop that partial tag.\n const cleaned = lastLt > max - 30 && !slice.slice(lastLt).includes('>')\n ? slice.substring(0, lastLt)\n : slice;\n return cleaned + '…';\n}\n\n/**\n * Main extraction function. Pure — no DOM side effects.\n */\nexport function extract(element: Element): SelectionContext {\n const rect = element.getBoundingClientRect();\n\n return {\n tagName: element.tagName.toLowerCase(),\n elementText: truncateText(element.textContent ?? '', 200),\n elementHtml: truncateHtml(element.outerHTML, 1500),\n elementAttributes: extractAttributes(element),\n parentChain: extractParentChain(element),\n boundingRect: {\n left: Math.round(rect.left),\n top: Math.round(rect.top),\n width: Math.round(rect.width),\n height: Math.round(rect.height),\n },\n };\n}\n\nexport type { SelectionContext };\n","// ── ElementPicker — DOM element selection with capture-phase interception ─\n//\n// Attaches mousemove/click/keydown listeners in CAPTURE phase to document.\n// mousemove → Highlighter.highlight (rAF-throttled)\n// click → extract + IssueForm.show (capture-phase interception)\n// keydown → Escape exits selection mode\n// scroll → refresh highlight\n//\n// All listeners registered with AbortSignal; deactivate() = controller.abort().\n\nimport type { Highlighter } from './Highlighter';\nimport { extract } from './SelectionExtractor';\nimport type { SelectionContext } from './types';\n\nexport interface ElementPickerOptions {\n /** The overlay host element — clicks inside this are ignored. */\n overlayHostElement: HTMLElement;\n highlighter: Highlighter;\n onElementClick: (element: Element, context: SelectionContext) => void;\n onEscape: () => void;\n}\n\nexport class ElementPicker {\n private _options: ElementPickerOptions;\n private _controller: AbortController | null = null;\n private _active = false;\n private _rafId: number | null = null;\n private _pendingEvent: MouseEvent | null = null;\n\n constructor(options: ElementPickerOptions) {\n this._options = options;\n }\n\n get isActive(): boolean {\n return this._active;\n }\n\n /**\n * Activate the picker: attach capture-phase listeners to document.\n * No-op if already active (state machine guard).\n */\n activate(): void {\n if (this._active) return;\n\n this._controller = new AbortController();\n this._active = true;\n\n const signal = this._controller.signal;\n\n document.addEventListener('mousemove', this.onMouseMove, { capture: true, signal });\n document.addEventListener('click', this.onClick, { capture: true, signal });\n document.addEventListener('keydown', this.onKeyDown, { capture: true, signal });\n document.addEventListener('scroll', this.onScroll, { capture: true, signal });\n }\n\n /**\n * Deactivate the picker: abort controller removes all listeners atomically.\n * Safe to call even if not active.\n */\n deactivate(): void {\n if (!this._active) return;\n\n this._controller?.abort();\n this._controller = null;\n this._active = false;\n\n if (this._rafId !== null) {\n cancelAnimationFrame(this._rafId);\n this._rafId = null;\n }\n this._pendingEvent = null;\n\n this._options.highlighter.clear();\n }\n\n // ── Event handlers ──────────────────────────────────────────────────\n\n private onMouseMove = (e: MouseEvent): void => {\n this._pendingEvent = e;\n\n if (this._rafId !== null) return;\n\n this._rafId = requestAnimationFrame(() => {\n this._rafId = null;\n const evt = this._pendingEvent;\n if (!evt) return;\n\n const target = evt.target as Element | null;\n if (!target || target.nodeType !== 1) return;\n\n // Filter out overlay host and document-level elements\n if (this._options.overlayHostElement.contains(target)) return;\n if (target === document.body || target === document.documentElement) return;\n\n this._options.highlighter.highlight(target);\n });\n };\n\n private onClick = (e: MouseEvent): void => {\n // CRITICAL: Capture-phase interception prevents host app click handlers\n e.preventDefault();\n e.stopPropagation();\n\n const target = e.target as Element | null;\n\n if (!target || target.nodeType !== 1) return;\n if (this._options.overlayHostElement.contains(target)) return;\n if (!target.isConnected) return;\n\n let context: SelectionContext;\n try {\n context = extract(target);\n } catch {\n return;\n }\n\n this._options.onElementClick(target, context);\n };\n\n private onKeyDown = (e: KeyboardEvent): void => {\n if (e.key === 'Escape') {\n e.preventDefault();\n e.stopPropagation();\n this._options.onEscape();\n }\n };\n\n private onScroll = (): void => {\n if (this._rafId !== null) return;\n\n this._rafId = requestAnimationFrame(() => {\n this._rafId = null;\n const evt = this._pendingEvent;\n if (!evt) return;\n\n const target = evt.target as Element | null;\n if (target && target.isConnected && !this._options.overlayHostElement.contains(target)) {\n this._options.highlighter.highlight(target);\n }\n });\n };\n}\n","// ── KeyComboDetector — Hold-to-charge key-combo detection ────────────────\n//\n// Default combo: Ctrl+Shift+E (NOT Ctrl+Shift+F which conflicts with browser fullscreen).\n// Charge cycle: holding the combo for `holdMs` (default 2000ms) \"charges up\"\n// the activation. Progress is streamed via onChargeProgress so the UI can\n// render a radial indicator next to the cursor — this replaces the older\n// silent 500ms timer that gave users zero feedback during the hold.\n//\n// Lifecycle:\n// keys down → onChargeStart, then onChargeProgress(frac) via rAF until frac=1\n// frac = 1 → onCharged fires once\n// any key up → onChargeCancel (consumer decides what to do with downstream state)\n//\n// Multiple keydown events (browser auto-repeat) are debounced — the charge\n// only starts when the first matching keydown fires.\n\nexport interface KeyComboConfig {\n ctrl?: boolean;\n shift?: boolean;\n alt?: boolean;\n key: string;\n}\n\nexport interface KeyComboCallbacks {\n /** Fires once when the combo keys go down and a charge begins. */\n onChargeStart?: () => void;\n /** Fires on every animation frame while charging. `frac` is 0..1. */\n onChargeProgress?: (frac: number) => void;\n /** Fires once when the hold reaches the threshold. */\n onCharged?: () => void;\n /**\n * Fires when any combo key is released.\n * Also fires once after onCharged so the consumer can tear down any\n * post-charge state (e.g. exit selector mode) — distinct from the\n * mid-charge cancel case, which the consumer identifies by checking\n * its own state.\n */\n onChargeCancel?: () => void;\n}\n\nconst DEFAULT_HOLD_MS = 2000;\n\nconst DEFAULT_COMBO: KeyComboConfig = {\n ctrl: true,\n shift: true,\n key: 'e',\n};\n\nfunction parseCombo(comboStr?: string): KeyComboConfig {\n if (!comboStr) return DEFAULT_COMBO;\n\n const lower = comboStr.toLowerCase();\n const parts = lower.split('+').map((p) => p.trim());\n\n const config: KeyComboConfig = { key: '' };\n\n for (const part of parts) {\n switch (part) {\n case 'ctrl':\n case 'control':\n config.ctrl = true;\n break;\n case 'shift':\n config.shift = true;\n break;\n case 'alt':\n config.alt = true;\n break;\n default:\n config.key = part;\n }\n }\n\n if (!config.key) return DEFAULT_COMBO;\n\n return config;\n}\n\nexport class KeyComboDetector {\n private _combo: KeyComboConfig;\n private _holdMs: number;\n private _callbacks: KeyComboCallbacks;\n private _controller: AbortController | null = null;\n\n // Charge state\n private _chargeStart = 0;\n private _charging = false;\n private _charged = false;\n private _rafId: number | null = null;\n\n constructor(comboStr: string | undefined, callbacks: KeyComboCallbacks, holdMs: number = DEFAULT_HOLD_MS) {\n this._combo = parseCombo(comboStr);\n this._holdMs = holdMs;\n this._callbacks = callbacks;\n }\n\n /** Start listening for the key combo. */\n start(): void {\n if (this._controller) return;\n\n this._controller = new AbortController();\n const signal = this._controller.signal;\n\n document.addEventListener('keydown', this.onKeyDown, { capture: true, signal });\n document.addEventListener('keyup', this.onKeyUp, { capture: true, signal });\n }\n\n /** Stop listening. Cancels any in-flight charge. */\n stop(): void {\n this._controller?.abort();\n this._controller = null;\n this.cancelRaf();\n this._charging = false;\n this._charged = false;\n this._chargeStart = 0;\n }\n\n /** Check if a KeyboardEvent matches the configured combo. */\n matchesCombo(e: KeyboardEvent): boolean {\n if (this._combo.ctrl && !e.ctrlKey) return false;\n if (this._combo.shift && !e.shiftKey) return false;\n if (this._combo.alt && !e.altKey) return false;\n return e.key.toLowerCase() === this._combo.key;\n }\n\n // ── Handlers ────────────────────────────────────────────────────────\n\n private onKeyDown = (e: KeyboardEvent): void => {\n if (!this.matchesCombo(e)) return;\n // Browser auto-repeat fires keydown repeatedly while held. Only start\n // the charge on the initial press.\n if (this._charging || this._charged) return;\n\n this._charging = true;\n this._charged = false;\n this._chargeStart = performance.now();\n this._callbacks.onChargeStart?.();\n this.tick();\n };\n\n private onKeyUp = (e: KeyboardEvent): void => {\n if (!this.isComboKey(e.key.toLowerCase())) return;\n\n // Capture the mode at the moment of release so we can tell the consumer\n // whether this was a mid-charge cancel or a post-charge release.\n const wasCharging = this._charging;\n const wasCharged = this._charged;\n\n this.cancelRaf();\n this._charging = false;\n this._charged = false;\n this._chargeStart = 0;\n\n // Always fire onChargeCancel — the consumer branches on its own state\n // (e.g. setup.ts treats 'charging' and 'selecting' differently).\n // Only fire if a charge cycle actually started.\n if (wasCharging || wasCharged) {\n this._callbacks.onChargeCancel?.();\n }\n };\n\n private tick = (): void => {\n if (!this._charging) return;\n\n const elapsed = performance.now() - this._chargeStart;\n const frac = Math.min(1, elapsed / this._holdMs);\n this._callbacks.onChargeProgress?.(frac);\n\n if (frac >= 1) {\n this._charging = false;\n this._charged = true;\n this.cancelRaf();\n this._callbacks.onCharged?.();\n return;\n }\n\n this._rafId = requestAnimationFrame(this.tick);\n };\n\n private cancelRaf(): void {\n if (this._rafId !== null) {\n cancelAnimationFrame(this._rafId);\n this._rafId = null;\n }\n }\n\n private isComboKey(key: string): boolean {\n if (key === this._combo.key) return true;\n if (this._combo.ctrl && key === 'control') return true;\n if (this._combo.shift && key === 'shift') return true;\n if (this._combo.alt && key === 'alt') return true;\n return false;\n }\n}\n","// ── FloatingLogo — Draggable Synod logo with localStorage persistence ────\n//\n// Pointer Events API: pointerdown → setPointerCapture → drag.\n// Click vs. drag: <5px displacement = click → onLogoClick callback.\n// Position persisted to localStorage key 'synod-feedback-logo-pos'.\n// Badge shows queued findings count.\n\nimport { createSynodLogo } from '../utils/logo';\n\nexport interface FloatingLogoCallbacks {\n onClick: () => void;\n onCountChange?: (count: number) => void;\n /**\n * Fired when the user right-clicks the logo. Receives the viewport\n * coordinates of the click so a context menu can be anchored there.\n * The default browser context menu is suppressed.\n */\n onContextMenu?: (x: number, y: number) => void;\n}\n\nconst STORAGE_KEY = 'synod-feedback-logo-pos';\nconst CLICK_THRESHOLD = 5; // px\n\nexport class FloatingLogo {\n private _logo: HTMLDivElement | null = null;\n private _badge: HTMLDivElement | null = null;\n private _callbacks: FloatingLogoCallbacks;\n\n // Drag state\n private _dragging = false;\n private _startX = 0;\n private _startY = 0;\n private _logoX = 0;\n private _logoY = 0;\n private _moved = false;\n\n constructor(_root: ShadowRoot | null, callbacks: FloatingLogoCallbacks) {\n void _root; // retained for API compatibility\n this._callbacks = callbacks;\n }\n\n /**\n * Mount the logo into the shadow root content wrapper.\n */\n mount(wrapper: HTMLElement): void {\n if (this._logo) return;\n\n const logo = document.createElement('div');\n logo.className = 'synod-fb-logo';\n logo.appendChild(createSynodLogo(28));\n logo.setAttribute('role', 'button');\n logo.setAttribute('aria-label', 'Synod feedback — click to open queue');\n logo.setAttribute('tabindex', '0');\n\n // Restore position from localStorage or use default (bottom-right)\n const pos = this.loadPosition();\n logo.style.left = `${pos.x}px`;\n logo.style.top = `${pos.y}px`;\n\n this._logoX = pos.x;\n this._logoY = pos.y;\n\n // Badge (initially hidden)\n const badge = document.createElement('div');\n badge.className = 'synod-fb-badge';\n badge.style.display = 'none';\n badge.setAttribute('aria-live', 'polite');\n logo.appendChild(badge);\n\n // Pointer events for drag\n logo.addEventListener('pointerdown', this.onPointerDown);\n logo.addEventListener('keydown', this.onKeyDown);\n // Right-click → custom context menu (suppress browser default)\n logo.addEventListener('contextmenu', this.onContextMenu);\n\n wrapper.appendChild(logo);\n\n this._logo = logo;\n this._badge = badge;\n }\n\n /**\n * Remove the logo from the DOM.\n */\n unmount(): void {\n this._logo?.removeEventListener('pointerdown', this.onPointerDown);\n this._logo?.removeEventListener('keydown', this.onKeyDown);\n this._logo?.removeEventListener('contextmenu', this.onContextMenu);\n this._logo?.remove();\n this._logo = null;\n this._badge = null;\n }\n\n /**\n * Update the badge count.\n */\n updateCount(count: number): void {\n if (!this._badge || !this._logo) return;\n\n if (count > 0) {\n this._badge.style.display = 'flex';\n this._badge.textContent = String(count);\n this._badge.setAttribute('aria-label', `${count} ${count === 1 ? 'finding' : 'findings'} queued`);\n\n this._badge.classList.remove('synod-fb-logo-badge-pulse');\n void this._badge.offsetWidth;\n this._badge.classList.add('synod-fb-logo-badge-pulse');\n } else {\n this._badge.style.display = 'none';\n }\n\n this._callbacks.onCountChange?.(count);\n }\n\n // ── Drag handlers ───────────────────────────────────────────────────\n\n private onPointerDown = (e: PointerEvent): void => {\n if (!this._logo) return;\n\n e.preventDefault();\n e.stopPropagation();\n\n this._dragging = true;\n this._moved = false;\n this._startX = e.clientX;\n this._startY = e.clientY;\n\n try {\n this._logo.setPointerCapture(e.pointerId);\n } catch {\n // jsdom or older browsers may not support setPointerCapture\n }\n this._logo.addEventListener('pointermove', this.onPointerMove);\n this._logo.addEventListener('pointerup', this.onPointerUp);\n this._logo.addEventListener('pointercancel', this.onPointerUp);\n };\n\n private onPointerMove = (e: PointerEvent): void => {\n if (!this._dragging || !this._logo) return;\n\n const dx = e.clientX - this._startX;\n const dy = e.clientY - this._startY;\n\n if (!this._moved && Math.abs(dx) + Math.abs(dy) > CLICK_THRESHOLD) {\n this._moved = true;\n }\n\n if (!this._moved) return;\n\n const newX = this._logoX + dx;\n const newY = this._logoY + dy;\n\n const clampedX = Math.max(8, Math.min(newX, window.innerWidth - 56));\n const clampedY = Math.max(8, Math.min(newY, window.innerHeight - 56));\n\n this._logo.style.left = `${clampedX}px`;\n this._logo.style.top = `${clampedY}px`;\n };\n\n private onPointerUp = (e: PointerEvent): void => {\n if (!this._logo) return;\n\n this._dragging = false;\n\n try {\n this._logo.releasePointerCapture(e.pointerId);\n } catch {\n // ignore if not captured\n }\n\n this._logo.removeEventListener('pointermove', this.onPointerMove);\n this._logo.removeEventListener('pointerup', this.onPointerUp);\n this._logo.removeEventListener('pointercancel', this.onPointerUp);\n\n if (this._moved) {\n const left = parseFloat(this._logo.style.left || '0');\n const top = parseFloat(this._logo.style.top || '0');\n this._logoX = left;\n this._logoY = top;\n this.savePosition(left, top);\n } else {\n this._callbacks.onClick();\n }\n };\n\n private onKeyDown = (e: KeyboardEvent): void => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n this._callbacks.onClick();\n }\n };\n\n private onContextMenu = (e: MouseEvent): void => {\n // Always suppress the browser's native context menu so the host page\n // doesn't show \"Save image / Inspect / ...\" on top of our logo. The\n // consumer's onContextMenu callback (if any) decides whether to show\n // our custom menu.\n e.preventDefault();\n e.stopPropagation();\n this._callbacks.onContextMenu?.(e.clientX, e.clientY);\n };\n\n // ── localStorage ────────────────────────────────────────────────────\n\n private loadPosition(): { x: number; y: number } {\n try {\n const raw = localStorage.getItem(STORAGE_KEY);\n if (raw) {\n const pos = JSON.parse(raw);\n if (typeof pos.x === 'number' && typeof pos.y === 'number') {\n return pos;\n }\n }\n } catch {\n // ignore\n }\n\n return {\n x: Math.max(8, window.innerWidth - 64),\n y: Math.max(8, window.innerHeight - 64),\n };\n }\n\n private savePosition(x: number, y: number): void {\n try {\n localStorage.setItem(STORAGE_KEY, JSON.stringify({ x, y }));\n } catch {\n // QuotaExceededError — silently fail\n }\n }\n}\n","// ── Viewport flip positioning utility ────────────────────────────────────\n\nexport interface DOMRect {\n left: number;\n top: number;\n width: number;\n height: number;\n}\n\nexport interface ElementSize {\n width: number;\n height: number;\n}\n\nexport interface Viewport {\n innerWidth: number;\n innerHeight: number;\n}\n\nexport interface PositionedRect {\n left: number;\n top: number;\n}\n\nexport const MIN_MARGIN = 8;\n\n/**\n * Calculate the optimal position for a popup relative to a target element rect.\n *\n * Default: position below the element (top = rect.bottom + 8).\n * Vertical flip: if not enough space below, position above.\n * Horizontal clamp: if right edge overflows, shift left.\n * Minimum margin of 8px from viewport edges.\n *\n * @param targetRect Bounding rect of the selected element\n * @param elementSize Width/height of the popup to position\n * @param viewport Viewport dimensions\n * @param margin Gap between element and popup (default 8px)\n */\nexport function calculateFlippedPosition(\n targetRect: DOMRect,\n elementSize: ElementSize,\n viewport: Viewport,\n margin: number = MIN_MARGIN,\n): PositionedRect {\n const rectBottom = targetRect.top + targetRect.height;\n const spaceBelow = viewport.innerHeight - rectBottom;\n\n let top: number;\n\n // Vertical flip: default below, flip above if not enough space\n if (spaceBelow < elementSize.height + margin) {\n // Not enough space below → place above\n top = targetRect.top - elementSize.height - margin;\n } else {\n // Default: below\n top = rectBottom + margin;\n }\n\n // Horizontal: default align to element left\n let left = targetRect.left;\n\n // Horizontal clamp: shift left if right edge overflows\n if (left + elementSize.width + margin > viewport.innerWidth) {\n left = viewport.innerWidth - elementSize.width - margin * 2;\n }\n\n // Clamp to minimum margin\n top = Math.max(top, MIN_MARGIN);\n left = Math.max(left, MIN_MARGIN);\n\n return { left, top };\n}\n","// ── Screenshot capture — lazy-loaded via modern-screenshot ────────────────\n//\n// Optional/best-effort: dynamic import('modern-screenshot'), compressed to\n// ≤32KB thumbnail. Cross-origin tainting is caught gracefully.\n\n/**\n * Maximum acceptable data URL size (32KB).\n *\n * Previously 8KB to fit inside a 10KB context_json envelope for the old\n * local-findings gateway flow. The self-feedback path now routes through\n * agents-core → synod-web, neither of which imposes a small body limit,\n * and the SDK's MAX_CONTEXT_JSON_BYTES was raised to 64KB in lockstep.\n */\nexport const MAX_SCREENSHOT_BYTES = 32_768;\n\n/**\n * Capture a screenshot of an element as a compressed JPEG data URL.\n *\n * Uses dynamic import of modern-screenshot (~40KB, lazy-loaded).\n * Compresses to 200px thumbnail at JPEG quality 0.4.\n *\n * @returns data URL string if successful and ≤32KB, or null on failure/oversize.\n */\nexport async function capture(element: Element): Promise<string | null> {\n try {\n const mod = await import('modern-screenshot');\n const domToJpeg = (mod as any).domToJpeg ?? (mod as any).default?.domToJpeg;\n\n if (typeof domToJpeg !== 'function') {\n console.warn('[synod-feedback] Screenshot: domToJpeg not found in modern-screenshot module');\n return null;\n }\n\n const dataUrl: string = await domToJpeg(element, { quality: 0.4, width: 200 });\n\n if (typeof dataUrl !== 'string' || dataUrl.length === 0) {\n console.warn('[synod-feedback] Screenshot: domToJpeg returned invalid data');\n return null;\n }\n\n // Size gate: omit if > 32KB (no retry, best-effort)\n if (dataUrl.length > MAX_SCREENSHOT_BYTES) {\n console.warn(\n `[synod-feedback] Screenshot: data URL too large (${dataUrl.length} bytes > ${MAX_SCREENSHOT_BYTES} limit), omitting`,\n );\n return null;\n }\n\n return dataUrl;\n } catch (err) {\n console.warn('[synod-feedback] Screenshot capture failed:', err);\n return null;\n }\n}\n","// ── IssueForm — Inline capture panel with viewport flip ──────────────────\n//\n// Positioned relative to selected element using calculateFlippedPosition().\n// Focus trap: Tab cycles within form fields. Esc cancels.\n// Buttons: \"Add to tray\" (queue + return to selecting), \"Send now\" (single send).\n// Screenshot toggle: off by default, triggers lazy screenshot capture.\n\nimport type { FindingSeverity, FindingContextJson } from '../client/types';\nimport type { SelectionContext } from '../interaction/types';\nimport { calculateFlippedPosition } from '../utils/viewport';\nimport { capture as captureScreenshot } from '../utils/screenshot';\nimport { createSynodLogo } from '../utils/logo';\n\nexport interface IssueFormResult {\n description: string;\n severity: FindingSeverity;\n contextJson: FindingContextJson;\n}\n\nexport interface IssueFormCallbacks {\n onAddToTray: (result: IssueFormResult) => void;\n onSendNow: (result: IssueFormResult) => void;\n onCancel: () => void;\n}\n\nconst FORM_WIDTH = 340;\n\nexport class IssueForm {\n private _wrapper: HTMLElement;\n private _callbacks: IssueFormCallbacks;\n private _formEl: HTMLDivElement | null = null;\n private _currentContext: SelectionContext | null = null;\n private _screenshotDataUrl: string | null = null;\n private _screenshotElement: Element | null = null;\n private _previouslyFocused: Element | null = null;\n private _descTextarea: HTMLTextAreaElement | null = null;\n private _severity: FindingSeverity = 'medium';\n private _screenshotToggle: HTMLInputElement | null = null;\n private _screenshotLabel: HTMLLabelElement | null = null;\n\n constructor(wrapper: HTMLElement, callbacks: IssueFormCallbacks) {\n this._wrapper = wrapper;\n this._callbacks = callbacks;\n }\n\n show(context: SelectionContext, element?: Element): void {\n this.hide();\n\n this._currentContext = context;\n this._screenshotElement = element ?? null;\n this._screenshotDataUrl = null;\n this._severity = 'medium';\n\n this._previouslyFocused = document.activeElement;\n\n const form = this.buildForm();\n this._formEl = form;\n\n const estimatedHeight = 380;\n const pos = calculateFlippedPosition(\n context.boundingRect,\n { width: FORM_WIDTH, height: estimatedHeight },\n { innerWidth: window.innerWidth, innerHeight: window.innerHeight },\n );\n\n form.style.left = `${pos.left}px`;\n form.style.top = `${pos.top}px`;\n\n this._wrapper.appendChild(form);\n\n requestAnimationFrame(() => {\n if (!this._formEl) return;\n const actualHeight = this._formEl.offsetHeight;\n if (Math.abs(actualHeight - estimatedHeight) > 20) {\n const adjustedPos = calculateFlippedPosition(\n context.boundingRect,\n { width: FORM_WIDTH, height: actualHeight },\n { innerWidth: window.innerWidth, innerHeight: window.innerHeight },\n );\n this._formEl.style.left = `${adjustedPos.left}px`;\n this._formEl.style.top = `${adjustedPos.top}px`;\n }\n });\n\n requestAnimationFrame(() => {\n this._descTextarea?.focus();\n });\n\n form.addEventListener('keydown', this.onKeyDown);\n }\n\n hide(): void {\n this._formEl?.removeEventListener('keydown', this.onKeyDown);\n this._formEl?.remove();\n this._formEl = null;\n this._currentContext = null;\n this._screenshotDataUrl = null;\n this._screenshotElement = null;\n\n if (this._previouslyFocused && this._previouslyFocused.isConnected) {\n (this._previouslyFocused as HTMLElement).focus?.();\n }\n this._previouslyFocused = null;\n }\n\n get isVisible(): boolean {\n return this._formEl !== null && this._formEl.isConnected;\n }\n\n private buildForm(): HTMLDivElement {\n const form = document.createElement('div');\n form.className = 'synod-fb-form';\n form.setAttribute('role', 'dialog');\n form.setAttribute('aria-modal', 'true');\n form.setAttribute('aria-label', 'Describe issue');\n\n // Header\n const header = document.createElement('div');\n header.className = 'synod-fb-form-header';\n\n const headerLogo = document.createElement('div');\n headerLogo.className = 'synod-fb-form-header-logo';\n headerLogo.appendChild(createSynodLogo(16));\n\n const headerText = document.createElement('div');\n headerText.className = 'synod-fb-form-header-text';\n // Build a compact tag#id.class descriptor for the header subtitle — the\n // previous CSS-selector snippet was nth-of-type noise that wasn't useful\n // even as a visual identifier.\n const ctx = this._currentContext!;\n const id = ctx.elementAttributes?.['id'];\n const cls = ctx.elementAttributes?.['class']?.trim();\n let descriptor = ctx.tagName;\n if (id) descriptor += `#${id}`;\n if (cls) descriptor += '.' + cls.split(/\\s+/).join('.');\n if (descriptor.length > 50) descriptor = descriptor.substring(0, 49) + '…';\n headerText.textContent = `${descriptor} · ${ctx.boundingRect.width}×${ctx.boundingRect.height}`;\n\n const closeBtn = document.createElement('button');\n closeBtn.className = 'synod-fb-form-close';\n closeBtn.textContent = '×';\n closeBtn.setAttribute('aria-label', 'Cancel');\n closeBtn.addEventListener('click', () => this._callbacks.onCancel());\n\n header.appendChild(headerLogo);\n header.appendChild(headerText);\n header.appendChild(closeBtn);\n\n // Body\n const body = document.createElement('div');\n body.className = 'synod-fb-form-body';\n\n // Description field (title is AI-generated from this — see setup.ts)\n const descField = document.createElement('div');\n descField.className = 'synod-fb-field';\n const descLabel = document.createElement('label');\n descLabel.className = 'synod-fb-label';\n descLabel.textContent = 'Description *';\n descLabel.setAttribute('for', 'synod-fb-desc');\n const descTextarea = document.createElement('textarea');\n descTextarea.className = 'synod-fb-textarea';\n descTextarea.id = 'synod-fb-desc';\n descTextarea.rows = 3;\n descTextarea.maxLength = 2000;\n descTextarea.setAttribute('aria-label', 'Issue description');\n descTextarea.setAttribute('placeholder', 'Describe what’s wrong…');\n descTextarea.setAttribute('required', '');\n this._descTextarea = descTextarea;\n descField.appendChild(descLabel);\n descField.appendChild(descTextarea);\n\n // Severity segmented control\n const sevField = document.createElement('div');\n sevField.className = 'synod-fb-field';\n const sevLabel = document.createElement('div');\n sevLabel.className = 'synod-fb-label';\n sevLabel.textContent = 'Severity';\n const sevGroup = document.createElement('div');\n sevGroup.className = 'synod-fb-severity-group';\n sevGroup.setAttribute('role', 'group');\n sevGroup.setAttribute('aria-label', 'Severity');\n\n const severities: FindingSeverity[] = ['low', 'medium', 'high'];\n for (const sev of severities) {\n const btn = document.createElement('button');\n btn.className = 'synod-fb-sev-btn';\n btn.dataset.sev = sev;\n btn.textContent = sev.charAt(0).toUpperCase() + sev.slice(1);\n btn.setAttribute('aria-pressed', sev === this._severity ? 'true' : 'false');\n if (sev === this._severity) btn.classList.add('active');\n btn.addEventListener('click', (e) => {\n e.preventDefault();\n this._severity = sev;\n sevGroup.querySelectorAll('.synod-fb-sev-btn').forEach((b) => {\n const btnSev = (b as HTMLButtonElement).dataset.sev;\n b.setAttribute('aria-pressed', btnSev === sev ? 'true' : 'false');\n b.classList.toggle('active', btnSev === sev);\n });\n });\n btn.addEventListener('keydown', (e) => {\n if (e.key === 'ArrowRight' || e.key === 'ArrowLeft') {\n e.preventDefault();\n const btns = Array.from(sevGroup.querySelectorAll('.synod-fb-sev-btn')) as HTMLButtonElement[];\n const idx = btns.indexOf(e.currentTarget as HTMLButtonElement);\n const nextIdx = e.key === 'ArrowRight'\n ? (idx + 1) % btns.length\n : (idx - 1 + btns.length) % btns.length;\n btns[nextIdx].click();\n btns[nextIdx].focus();\n }\n });\n sevGroup.appendChild(btn);\n }\n sevField.appendChild(sevLabel);\n sevField.appendChild(sevGroup);\n\n // Screenshot toggle\n const ssField = document.createElement('div');\n ssField.className = 'synod-fb-field';\n const ssToggle = document.createElement('label');\n ssToggle.className = 'synod-fb-screenshot-toggle';\n const ssCheckbox = document.createElement('input');\n ssCheckbox.type = 'checkbox';\n ssCheckbox.addEventListener('change', () => this.onScreenshotToggle(ssCheckbox.checked));\n this._screenshotToggle = ssCheckbox;\n const ssLabel = document.createElement('span');\n ssLabel.textContent = '📷 Include screenshot';\n this._screenshotLabel = ssToggle;\n ssToggle.appendChild(ssCheckbox);\n ssToggle.appendChild(ssLabel);\n ssField.appendChild(ssToggle);\n\n // Action buttons\n const actions = document.createElement('div');\n actions.className = 'synod-fb-form-actions';\n const addBtn = document.createElement('button');\n addBtn.className = 'synod-fb-btn-secondary';\n addBtn.textContent = '+ Add to tray';\n addBtn.addEventListener('click', (e) => {\n e.preventDefault();\n this.handleSubmit('add');\n });\n\n const sendBtn = document.createElement('button');\n sendBtn.className = 'synod-fb-btn-primary';\n sendBtn.textContent = 'Send now';\n sendBtn.addEventListener('click', (e) => {\n e.preventDefault();\n this.handleSubmit('send');\n });\n\n actions.appendChild(addBtn);\n actions.appendChild(sendBtn);\n\n body.appendChild(descField);\n body.appendChild(sevField);\n body.appendChild(ssField);\n body.appendChild(actions);\n\n form.appendChild(header);\n form.appendChild(body);\n\n return form;\n }\n\n private async onScreenshotToggle(checked: boolean): Promise<void> {\n if (!checked) {\n this._screenshotDataUrl = null;\n return;\n }\n\n if (!this._screenshotElement) {\n this.markScreenshotUnavailable();\n return;\n }\n\n if (this._screenshotLabel) {\n const labelSpan = this._screenshotLabel.querySelector('span');\n if (labelSpan) labelSpan.textContent = '📷 Capturing…';\n }\n\n const dataUrl = await captureScreenshot(this._screenshotElement);\n\n if (dataUrl) {\n this._screenshotDataUrl = dataUrl;\n if (this._screenshotLabel) {\n const labelSpan = this._screenshotLabel.querySelector('span');\n if (labelSpan) labelSpan.textContent = '📷 Screenshot captured ✓';\n }\n } else {\n this.markScreenshotUnavailable();\n }\n }\n\n private markScreenshotUnavailable(): void {\n this._screenshotDataUrl = null;\n if (this._screenshotToggle) {\n this._screenshotToggle.checked = false;\n this._screenshotToggle.disabled = true;\n }\n if (this._screenshotLabel) {\n this._screenshotLabel.classList.add('disabled', 'unavailable');\n const labelSpan = this._screenshotLabel.querySelector('span');\n if (labelSpan) labelSpan.textContent = '⚠ Screenshot unavailable';\n }\n }\n\n private handleSubmit(action: 'add' | 'send'): void {\n if (!this._currentContext || !this._descTextarea) return;\n\n const description = this._descTextarea.value.trim();\n if (!description) return;\n\n const contextJson: FindingContextJson = {\n element_text: this._currentContext.elementText,\n element_html: this._currentContext.elementHtml,\n element_attributes: this._currentContext.elementAttributes,\n parent_chain: this._currentContext.parentChain,\n url: typeof window !== 'undefined' ? window.location.href : undefined,\n // Best-effort route — pathname + hash + search captures the SPA \"screen\"\n // without re-sending the origin (already in url).\n route: typeof window !== 'undefined'\n ? window.location.pathname + window.location.search + window.location.hash\n : undefined,\n page_title: typeof document !== 'undefined' ? document.title : undefined,\n viewport: typeof window !== 'undefined'\n ? { width: window.innerWidth, height: window.innerHeight }\n : undefined,\n user_agent: typeof navigator !== 'undefined' ? navigator.userAgent : undefined,\n ...(this._screenshotDataUrl ? { screenshot_data_url: this._screenshotDataUrl } : {}),\n };\n\n const result: IssueFormResult = {\n description,\n severity: this._severity,\n contextJson,\n };\n\n if (action === 'add') {\n this._callbacks.onAddToTray(result);\n } else {\n this._callbacks.onSendNow(result);\n }\n }\n\n private onKeyDown = (e: KeyboardEvent): void => {\n if (e.key === 'Escape') {\n e.preventDefault();\n e.stopPropagation();\n this._callbacks.onCancel();\n return;\n }\n\n if (e.key === 'Tab' && this._formEl) {\n const focusable = this.getFocusableElements();\n if (focusable.length === 0) return;\n\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n\n if (e.shiftKey) {\n if (document.activeElement === first) {\n e.preventDefault();\n last.focus();\n }\n } else {\n if (document.activeElement === last) {\n e.preventDefault();\n first.focus();\n }\n }\n }\n\n if (e.ctrlKey && e.key === 'Enter') {\n e.preventDefault();\n this.handleSubmit(e.shiftKey ? 'send' : 'add');\n }\n };\n\n private getFocusableElements(): HTMLElement[] {\n if (!this._formEl) return [];\n return Array.from(\n this._formEl.querySelectorAll<HTMLElement>(\n 'input:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex]:not([tabindex=\"-1\"])',\n ),\n );\n }\n}\n","// ── Title helpers — pending-state + queue display utilities ──────────────\n//\n// The browser SDK no longer generates titles itself — that happens\n// server-side on finding receive. These helpers just let the queue UI\n// distinguish \"title pending\" (empty placeholder) and present a readable\n// snippet of the description where the title would normally appear.\n\n/**\n * Sentinel value used in FindingPayload.title while no title has been\n * assigned yet. The gateway replaces this with a real title on receive.\n */\nexport const PENDING_TITLE = '';\n\n/**\n * True when the finding's title is still awaiting server-side generation.\n */\nexport function isTitlePending(title: string | undefined | null): boolean {\n return !title || title.trim() === '';\n}\n\n/**\n * Short single-line snippet of a description, suitable for queue row labels\n * when no title exists yet. Collapses whitespace, truncates at ~60 chars\n * on a word boundary with an ellipsis.\n */\nexport function truncDesc(description: string): string {\n const trimmed = (description ?? '').trim().replace(/\\s+/g, ' ');\n if (trimmed.length <= 60) return trimmed;\n\n const slice = trimmed.slice(0, 57);\n const lastSpace = slice.lastIndexOf(' ');\n const head = lastSpace > 25 ? slice.slice(0, lastSpace) : slice;\n return head + '…';\n}\n","// ── Clipboard codec for offline finding transfer ────────────────────────\n//\n// Used by the extension's \"Copy to clipboard\" queue button and by the\n// Synod Desktop Findings panel \"Paste from clipboard\" icon. Lets a user\n// capture findings on a machine that can't reach the gateway (e.g. a\n// stakeholder's laptop at a customer site) and bring them back to the\n// desktop app for ingestion.\n//\n// Format: a single string prefixed `synod-fb-v1:` followed by the URL-safe\n// base64 encoding of a UTF-8 JSON payload. We don't ship raw JSON because\n// (a) it's friendlier for the user if they accidentally paste it somewhere,\n// (b) base64 survives clipboard managers / rich-text editors that mangle\n// whitespace and quote characters, and (c) the prefix lets the desktop\n// validate the blob cheaply before attempting to decode.\n//\n// Screenshots are stripped on encode — they are too large for the\n// clipboard and don't survive the round-trip reliably.\n\nimport type { FindingPayload, FindingSeverity } from '../client/types';\n\nconst PREFIX = 'synod-fb-v1:';\nconst FORMAT_VERSION = 1;\n\ninterface ClipboardPayload {\n v: number;\n findings: FindingPayload[];\n}\n\n/**\n * Strip the screenshot_data_url field from a copy of the context_json.\n * Returns the original object if there's no screenshot (no copy needed).\n */\nfunction withoutScreenshot(payload: FindingPayload): FindingPayload {\n if (!payload.context_json?.screenshot_data_url) return payload;\n const { screenshot_data_url: _drop, ...rest } = payload.context_json;\n return { ...payload, context_json: rest };\n}\n\n/**\n * URL-safe base64 encoder that handles Unicode (UTF-8 → binary string → base64).\n * Browser-only: the desktop UI has its own mirror (findingsClipboard.js)\n * that uses the same TextEncoder/TextDecoder + atob/btoa pattern.\n */\nfunction encodeBase64(unicodeStr: string): string {\n const utf8 = new TextEncoder().encode(unicodeStr);\n // Convert bytes to a binary string for btoa()\n let binary = '';\n for (const b of utf8) binary += String.fromCharCode(b);\n const standard = btoa(binary);\n // URL-safe variants for clipboard resilience\n return standard.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\nfunction decodeBase64(urlSafe: string): string {\n const standard = urlSafe.replace(/-/g, '+').replace(/_/g, '/');\n const padded = standard + '='.repeat((4 - (standard.length % 4)) % 4);\n const binary = atob(padded);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);\n return new TextDecoder().decode(bytes);\n}\n\n/**\n * Encode an array of findings into the clipboard transfer format.\n * Screenshots are stripped from every finding's context_json.\n *\n * Empty arrays still encode (returns the prefix + empty-list base64)\n * so consumers can distinguish \"Synod clipboard blob with no items\" from\n * \"not a Synod clipboard blob at all\".\n */\nexport function encodeFindingsToClipboard(findings: FindingPayload[]): string {\n const stripped = findings.map(withoutScreenshot);\n const payload: ClipboardPayload = { v: FORMAT_VERSION, findings: stripped };\n const json = JSON.stringify(payload);\n return PREFIX + encodeBase64(json);\n}\n\n/**\n * Decode a clipboard string back into findings.\n *\n * Returns `{ ok: true, findings }` on success, or `{ ok: false, error }`\n * if the input is not a Synod findings blob, the JSON is malformed, the\n * version is unsupported, or any finding has an invalid shape.\n *\n * On any decode error the consumer should fall back to plain-text paste\n * behaviour (or just show an \"invalid clipboard\" toast).\n */\nexport function decodeFindingsFromClipboard(\n raw: string,\n): { ok: true; findings: FindingPayload[] } | { ok: false; error: string } {\n if (typeof raw !== 'string' || raw.length === 0) {\n return { ok: false, error: 'Empty clipboard' };\n }\n const trimmed = raw.trim();\n if (!trimmed.startsWith(PREFIX)) {\n return { ok: false, error: 'Not a Synod findings blob (missing prefix)' };\n }\n const b64 = trimmed.slice(PREFIX.length);\n\n let json: string;\n try {\n json = decodeBase64(b64);\n } catch {\n return { ok: false, error: 'Malformed base64' };\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(json);\n } catch {\n return { ok: false, error: 'Malformed JSON payload' };\n }\n\n if (!parsed || typeof parsed !== 'object') {\n return { ok: false, error: 'Payload is not an object' };\n }\n const obj = parsed as Partial<ClipboardPayload>;\n if (obj.v !== FORMAT_VERSION) {\n return { ok: false, error: `Unsupported format version ${obj.v}` };\n }\n if (!Array.isArray(obj.findings)) {\n return { ok: false, error: 'Payload.findings is not an array' };\n }\n\n const validSeverities: FindingSeverity[] = ['low', 'medium', 'high'];\n const findings: FindingPayload[] = [];\n for (let i = 0; i < obj.findings.length; i++) {\n const f = obj.findings[i] as Partial<FindingPayload>;\n if (typeof f?.description !== 'string' || f.description.trim() === '') {\n return { ok: false, error: `Finding[${i}] missing description` };\n }\n if (typeof f.severity !== 'string' || !validSeverities.includes(f.severity as FindingSeverity)) {\n return { ok: false, error: `Finding[${i}] has invalid severity \"${f.severity}\"` };\n }\n if (!f.context_json || typeof f.context_json !== 'object') {\n return { ok: false, error: `Finding[${i}] missing context_json` };\n }\n findings.push({\n title: typeof f.title === 'string' ? f.title : '',\n description: f.description,\n severity: f.severity as FindingSeverity,\n context_json: f.context_json,\n ...(f.suggested_action ? { suggested_action: f.suggested_action } : {}),\n });\n }\n\n return { ok: true, findings };\n}\n","// ── QueuePanel — Batch review drawer (list/edit/delete/send) ─────────────\n//\n// Shows pending findings in a sliding drawer.\n// Per-item: thumbnail, title, selector+severity, edit, delete.\n// Send batch → sendBatch() → partial success surfacing.\n// Toast notifications for results.\n\nimport type {\n FindingPayload,\n FindingSeverity,\n BatchResult,\n ClientError,\n} from '../client/types';\nimport { createSynodLogo } from '../utils/logo';\nimport { isTitlePending, truncDesc } from '../utils/title';\nimport { encodeFindingsToClipboard } from '../utils/clipboard';\n\nexport interface QueuePanelCallbacks {\n getQueueSnapshot: () => FindingPayload[];\n onSendBatch: () => Promise<BatchResult>;\n onClearAll: () => void;\n onRemoveItem: (index: number) => void;\n onEditItem: (index: number, title: string, description: string, severity: FindingSeverity) => void;\n /**\n * Optional: copy all queued findings to the clipboard in the Synod\n * findings transfer format. If omitted, the panel uses the default\n * navigator.clipboard.writeText implementation.\n */\n onCopyToClipboard?: (findings: FindingPayload[]) => Promise<boolean>;\n}\n\nexport class QueuePanel {\n private _wrapper: HTMLElement;\n private _callbacks: QueuePanelCallbacks;\n private _panelEl: HTMLDivElement | null = null;\n private _bodyEl: HTMLDivElement | null = null;\n private _bannerEl: HTMLDivElement | null = null;\n private _sendBtn: HTMLButtonElement | null = null;\n private _isOpen = false;\n private _toastContainer: HTMLDivElement | null = null;\n\n constructor(wrapper: HTMLElement, callbacks: QueuePanelCallbacks) {\n this._wrapper = wrapper;\n this._callbacks = callbacks;\n }\n\n get isOpen(): boolean {\n return this._isOpen;\n }\n\n /**\n * Open the queue panel drawer.\n */\n show(): void {\n if (this._isOpen) return;\n\n const panel = this.buildPanel();\n this._panelEl = panel;\n this._wrapper.appendChild(panel);\n this._isOpen = true;\n\n this.renderItems();\n }\n\n /**\n * Close the queue panel drawer.\n */\n hide(): void {\n this._panelEl?.remove();\n this._panelEl = null;\n this._bodyEl = null;\n this._bannerEl = null;\n this._sendBtn = null;\n this._isOpen = false;\n }\n\n /**\n * Refresh item list (call after queue changes).\n */\n refresh(): void {\n if (!this._isOpen) return;\n this.renderItems();\n }\n\n // ── Build DOM ───────────────────────────────────────────────────────\n\n private buildPanel(): HTMLDivElement {\n const panel = document.createElement('div');\n panel.className = 'synod-fb-queue';\n panel.setAttribute('role', 'dialog');\n panel.setAttribute('aria-modal', 'true');\n panel.setAttribute('aria-label', 'Pending findings review');\n\n // Header\n const header = document.createElement('div');\n header.className = 'synod-fb-queue-header';\n const headerLogo = document.createElement('div');\n headerLogo.className = 'synod-fb-queue-header-logo';\n headerLogo.appendChild(createSynodLogo(16));\n const title = document.createElement('div');\n title.className = 'synod-fb-queue-title';\n title.textContent = 'Pending Findings';\n const closeBtn = document.createElement('button');\n closeBtn.className = 'synod-fb-icon-btn';\n closeBtn.textContent = '×';\n closeBtn.setAttribute('aria-label', 'Close queue panel');\n closeBtn.addEventListener('click', () => this.hide());\n header.appendChild(headerLogo);\n header.appendChild(title);\n header.appendChild(closeBtn);\n\n // Banner (for errors/info)\n const banner = document.createElement('div');\n banner.className = 'synod-fb-queue-banner';\n banner.style.display = 'none';\n this._bannerEl = banner;\n\n // Body (item list)\n const body = document.createElement('div');\n body.className = 'synod-fb-queue-body';\n this._bodyEl = body;\n\n // Footer\n const footer = document.createElement('div');\n footer.className = 'synod-fb-queue-footer';\n\n const clearBtn = document.createElement('button');\n clearBtn.className = 'synod-fb-btn-danger';\n clearBtn.textContent = 'Clear all';\n clearBtn.addEventListener('click', () => this.handleClearAll());\n\n const copyBtn = document.createElement('button');\n copyBtn.className = 'synod-fb-icon-btn';\n copyBtn.setAttribute('aria-label', 'Copy all findings to clipboard');\n copyBtn.title = 'Copy to clipboard (offline transfer)';\n copyBtn.textContent = '⧉';\n copyBtn.addEventListener('click', () => this.handleCopyToClipboard());\n\n const sendBtn = document.createElement('button');\n sendBtn.className = 'synod-fb-btn-primary';\n sendBtn.textContent = 'Send batch';\n sendBtn.addEventListener('click', () => this.handleSendBatch());\n this._sendBtn = sendBtn;\n\n footer.appendChild(clearBtn);\n footer.appendChild(copyBtn);\n footer.appendChild(sendBtn);\n\n panel.appendChild(header);\n panel.appendChild(banner);\n panel.appendChild(body);\n panel.appendChild(footer);\n\n // Esc handler\n panel.addEventListener('keydown', (e) => {\n if (e.key === 'Escape') {\n e.preventDefault();\n this.hide();\n }\n });\n\n return panel;\n }\n\n private renderItems(): void {\n if (!this._bodyEl) return;\n\n const items = this._callbacks.getQueueSnapshot();\n\n // Clear body\n this._bodyEl.innerHTML = '';\n\n if (items.length === 0) {\n const empty = document.createElement('div');\n empty.className = 'synod-fb-queue-empty';\n empty.textContent = 'No findings queued yet. Click an element to start capturing.';\n this._bodyEl.appendChild(empty);\n\n if (this._sendBtn) {\n this._sendBtn.textContent = 'Send batch';\n this._sendBtn.disabled = true;\n }\n return;\n }\n\n if (this._sendBtn) {\n this._sendBtn.textContent = `Send batch (${items.length})`;\n this._sendBtn.disabled = false;\n }\n\n items.forEach((item, index) => {\n const row = this.buildItemRow(item, index);\n this._bodyEl!.appendChild(row);\n });\n }\n\n private buildItemRow(item: FindingPayload, index: number): HTMLDivElement {\n const row = document.createElement('div');\n row.className = 'synod-fb-queue-item';\n row.dataset.index = String(index);\n\n // Thumbnail\n const thumb = document.createElement('div');\n thumb.className = 'synod-fb-queue-item-thumb';\n if (item.context_json.screenshot_data_url) {\n const img = document.createElement('img');\n img.src = item.context_json.screenshot_data_url;\n img.alt = 'Screenshot';\n thumb.appendChild(img);\n }\n\n // Content\n const content = document.createElement('div');\n content.className = 'synod-fb-queue-item-content';\n\n const titleEl = document.createElement('div');\n titleEl.className = 'synod-fb-queue-item-title';\n if (isTitlePending(item.title)) {\n // Title is generated server-side on receive — until then show a\n // snippet of the description so the row is readable in the queue.\n titleEl.classList.add('synod-fb-pending');\n titleEl.textContent = truncDesc(item.description) || '(untitled)';\n } else {\n titleEl.textContent = item.title;\n }\n\n const metaEl = document.createElement('div');\n metaEl.className = 'synod-fb-queue-item-meta';\n const selectorSnippet = (item.context_json.css_selector || '').substring(0, 40);\n metaEl.textContent = `${selectorSnippet} · ${item.severity}`;\n\n content.appendChild(titleEl);\n content.appendChild(metaEl);\n\n // Actions\n const actions = document.createElement('div');\n actions.className = 'synod-fb-queue-item-actions';\n\n const editBtn = document.createElement('button');\n editBtn.className = 'synod-fb-icon-btn';\n editBtn.textContent = '✎';\n editBtn.setAttribute('aria-label', `Edit finding ${index + 1}`);\n editBtn.addEventListener('click', (e) => {\n e.stopPropagation();\n this.toggleEditRow(row, item, index);\n });\n\n const deleteBtn = document.createElement('button');\n deleteBtn.className = 'synod-fb-icon-btn';\n deleteBtn.textContent = '✕';\n deleteBtn.setAttribute('aria-label', `Delete finding ${index + 1}`);\n deleteBtn.addEventListener('click', (e) => {\n e.stopPropagation();\n this._callbacks.onRemoveItem(index);\n this.refresh();\n });\n\n actions.appendChild(editBtn);\n actions.appendChild(deleteBtn);\n\n row.appendChild(thumb);\n row.appendChild(content);\n row.appendChild(actions);\n\n return row;\n }\n\n private toggleEditRow(row: HTMLDivElement, item: FindingPayload, index: number): void {\n // Toggle: if already editing, collapse; otherwise expand\n const existingEdit = row.querySelector('.synod-fb-edit-form');\n if (existingEdit) {\n existingEdit.remove();\n return;\n }\n\n const editForm = document.createElement('div');\n editForm.className = 'synod-fb-edit-form';\n editForm.style.padding = '8px 0 4px';\n\n const titleInput = document.createElement('input');\n titleInput.className = 'synod-fb-input';\n titleInput.value = item.title;\n titleInput.style.marginBottom = '4px';\n\n const descInput = document.createElement('textarea');\n descInput.className = 'synod-fb-textarea';\n descInput.value = item.description;\n descInput.rows = 2;\n descInput.style.marginBottom = '4px';\n\n const sevGroup = document.createElement('div');\n sevGroup.className = 'synod-fb-severity-group';\n sevGroup.setAttribute('role', 'group');\n\n let editSeverity: FindingSeverity = item.severity;\n const severities: FindingSeverity[] = ['low', 'medium', 'high'];\n for (const sev of severities) {\n const btn = document.createElement('button');\n btn.className = 'synod-fb-sev-btn';\n btn.dataset.sev = sev;\n btn.textContent = sev.charAt(0).toUpperCase() + sev.slice(1);\n btn.setAttribute('aria-pressed', sev === editSeverity ? 'true' : 'false');\n if (sev === editSeverity) btn.classList.add('active');\n btn.addEventListener('click', (e) => {\n e.preventDefault();\n editSeverity = sev;\n sevGroup.querySelectorAll('.synod-fb-sev-btn').forEach((b) => {\n const bSev = (b as HTMLButtonElement).dataset.sev;\n b.setAttribute('aria-pressed', bSev === sev ? 'true' : 'false');\n b.classList.toggle('active', bSev === sev);\n });\n });\n sevGroup.appendChild(btn);\n }\n\n const saveBtn = document.createElement('button');\n saveBtn.className = 'synod-fb-btn-primary';\n saveBtn.textContent = 'Save';\n saveBtn.style.marginTop = '4px';\n saveBtn.addEventListener('click', (e) => {\n e.preventDefault();\n this._callbacks.onEditItem(index, titleInput.value.trim(), descInput.value.trim(), editSeverity);\n this.refresh();\n });\n\n editForm.appendChild(titleInput);\n editForm.appendChild(descInput);\n editForm.appendChild(sevGroup);\n editForm.appendChild(saveBtn);\n\n row.appendChild(editForm);\n titleInput.focus();\n }\n\n // ── Actions ─────────────────────────────────────────────────────────\n\n private async handleSendBatch(): Promise<void> {\n if (!this._sendBtn) return;\n\n const originalText = this._sendBtn.textContent;\n this._sendBtn.disabled = true;\n this._sendBtn.textContent = 'Sending…';\n this.hideBanner();\n\n // Dim items during send\n if (this._bodyEl) {\n this._bodyEl.style.opacity = '0.5';\n this._bodyEl.style.pointerEvents = 'none';\n }\n\n try {\n const result = await this._callbacks.onSendBatch();\n this.handleSendResult(result);\n } catch (err) {\n this.handleSendError(err as ClientError | Error);\n } finally {\n if (this._bodyEl) {\n this._bodyEl.style.opacity = '';\n this._bodyEl.style.pointerEvents = '';\n }\n if (this._sendBtn) {\n this._sendBtn.disabled = false;\n this._sendBtn.textContent = originalText || 'Send batch';\n }\n }\n }\n\n /**\n * Public entry point for callers outside QueuePanel (notably the IssueForm's\n * \"Send now\" flow in interaction/setup.ts) to surface a BatchResult through\n * the same toast/banner path as the bulk-send button. Without this, send-now\n * awaited sendBatch() silently and showed no confirmation.\n */\n reportSendResult(result: BatchResult): void {\n this.handleSendResult(result);\n }\n\n /**\n * Public entry point matching reportSendResult for the error path.\n */\n reportSendError(err: ClientError | Error): void {\n this.handleSendError(err);\n }\n\n private handleSendResult(result: BatchResult): void {\n const total = result.created.length + result.errors.length;\n\n if (result.errors.length === 0 && result.created.length > 0) {\n // Full success\n this.showToast('success', `✓ ${result.created.length} ${result.created.length === 1 ? 'finding' : 'findings'} sent to Synod`);\n // Close drawer after delay\n setTimeout(() => this.hide(), 1000);\n } else if (result.created.length > 0 && result.errors.length > 0) {\n // Partial success\n this.showBanner('info', `${result.created.length} of ${total} findings sent. ${result.errors.length} failed.`);\n this.showToast('info', `${result.created.length} of ${total} findings sent`);\n } else if (result.errors.length > 0 && result.created.length === 0) {\n // All errors but not an exception\n const errMsg = result.errors.map(e => e.error).join('; ');\n this.showBanner('error', `⚠ Send failed: ${errMsg}. Your findings are saved — try again.`);\n }\n\n this.refresh();\n }\n\n private handleSendError(err: ClientError | Error): void {\n const isClientErr = 'code' in err;\n const code = isClientErr ? (err as ClientError).code : null;\n\n if (code === 'AUTH_ERROR') {\n this.showBanner(\n 'error',\n '⚠ Token invalid or expired. Regenerate the token in Synod Desktop Settings → Extensions.',\n );\n } else {\n const msg = err.message || 'Unknown error';\n this.showBanner('error', `⚠ Send failed: ${msg}. Your findings are saved — try again.`);\n }\n }\n\n private async handleCopyToClipboard(): Promise<void> {\n const items = this._callbacks.getQueueSnapshot();\n if (items.length === 0) {\n this.showToast('info', 'No findings to copy');\n return;\n }\n\n const encoded = encodeFindingsToClipboard(items);\n\n try {\n let ok = false;\n if (this._callbacks.onCopyToClipboard) {\n ok = await this._callbacks.onCopyToClipboard(items);\n } else if (typeof navigator !== 'undefined' && navigator.clipboard) {\n await navigator.clipboard.writeText(encoded);\n ok = true;\n }\n\n if (ok) {\n this.showToast(\n 'success',\n `✓ Copied ${items.length} ${items.length === 1 ? 'finding' : 'findings'} (screenshots stripped)`,\n );\n } else {\n this.showToast('error', '⚠ Clipboard unavailable in this browser');\n }\n } catch {\n this.showToast('error', '⚠ Copy failed — try Send batch instead');\n }\n }\n\n private handleClearAll(): void {\n const items = this._callbacks.getQueueSnapshot();\n if (items.length > 3) {\n // Confirmation for large batches\n if (!window.confirm(`Clear all ${items.length} findings?`)) return;\n }\n this._callbacks.onClearAll();\n this.refresh();\n }\n\n // ── Banner ──────────────────────────────────────────────────────────\n\n private showBanner(type: 'error' | 'info', message: string): void {\n if (!this._bannerEl) return;\n this._bannerEl.className = `synod-fb-queue-banner ${type}`;\n this._bannerEl.textContent = message;\n this._bannerEl.style.display = 'block';\n }\n\n private hideBanner(): void {\n if (!this._bannerEl) return;\n this._bannerEl.style.display = 'none';\n }\n\n // ── Toast ───────────────────────────────────────────────────────────\n\n private showToast(variant: 'success' | 'error' | 'info', message: string): void {\n // Ensure toast container exists\n if (!this._toastContainer) {\n this._toastContainer = document.createElement('div');\n this._toastContainer.className = 'synod-fb-toast-container';\n this._wrapper.appendChild(this._toastContainer);\n }\n\n const toast = document.createElement('div');\n toast.className = `synod-fb-toast ${variant}`;\n toast.setAttribute('role', variant === 'error' ? 'alert' : 'status');\n toast.setAttribute('aria-live', variant === 'error' ? 'assertive' : 'polite');\n const toastLogo = document.createElement('span');\n toastLogo.className = 'synod-fb-toast-logo';\n toastLogo.appendChild(createSynodLogo(14));\n const toastMsg = document.createElement('span');\n toastMsg.textContent = message;\n toast.appendChild(toastLogo);\n toast.appendChild(toastMsg);\n\n this._toastContainer.appendChild(toast);\n\n // Limit to 3 visible\n while (this._toastContainer.children.length > 3) {\n this._toastContainer.removeChild(this._toastContainer.firstChild!);\n }\n\n // Auto-dismiss\n const duration = variant === 'error' ? 6000 : variant === 'success' ? 4000 : 3000;\n setTimeout(() => {\n toast.remove();\n }, duration);\n }\n\n // ── Cleanup ─────────────────────────────────────────────────────────\n\n destroy(): void {\n this.hide();\n this._toastContainer?.remove();\n this._toastContainer = null;\n }\n}\n","// ── ChargeIndicator — cursor-tracking radial progress + Synod mark ───────\n//\n// Shown while the user holds the activation key combo. Renders a small\n// Synod logo at the cursor with a circular stroke-dasharray progress ring\n// around it. The ring fills clockwise from 12 o'clock as the hold progresses.\n// When the hold completes (fraction = 1), the .charged class is added so\n// the ring color shifts from #2DAFAF to #36D6D6 (matching the brightest\n// triangle in the Synod mark).\n//\n// Lives inside the closed shadow root of FeedbackOverlay. pointer-events:none\n// so it never blocks the host page's own pointer events.\n\nimport { createSynodLogo } from '../utils/logo';\n\nconst RING_RADIUS = 18; // px — matches the SVG circle below\nconst RING_CIRCUMFERENCE = 2 * Math.PI * RING_RADIUS; // ~113.1\n\nexport class ChargeIndicator {\n private _root: HTMLElement | null = null;\n private _circle: SVGCircleElement | null = null;\n private _mounted = false;\n\n /**\n * Build the indicator's DOM once and append it to the given wrapper.\n * The indicator starts hidden; call showAt() to make it visible.\n */\n mount(wrapper: HTMLElement): void {\n if (this._mounted) return;\n\n const root = document.createElement('div');\n root.className = 'synod-fb-charge';\n root.setAttribute('aria-hidden', 'true');\n root.style.display = 'none';\n\n // SVG ring — rotated -90deg via CSS so stroke starts at the top.\n const svgNS = 'http://www.w3.org/2000/svg';\n const svg = document.createElementNS(svgNS, 'svg');\n svg.setAttribute('class', 'synod-fb-charge-ring');\n svg.setAttribute('viewBox', '0 0 40 40');\n svg.setAttribute('width', '40');\n svg.setAttribute('height', '40');\n\n const bgCircle = document.createElementNS(svgNS, 'circle');\n bgCircle.setAttribute('cx', '20');\n bgCircle.setAttribute('cy', '20');\n bgCircle.setAttribute('r', String(RING_RADIUS));\n bgCircle.setAttribute('class', 'synod-fb-charge-ring-bg');\n\n const fgCircle = document.createElementNS(svgNS, 'circle');\n fgCircle.setAttribute('cx', '20');\n fgCircle.setAttribute('cy', '20');\n fgCircle.setAttribute('r', String(RING_RADIUS));\n fgCircle.setAttribute('class', 'synod-fb-charge-ring-fg');\n fgCircle.setAttribute('stroke-dasharray', String(RING_CIRCUMFERENCE));\n // Start fully empty (dashoffset = full circumference).\n fgCircle.setAttribute('stroke-dashoffset', String(RING_CIRCUMFERENCE));\n\n svg.appendChild(bgCircle);\n svg.appendChild(fgCircle);\n root.appendChild(svg);\n\n // Center logo mark\n root.appendChild(createSynodLogo(22));\n\n wrapper.appendChild(root);\n\n this._root = root;\n this._circle = fgCircle;\n this._mounted = true;\n }\n\n /** Whether mount() has been called and the indicator is in the DOM. */\n get isMounted(): boolean {\n return this._mounted && this._root !== null && this._root.isConnected;\n }\n\n /** Display the indicator at viewport-relative coordinates. */\n showAt(x: number, y: number): void {\n if (!this._root) return;\n this._root.style.left = `${x}px`;\n this._root.style.top = `${y}px`;\n this._root.style.display = 'flex';\n }\n\n /**\n * Update the ring's progress.\n * @param fraction 0..1 — clamped. At 1 the .charged class is added.\n */\n setProgress(fraction: number): void {\n if (!this._root || !this._circle) return;\n const clamped = Math.max(0, Math.min(1, fraction));\n const offset = RING_CIRCUMFERENCE * (1 - clamped);\n this._circle.setAttribute('stroke-dashoffset', String(offset));\n if (clamped >= 1) {\n this._root.classList.add('charged');\n } else {\n this._root.classList.remove('charged');\n }\n }\n\n /** Hide the indicator. Safe to call when already hidden. */\n hide(): void {\n if (!this._root) return;\n this._root.style.display = 'none';\n this._root.classList.remove('charged');\n // Reset progress so the next show starts empty.\n if (this._circle) {\n this._circle.setAttribute(\n 'stroke-dashoffset',\n String(RING_CIRCUMFERENCE),\n );\n }\n }\n\n /** Remove the indicator from the DOM and release references. */\n destroy(): void {\n if (this._root && this._root.parentElement) {\n this._root.parentElement.removeChild(this._root);\n }\n this._root = null;\n this._circle = null;\n this._mounted = false;\n }\n}\n","// ── LogoContextMenu — right-click menu on the FloatingLogo ────────────────\n//\n// Small fixed-position menu shown when the user right-clicks the FloatingLogo.\n// Currently exposes one action: \"Hide Synod overlay\" which unmounts the logo\n// (and any visible picker / form / queue) but leaves the hotkey listener\n// armed so the user can re-activate by charging the combo again.\n//\n// Dismiss behaviors:\n// - Click outside the menu\n// - Escape key\n// - Scroll / resize on the host page\n// - Any action button click (after firing the callback)\n\nexport interface LogoContextMenuCallbacks {\n onHide: () => void;\n}\n\nexport class LogoContextMenu {\n private _root: HTMLDivElement | null = null;\n private _callbacks: LogoContextMenuCallbacks;\n private _outsideClickListener: ((e: MouseEvent) => void) | null = null;\n private _escapeListener: ((e: KeyboardEvent) => void) | null = null;\n private _dismissListener: (() => void) | null = null;\n\n constructor(callbacks: LogoContextMenuCallbacks) {\n this._callbacks = callbacks;\n }\n\n get isOpen(): boolean {\n return this._root !== null && this._root.isConnected;\n }\n\n /**\n * Show the menu at the given viewport coordinates.\n * If already open, the existing menu is closed first.\n */\n showAt(wrapper: HTMLElement, x: number, y: number): void {\n this.hide();\n\n const root = document.createElement('div');\n root.className = 'synod-fb-context-menu';\n root.setAttribute('role', 'menu');\n root.setAttribute('aria-label', 'Synod overlay options');\n\n // Clamp to viewport so the menu never overflows the right/bottom edge.\n const menuWidth = 180;\n const menuHeight = 40;\n const clampedX = Math.min(x, window.innerWidth - menuWidth - 8);\n const clampedY = Math.min(y, window.innerHeight - menuHeight - 8);\n root.style.left = `${Math.max(8, clampedX)}px`;\n root.style.top = `${Math.max(8, clampedY)}px`;\n\n const hideItem = document.createElement('button');\n hideItem.type = 'button';\n hideItem.className = 'synod-fb-context-menu-item';\n hideItem.setAttribute('role', 'menuitem');\n hideItem.textContent = 'Hide Synod overlay';\n hideItem.addEventListener('click', () => {\n this.hide();\n this._callbacks.onHide();\n });\n\n root.appendChild(hideItem);\n wrapper.appendChild(root);\n this._root = root;\n\n // Dismiss on outside click. The listener is attached to document so it\n // catches clicks anywhere on the host page (the overlay host has\n // pointer-events:none except for interactive children).\n this._outsideClickListener = (e: MouseEvent) => {\n if (!this._root) return;\n // Don't dismiss if the click landed inside the menu itself.\n if (e.target instanceof Node && this._root.contains(e.target)) return;\n this.hide();\n };\n // Defer attaching so the same contextmenu event that opened us doesn't\n // immediately close us.\n setTimeout(() => {\n if (!this._root) return;\n document.addEventListener('click', this._outsideClickListener!, true);\n document.addEventListener('contextmenu', this._outsideClickListener!, true);\n }, 0);\n\n this._escapeListener = (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n e.stopPropagation();\n this.hide();\n }\n };\n document.addEventListener('keydown', this._escapeListener, true);\n\n // Dismiss on scroll/resize — the menu is positioned in viewport coords\n // and would otherwise drift away from the cursor anchor.\n this._dismissListener = () => this.hide();\n window.addEventListener('scroll', this._dismissListener, true);\n window.addEventListener('resize', this._dismissListener, true);\n }\n\n hide(): void {\n if (this._outsideClickListener) {\n document.removeEventListener('click', this._outsideClickListener, true);\n document.removeEventListener('contextmenu', this._outsideClickListener, true);\n this._outsideClickListener = null;\n }\n if (this._escapeListener) {\n document.removeEventListener('keydown', this._escapeListener, true);\n this._escapeListener = null;\n }\n if (this._dismissListener) {\n window.removeEventListener('scroll', this._dismissListener, true);\n window.removeEventListener('resize', this._dismissListener, true);\n this._dismissListener = null;\n }\n this._root?.remove();\n this._root = null;\n }\n\n destroy(): void {\n this.hide();\n }\n}\n","// ── setup.ts — Integration wiring for the interaction layer ──────────────\n//\n// Registers onEnable/onDisable hooks with the FeedbackManager.\n// On enable: mounts FeedbackOverlay, creates picker, logo, form, queue.\n// On disable: unmounts everything, zero DOM pollution.\n//\n// This is the single integration point where SDG-294 touches SDG-298's API.\n// NOTE: We import FeedbackManager and use a passed-in instance to avoid\n// circular dependencies with index.ts.\n\nimport type { FeedbackManager } from '../lifecycle/FeedbackManager';\nimport type { FindingPayload, FindingSeverity, ClientError } from '../client/types';\nimport { FeedbackOverlay } from '../ui/FeedbackOverlay';\nimport { Highlighter } from '../interaction/Highlighter';\nimport { ElementPicker } from '../interaction/ElementPicker';\nimport { KeyComboDetector } from './KeyComboDetector';\nimport { FloatingLogo } from '../ui/FloatingLogo';\nimport { IssueForm } from '../ui/IssueForm';\nimport { QueuePanel } from '../ui/QueuePanel';\nimport { ChargeIndicator } from '../ui/ChargeIndicator';\nimport { LogoContextMenu } from '../ui/LogoContextMenu';\nimport { extract } from './SelectionExtractor';\nimport { truncDesc } from '../utils/title';\nimport type { OverlayState, SelectionContext } from './types';\n\n// Internal state\nlet manager: FeedbackManager | null = null;\nlet overlay: FeedbackOverlay | null = null;\nlet highlighter: Highlighter | null = null;\nlet picker: ElementPicker | null = null;\nlet comboDetector: KeyComboDetector | null = null;\nlet logo: FloatingLogo | null = null;\nlet issueForm: IssueForm | null = null;\nlet queuePanel: QueuePanel | null = null;\nlet chargeIndicator: ChargeIndicator | null = null;\nlet contextMenu: LogoContextMenu | null = null;\nlet state: OverlayState = 'inactive';\nlet unregisterEnable: (() => void) | null = null;\nlet unregisterDisable: (() => void) | null = null;\nlet lastSelectionContext: SelectionContext | null = null;\nlet lastMouseX = 0;\nlet lastMouseY = 0;\nlet mouseMoveListener: ((e: MouseEvent) => void) | null = null;\n\n/**\n * Reset the interaction layer state.\n * Called internally by __resetForTesting to allow fresh registration.\n * @internal\n */\nexport function __resetInteractionLayer(): void {\n if (unregisterEnable) unregisterEnable();\n if (unregisterDisable) unregisterDisable();\n unregisterEnable = null;\n unregisterDisable = null;\n manager = null;\n overlay = null;\n highlighter = null;\n picker = null;\n comboDetector = null;\n logo = null;\n issueForm = null;\n queuePanel = null;\n chargeIndicator = null;\n contextMenu = null;\n state = 'inactive';\n lastSelectionContext = null;\n lastMouseX = 0;\n lastMouseY = 0;\n mouseMoveListener = null;\n}\n\n/**\n * Build a FindingPayload from form data and selection context.\n *\n * The title is left empty (`PENDING_TITLE`). Title generation happens\n * server-side — the gateway detects empty titles on receive and either\n * asks the AI helper or applies a truncated-description fallback. This\n * keeps the browser SDK free of AI coupling and works for the offline\n * clipboard-transfer path too.\n *\n * The `source` field is intentionally omitted — the gateway hardcodes it.\n */\nexport function buildFinding(\n description: string,\n severity: FindingSeverity,\n context: SelectionContext,\n screenshotDataUrl: string | null,\n): FindingPayload {\n return {\n title: '',\n description,\n severity,\n // Gateway handler (crates/core/src/handlers/external_findings.rs:42)\n // requires suggested_action as a non-optional String and validates it\n // against [\"create_story\", \"create_research\", \"create_hotfix\"]. Omitting\n // it makes the whole batch return 422.\n suggested_action: 'create_story',\n context_json: {\n element_text: context.elementText,\n element_html: context.elementHtml,\n element_attributes: context.elementAttributes,\n parent_chain: context.parentChain,\n url: typeof window !== 'undefined' ? window.location.href : undefined,\n // Best-effort route — pathname + hash + search captures the SPA \"screen\"\n // without re-sending the origin (already in url).\n route: typeof window !== 'undefined'\n ? window.location.pathname + window.location.search + window.location.hash\n : undefined,\n page_title: typeof document !== 'undefined' ? document.title : undefined,\n viewport: typeof window !== 'undefined'\n ? { width: window.innerWidth, height: window.innerHeight }\n : undefined,\n user_agent: typeof navigator !== 'undefined' ? navigator.userAgent : undefined,\n ...(screenshotDataUrl ? { screenshot_data_url: screenshotDataUrl } : {}),\n },\n };\n}\n\n/**\n * Register the interaction layer with a FeedbackManager instance.\n * Call this once at package initialization.\n */\nexport function registerInteractionLayer(fm: FeedbackManager): void {\n if (unregisterEnable) return; // Already registered\n\n manager = fm;\n\n unregisterEnable = manager.onEnable(() => {\n if (state !== 'inactive') return;\n\n overlay = new FeedbackOverlay();\n overlay.mount(document.body);\n\n const wrapper = overlay.getContentWrapper()!;\n\n highlighter = new Highlighter(overlay);\n\n state = 'idle';\n\n // Charge indicator (cursor-tracking radial progress during key hold)\n chargeIndicator = new ChargeIndicator();\n chargeIndicator.mount(wrapper);\n\n // Right-click context menu (Hide action). Created lazily; only shown\n // when the user right-clicks the FloatingLogo.\n contextMenu = new LogoContextMenu({\n onHide: () => {\n hideVisibleUI();\n },\n });\n\n // Track latest cursor position so the charge indicator can anchor to it\n // when the combo goes down. Registered on document so we get positions\n // even when hovering over the (pointer-events:none) overlay host.\n mouseMoveListener = (e: MouseEvent) => {\n lastMouseX = e.clientX;\n lastMouseY = e.clientY;\n };\n document.addEventListener('mousemove', mouseMoveListener);\n\n // Key-combo detector — drives the charge → activate → release cycle.\n comboDetector = new KeyComboDetector(manager.config?.keyCombo, {\n onChargeStart: () => {\n if (state !== 'idle') return;\n state = 'charging';\n chargeIndicator?.showAt(lastMouseX, lastMouseY);\n },\n onChargeProgress: (frac) => {\n if (state !== 'charging') return;\n chargeIndicator?.setProgress(frac);\n // Re-anchor in case the cursor moved during the hold.\n chargeIndicator?.showAt(lastMouseX, lastMouseY);\n },\n onCharged: () => {\n if (state !== 'charging') return;\n chargeIndicator?.hide();\n // Lazy-mount the visible UI (logo + form + queue) the first time\n // the user successfully charges the combo. After this the logo\n // stays mounted until the user explicitly hides it via the\n // right-click menu. Subsequent charges just activate the picker.\n ensureVisibleUI();\n activateSelection();\n },\n onChargeCancel: () => {\n if (state === 'charging') {\n chargeIndicator?.hide();\n state = 'idle';\n }\n // 'selecting' is intentionally untouched: once the charge completes,\n // the picker stays active until the user clicks an element or presses\n // Escape — releasing the keys mid-pick should NOT dismiss.\n // 'capturing' (form open) is also untouched: ESC or the form's own\n // buttons close it; releasing keys mid-form is normal.\n },\n });\n comboDetector.start();\n });\n\n unregisterDisable = manager.onDisable(() => {\n picker?.deactivate();\n comboDetector?.stop();\n queuePanel?.destroy();\n issueForm?.hide();\n highlighter?.destroy();\n logo?.unmount();\n chargeIndicator?.destroy();\n contextMenu?.destroy();\n if (mouseMoveListener) {\n document.removeEventListener('mousemove', mouseMoveListener);\n }\n overlay?.unmount();\n\n overlay = null;\n highlighter = null;\n picker = null;\n comboDetector = null;\n logo = null;\n issueForm = null;\n queuePanel = null;\n chargeIndicator = null;\n contextMenu = null;\n mouseMoveListener = null;\n state = 'inactive';\n lastSelectionContext = null;\n lastMouseX = 0;\n lastMouseY = 0;\n });\n}\n\n/**\n * Lazy-mount the visible UI surface: FloatingLogo + IssueForm + QueuePanel.\n *\n * Called from the KeyComboDetector's onCharged callback. After the first\n * call, the logo (and form/queue scaffolding) stays mounted across picker\n * sessions. The user can unmount them again via the right-click → Hide\n * action on the logo (see {@link hideVisibleUI}).\n *\n * This split exists so the host page sees no SDK chrome at all until the\n * user has actually invoked the hotkey — auto-enable on init() does not\n * paint anything.\n */\nfunction ensureVisibleUI(): void {\n if (!overlay || !manager) return;\n const wrapper = overlay.getContentWrapper();\n if (!wrapper) return;\n\n if (!logo) {\n logo = new FloatingLogo(overlay.root, {\n onClick: () => {\n if (state === 'idle' || state === 'selecting') {\n toggleQueuePanel();\n }\n },\n onContextMenu: (x, y) => {\n showLogoContextMenu(x, y);\n },\n });\n logo.mount(wrapper);\n }\n\n if (!issueForm) {\n issueForm = new IssueForm(wrapper, {\n onAddToTray: (result) => {\n const ctx = lastSelectionContext;\n if (!ctx || !manager) return;\n const finding = buildFinding(\n result.description,\n result.severity,\n ctx,\n result.contextJson.screenshot_data_url ?? null,\n );\n manager.queue(finding);\n updateBadgeCount();\n issueForm?.hide();\n deactivateSelection();\n },\n onSendNow: async (result) => {\n const ctx = lastSelectionContext;\n if (!ctx || !manager) return;\n const finding = buildFinding(\n result.description,\n result.severity,\n ctx,\n result.contextJson.screenshot_data_url ?? null,\n );\n manager.queue(finding);\n issueForm?.hide();\n try {\n const batchResult = await manager.sendBatch();\n // Route through QueuePanel's existing toast/banner path so send-now\n // gets the same \"✓ N findings sent to Synod\" confirmation as the\n // bulk-send button. Previously this discarded the result and showed\n // nothing.\n queuePanel?.reportSendResult(batchResult);\n updateBadgeCount();\n deactivateSelection();\n } catch (err) {\n queuePanel?.reportSendError(err as ClientError | Error);\n updateBadgeCount();\n toggleQueuePanel();\n }\n },\n onCancel: () => {\n issueForm?.hide();\n deactivateSelection();\n },\n });\n }\n\n if (!queuePanel) {\n queuePanel = new QueuePanel(wrapper, {\n getQueueSnapshot: () => manager?.getQueueSnapshot() ?? [],\n onSendBatch: () => manager!.sendBatch(),\n onClearAll: () => {\n manager?.clearQueue();\n updateBadgeCount();\n },\n onRemoveItem: (index) => {\n if (!manager) return;\n const snapshot = manager.getQueueSnapshot();\n const remaining = snapshot.filter((_, i) => i !== index);\n manager.replaceQueue(remaining);\n updateBadgeCount();\n },\n onEditItem: (index, title, description, severity) => {\n if (!manager) return;\n const snapshot = manager.getQueueSnapshot();\n if (index >= 0 && index < snapshot.length) {\n snapshot[index] = { ...snapshot[index], title, description, severity };\n manager.replaceQueue(snapshot);\n }\n },\n });\n }\n\n // Sync the badge with the current queue in case findings were queued\n // while the logo was hidden.\n updateBadgeCount();\n}\n\n/**\n * Tear down the visible UI surface (logo + form + queue + active picker).\n *\n * The hotkey listener and overlay host stay alive so the user can\n * re-activate by charging the combo again. This is the action behind the\n * right-click → \"Hide Synod overlay\" menu item.\n */\nfunction hideVisibleUI(): void {\n if (state === 'charging') {\n chargeIndicator?.hide();\n }\n picker?.deactivate();\n picker = null;\n highlighter?.clear();\n issueForm?.hide();\n queuePanel?.destroy();\n logo?.unmount();\n\n queuePanel = null;\n logo = null;\n // Note: issueForm is left mounted (it's hidden) — keeping it avoids\n // re-wiring form handlers on every hide/show cycle. The next\n // ensureVisibleUI() call will only re-create the logo + queue.\n state = 'idle';\n}\n\n/**\n * Show the right-click context menu at the given viewport coordinates.\n */\nfunction showLogoContextMenu(x: number, y: number): void {\n if (!overlay || !contextMenu) return;\n const wrapper = overlay.getContentWrapper();\n if (!wrapper) return;\n contextMenu.showAt(wrapper, x, y);\n}\n\n// ── Internal state machine ──────────────────────────────────────────────\n\nfunction activateSelection(): void {\n if ((state !== 'idle' && state !== 'charging') || !overlay || !highlighter) return;\n\n const hostEl = overlay.getHostElement();\n if (!hostEl) return;\n\n if (picker) {\n picker.deactivate();\n }\n\n picker = new ElementPicker({\n overlayHostElement: hostEl,\n highlighter,\n onElementClick: (element, context) => {\n if (state !== 'selecting') return;\n lastSelectionContext = context;\n state = 'capturing';\n picker?.deactivate();\n issueForm?.show(context, element);\n },\n onEscape: () => {\n if (state === 'selecting') {\n deactivateSelection();\n } else if (state === 'capturing') {\n issueForm?.hide();\n deactivateSelection();\n }\n },\n });\n\n picker.activate();\n state = 'selecting';\n}\n\nfunction deactivateSelection(): void {\n picker?.deactivate();\n highlighter?.clear();\n state = 'idle';\n}\n\nfunction toggleQueuePanel(): void {\n if (!queuePanel) return;\n if (queuePanel.isOpen) {\n queuePanel.hide();\n } else {\n queuePanel.show();\n queuePanel.refresh();\n }\n}\n\nfunction updateBadgeCount(): void {\n logo?.updateCount(manager?.getQueueSnapshot().length ?? 0);\n}\n\n// Re-export extract for testing convenience\nexport { extract };\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACoFO,SAAS,kBACd,MACA,SACA,YACA,YAAY,OACC;AACb,QAAM,MAAM,IAAI,MAAM,OAAO;AAC7B,MAAI,OAAO;AACX,MAAI,OAAO;AACX,MAAI,aAAa;AACjB,MAAI,YAAY;AAChB,SAAO;AACT;;;ACvFO,IAAM,oBAAoB,CAAC,KAAM,KAAM,GAAI;AAQ3C,SAAS,gBAAgB,SAAqC;AACnE,MAAI,UAAU,KAAK,WAAW,kBAAkB,QAAQ;AACtD,WAAO;AAAA,EACT;AACA,SAAO,kBAAkB,OAAO;AAClC;AAgBO,SAAS,YAAY,QAA4B,OAAyB;AAE/E,MAAI,iBAAiB,UAAW,QAAO;AAGvC,MAAI,iBAAiB,gBAAgB,MAAM,SAAS,aAAc,QAAO;AAGzE,MAAI,SAAS,OAAO,UAAU,YAAY,UAAU,SAAU,MAA2B,SAAS,cAAc;AAC9G,WAAO;AAAA,EACT;AAGA,MAAI,WAAW,OAAW,QAAO;AAGjC,MAAI,UAAU,OAAO,SAAS,IAAK,QAAO;AAG1C,MAAI,WAAW,IAAK,QAAO;AAG3B,SAAO;AACT;;;AC3DA,IAAM,sBAAsB;AAC5B,IAAM,kBAAkB;AACxB,IAAM,sBAAsB;AAE5B,IAAM,oBAAoB;AAgBnB,SAAS,eAAe,OAAgD;AAC7E,QAAM,UAAoB,CAAC;AAE3B,MAAI,CAAC,MAAM,aAAa,MAAM,UAAU,KAAK,MAAM,IAAI;AACrD,YAAQ,KAAK,WAAW;AAAA,EAC1B;AAEA,MAAI,CAAC,MAAM,SAAS,MAAM,MAAM,KAAK,MAAM,IAAI;AAC7C,YAAQ,KAAK,OAAO;AAAA,EACtB;AAEA,MAAI,QAAQ,SAAS,GAAG;AACtB,UAAM,IAAI;AAAA,MACR,qDAAqD,QAAQ,KAAK,IAAI,CAAC;AAAA,IAEzE;AAAA,EACF;AAEA,QAAM,aAAa,MAAM,cAAc;AACvC,MAAI;AACF,QAAI,IAAI,UAAU;AAAA,EACpB,QAAQ;AACN,UAAM,IAAI;AAAA,MACR,wCAAwC,UAAU;AAAA,IACpD;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA,WAAW,MAAM;AAAA,IACjB,OAAO,MAAM;AAAA,IACb,UAAU,MAAM,YAAY;AAAA,IAC5B,SAAS,MAAM,WAAW;AAAA,IAC1B,YAAY,MAAM,cAAc;AAAA,EAClC;AACF;AAWO,IAAM,yBAAyB;AAM/B,SAAS,mBAAmB,aAA+B;AAChE,QAAM,aAAa,KAAK,UAAU,WAAW;AAC7C,SAAO,IAAI,YAAY,EAAE,OAAO,UAAU,EAAE,UAAU;AACxD;;;ACjEA,IAAM,gBAAgB;AAEf,IAAM,sBAAN,MAA0B;AAAA,EAK/B,YAAY,QAAwB;AAHpC,SAAQ,SAA2B,CAAC;AACpC,SAAQ,oBAAuC,CAAC;AAG9C,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,SAA+B;AACnC,QAAI,CAAC,mBAAmB,QAAQ,YAAY,GAAG;AAC7C,YAAM;AAAA,QACJ;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AACA,SAAK,OAAO,KAAK,OAAO;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,cAAsB;AACxB,WAAO,KAAK,OAAO;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA,EAKA,mBAAqC;AACnC,WAAO,CAAC,GAAG,KAAK,MAAM;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA,EAKA,aAAmB;AACjB,SAAK,SAAS,CAAC;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAM,YAAkC;AACtC,QAAI,KAAK,OAAO,WAAW,GAAG;AAC5B,aAAO,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC,EAAE;AAAA,IACnC;AAEA,UAAM,iBAAiB,CAAC,GAAG,KAAK,MAAM;AACtC,UAAM,cAAuC;AAAA,MAC3C,YAAY,KAAK,OAAO;AAAA,MACxB,UAAU;AAAA,IACZ;AAEA,UAAM,WAAW,MAAM,KAAK,cAAc,WAAW;AACrD,UAAM,cAAc,KAAK,gBAAgB,UAAU,cAAc;AAEjE,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAc,cAAc,MAAuD;AACjF,UAAM,aAAa,KAAK,OAAO,cAAc;AAC7C,QAAI,YAAgC;AAGpC,aAAS,UAAU,GAAG,WAAW,YAAY,WAAW;AAEtD,UAAI,UAAU,GAAG;AACf,cAAM,QAAQ,gBAAgB,UAAU,CAAC;AACzC,YAAI,UAAU,QAAW;AACvB,gBAAM,KAAK,MAAM,KAAK;AAAA,QACxB;AAAA,MACF;AAGA,YAAM,aAAa,IAAI,gBAAgB;AACvC,WAAK,kBAAkB,KAAK,UAAU;AACtC,YAAM,YAAY,WAAW,MAAM,WAAW,MAAM,GAAG,KAAK,OAAO,OAAO;AAE1E,UAAI;AACF,cAAM,MAAM,MAAM,MAAM,GAAG,KAAK,OAAO,UAAU,GAAG,aAAa,IAAI;AAAA,UACnE,QAAQ;AAAA,UACR,SAAS;AAAA,YACP,gBAAgB;AAAA,YAChB,eAAe,UAAU,KAAK,OAAO,KAAK;AAAA,UAC5C;AAAA,UACA,MAAM,KAAK,UAAU,IAAI;AAAA,UACzB,QAAQ,WAAW;AAAA,QACrB,CAAC;AAED,qBAAa,SAAS;AAGtB,YAAI,IAAI,IAAI;AACV,gBAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,iBAAO;AAAA,QACT;AAGA,cAAM,YAAY,KAAK,kBAAkB,IAAI,MAAM;AAEnD,YAAI,CAAC,YAAY,IAAI,QAAQ,IAAI,KAAK,WAAW,YAAY;AAC3D,gBAAM;AAAA,QACR;AAGA,oBAAY;AAAA,MACd,SAAS,KAAK;AACZ,qBAAa,SAAS;AAGtB,YAAI,cAAc,GAAG,GAAG;AACtB,cAAI,CAAC,IAAI,aAAa,WAAW,YAAY;AAC3C,kBAAM;AAAA,UACR;AACA,sBAAY;AACZ;AAAA,QACF;AAGA,cAAM,aAAa,KAAK,uBAAuB,GAAG;AAElD,YAAI,CAAC,WAAW,aAAa,WAAW,YAAY;AAClD,gBAAM;AAAA,QACR;AAEA,oBAAY;AAAA,MACd,UAAE;AAEA,cAAM,MAAM,KAAK,kBAAkB,QAAQ,UAAU;AACrD,YAAI,OAAO,GAAG;AACZ,eAAK,kBAAkB,OAAO,KAAK,CAAC;AAAA,QACtC;AAAA,MACF;AAAA,IACF;AAGA,UAAM,aAAa;AAAA,MACjB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,gBAAgB,UAAyB,cAA6C;AAI5F,UAAM,eAAe,IAAI,IAAI,SAAS,OAAO,IAAI,OAAK,EAAE,KAAK,CAAC;AAC9D,UAAM,iBAA2B,CAAC;AAGlC,aAAS,IAAI,GAAG,IAAI,aAAa,QAAQ,KAAK;AAC5C,UAAI,CAAC,aAAa,IAAI,CAAC,GAAG;AACxB,uBAAe,KAAK,CAAC;AAAA,MACvB;AAAA,IACF;AAIA,SAAK,iBAAiB,gBAAgB,aAAa,MAAM;AAEzD,WAAO;AAAA,MACL,SAAS,SAAS;AAAA,MAClB,QAAQ,SAAS;AAAA,IACnB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,iBAAiB,aAAuB,WAAyB;AAEvE,UAAM,gBAAgB,IAAI,IAAI,WAAW;AACzC,UAAM,YAA8B,CAAC;AAGrC,aAAS,IAAI,GAAG,IAAI,KAAK,OAAO,QAAQ,KAAK;AAC3C,UAAI,IAAI,WAAW;AACjB,YAAI,CAAC,cAAc,IAAI,CAAC,GAAG;AACzB,oBAAU,KAAK,KAAK,OAAO,CAAC,CAAC;AAAA,QAC/B;AAAA,MACF,OAAO;AAEL,kBAAU,KAAK,KAAK,OAAO,CAAC,CAAC;AAAA,MAC/B;AAAA,IACF;AAEA,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA,EAIQ,kBAAkB,QAA6B;AACrD,YAAQ,MAAM;AAAA,MACZ,KAAK,WAAW;AACd,eAAO;AAAA,UACL;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MAEF,KAAK,WAAW;AACd,eAAO;AAAA,UACL;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MAEF,MAAK,UAAU,OAAO,SAAS;AAC7B,eAAO;AAAA,UACL;AAAA,UACA,iBAAiB,MAAM;AAAA,UACvB;AAAA,UACA;AAAA,QACF;AAAA,MAEF,MAAK,UAAU,OAAO,SAAS;AAC7B,eAAO;AAAA,UACL;AAAA,UACA,iBAAiB,MAAM;AAAA,UACvB;AAAA,UACA;AAAA,QACF;AAAA,MAEF;AACE,eAAO;AAAA,UACL;AAAA,UACA,qBAAqB,MAAM;AAAA,UAC3B;AAAA,UACA;AAAA,QACF;AAAA,IACJ;AAAA,EACF;AAAA,EAEQ,uBAAuB,KAA2B;AAExD,QAAI,eAAe,WAAW;AAC5B,aAAO;AAAA,QACL;AAAA,QACA,kBAAkB,IAAI,OAAO;AAAA,QAC7B;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAGA,QACG,eAAe,gBAAgB,IAAI,SAAS,gBAC5C,OAAO,OAAO,QAAQ,YAAY,UAAU,OAAQ,IAAyB,SAAS,cACvF;AACA,aAAO;AAAA,QACL;AAAA,QACA,2BAA2B,KAAK,OAAO,OAAO;AAAA,QAC9C;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAGA,UAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,WAAO;AAAA,MACL;AAAA,MACA,+BAA+B,OAAO;AAAA,MACtC;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,UAAgB;AACd,eAAW,cAAc,KAAK,mBAAmB;AAC/C,UAAI;AACF,mBAAW,MAAM;AAAA,MACnB,QAAQ;AAAA,MAER;AAAA,IACF;AACA,SAAK,oBAAoB,CAAC;AAAA,EAC5B;AAAA;AAAA,EAIQ,MAAM,IAA2B;AACvC,WAAO,IAAI,QAAQ,aAAW,WAAW,SAAS,EAAE,CAAC;AAAA,EACvD;AACF;AAIA,SAAS,cAAc,KAAkC;AACvD,SAAO,eAAe,SAAS,UAAU,OAAO,eAAe;AACjE;;;ACzUO,IAAM,kBAAN,MAAsB;AAAA,EAAtB;AACL,SAAQ,eAAe;AACvB,SAAQ,WAAW;AACnB,SAAQ,UAAsC;AAC9C,SAAQ,UAAiC;AACzC,SAAQ,kBAAmC,CAAC;AAC5C,SAAQ,mBAAqC,CAAC;AAAA;AAAA,EAE9C,IAAI,cAAuB;AACzB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,UAAmB;AACrB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,IAAI,SAAqC;AACvC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,IAAI,SAAgC;AAClC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,KAAK,QAA8B;AACjC,UAAM,YAAY,eAAe,MAAM;AAGvC,QAAI,KAAK,SAAS;AAChB,WAAK,QAAQ,QAAQ;AAAA,IACvB;AAEA,SAAK,UAAU,IAAI,oBAAoB,SAAS;AAChD,SAAK,UAAU;AACf,SAAK,eAAe;AACpB,SAAK,WAAW;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA,EAKA,SAAe;AACb,QAAI,CAAC,KAAK,cAAc;AACtB,YAAM,IAAI,MAAM,yEAAyE;AAAA,IAC3F;AACA,QAAI,KAAK,SAAU;AAEnB,SAAK,WAAW;AAEhB,eAAW,WAAW,CAAC,GAAG,KAAK,eAAe,GAAG;AAC/C,cAAQ;AAAA,IACV;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,UAAgB;AACd,QAAI,CAAC,KAAK,aAAc;AACxB,QAAI,CAAC,KAAK,SAAU;AAEpB,SAAK,WAAW;AAChB,eAAW,WAAW,CAAC,GAAG,KAAK,gBAAgB,GAAG;AAChD,cAAQ;AAAA,IACV;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,SAAS,SAAoC;AAC3C,SAAK,gBAAgB,KAAK,OAAO;AACjC,QAAI,KAAK,UAAU;AACjB,cAAQ;AAAA,IACV;AACA,WAAO,MAAM;AACX,YAAM,MAAM,KAAK,gBAAgB,QAAQ,OAAO;AAChD,UAAI,OAAO,EAAG,MAAK,gBAAgB,OAAO,KAAK,CAAC;AAAA,IAClD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,UAAU,SAAqC;AAC7C,SAAK,iBAAiB,KAAK,OAAO;AAClC,WAAO,MAAM;AACX,YAAM,MAAM,KAAK,iBAAiB,QAAQ,OAAO;AACjD,UAAI,OAAO,EAAG,MAAK,iBAAiB,OAAO,KAAK,CAAC;AAAA,IACnD;AAAA,EACF;AAAA;AAAA,EAIA,MAAM,SAA+B;AACnC,SAAK,cAAc;AACnB,SAAK,QAAS,MAAM,OAAO;AAAA,EAC7B;AAAA,EAEA,MAAM,YAAkC;AACtC,SAAK,cAAc;AACnB,WAAO,KAAK,QAAS,UAAU;AAAA,EACjC;AAAA,EAEA,IAAI,cAAsB;AACxB,WAAO,KAAK,SAAS,eAAe;AAAA,EACtC;AAAA,EAEA,mBAAqC;AACnC,WAAO,KAAK,SAAS,iBAAiB,KAAK,CAAC;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA,EAKA,aAAmB;AACjB,SAAK,SAAS,WAAW;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAAa,OAA+B;AAC1C,QAAI,CAAC,KAAK,QAAS;AACnB,SAAK,QAAQ,WAAW;AACxB,eAAW,QAAQ,OAAO;AACxB,WAAK,QAAQ,MAAM,IAAI;AAAA,IACzB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,UAAgB;AACd,SAAK,SAAS,QAAQ;AACtB,SAAK,UAAU;AACf,SAAK,eAAe;AACpB,SAAK,WAAW;AAChB,SAAK,kBAAkB,CAAC;AACxB,SAAK,mBAAmB,CAAC;AAAA,EAC3B;AAAA,EAEQ,gBAAsB;AAC5B,QAAI,CAAC,KAAK,gBAAgB,CAAC,KAAK,SAAS;AACvC,YAAM,IAAI,MAAM,2DAA2D;AAAA,IAC7E;AAAA,EACF;AACF;;;AChLO,IAAM,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACO3B,IAAM,UAAU;AAChB,IAAM,QAAQ;AAEP,IAAM,kBAAN,MAAsB;AAAA,EAAtB;AACL,SAAQ,QAA4B;AACpC,SAAQ,cAAiC;AACzC,SAAQ,kBAAyC;AAAA;AAAA;AAAA,EAGjD,IAAI,YAAqB;AACvB,WAAO,KAAK,UAAU,QAAQ,KAAK,MAAM;AAAA,EAC3C;AAAA;AAAA,EAGA,IAAI,OAA0B;AAC5B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,IAAI,SAAiB;AACnB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,iBAAqC;AACnC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,QAA2B;AAC/B,QAAI,KAAK,UAAW;AAEpB,UAAM,OAAO,SAAS,cAAc,KAAK;AACzC,SAAK,KAAK;AACV,SAAK,MAAM,UAAU,uCAAuC,KAAK;AAGjE,UAAM,aAAa,KAAK,aAAa,EAAE,MAAM,SAAS,CAAC;AAGvD,UAAM,QAAQ,SAAS,cAAc,OAAO;AAC5C,UAAM,cAAc;AACpB,eAAW,YAAY,KAAK;AAG5B,UAAM,iBAAiB,SAAS,cAAc,KAAK;AACnD,mBAAe,YAAY;AAC3B,eAAW,YAAY,cAAc;AAErC,WAAO,YAAY,IAAI;AAEvB,SAAK,QAAQ;AACb,SAAK,cAAc;AACnB,SAAK,kBAAkB;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,UAAgB;AACd,QAAI,KAAK,SAAS,KAAK,MAAM,eAAe;AAC1C,WAAK,MAAM,cAAc,YAAY,KAAK,KAAK;AAAA,IACjD;AACA,SAAK,QAAQ;AACb,SAAK,cAAc;AACnB,SAAK,kBAAkB;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,oBAA2C;AACzC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,SAA+B;AACtC,QAAI,CAAC,WAAW,CAAC,KAAK,MAAO,QAAO;AACpC,WAAO,KAAK,UAAU,WAAW,KAAK,MAAM,SAAS,OAAO;AAAA,EAC9D;AACF;;;AClGO,IAAM,wBAAwB;AAAA;AAAA;AAAA;AAAA;AAAA;AAe9B,SAAS,gBAAgB,MAA6B;AAC3D,QAAM,MAAM,IAAI,UAAU,EAAE,gBAAgB,uBAAuB,eAAe;AAClF,QAAM,MAAM,IAAI;AAChB,MAAI,aAAa,SAAS,OAAO,IAAI,CAAC;AACtC,MAAI,aAAa,UAAU,OAAO,IAAI,CAAC;AACvC,MAAI,aAAa,eAAe,MAAM;AACtC,SAAO;AACT;;;ACpBO,IAAM,cAAN,MAAkB;AAAA,EAMvB,YAAYA,UAA0B;AAJtC,SAAQ,eAAsC;AAC9C,SAAQ,eAAsC;AAC9C,SAAQ,UAAiC;AAGvC,SAAK,WAAWA;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,UAAU,SAAwB;AAChC,QAAI,CAAC,QAAQ,aAAa;AACxB,WAAK,MAAM;AACX;AAAA,IACF;AAEA,UAAM,UAAU,KAAK,SAAS,kBAAkB;AAChD,QAAI,CAAC,QAAS;AAEd,QAAI,CAAC,KAAK,cAAc;AACtB,WAAK,eAAe,SAAS,cAAc,KAAK;AAChD,WAAK,aAAa,YAAY;AAC9B,cAAQ,YAAY,KAAK,YAAY;AAAA,IACvC;AAEA,QAAI,CAAC,KAAK,cAAc;AACtB,WAAK,eAAe,SAAS,cAAc,KAAK;AAChD,WAAK,aAAa,YAAY;AAC9B,WAAK,aAAa,aAAa,eAAe,MAAM;AACpD,cAAQ,YAAY,KAAK,YAAY;AAAA,IACvC;AAEA,QAAI,CAAC,KAAK,SAAS;AACjB,WAAK,UAAU,SAAS,cAAc,KAAK;AAC3C,WAAK,QAAQ,YAAY;AACzB,WAAK,QAAQ,aAAa,eAAe,MAAM;AAC/C,WAAK,QAAQ,YAAY,gBAAgB,EAAE,CAAC;AAC5C,cAAQ,YAAY,KAAK,OAAO;AAAA,IAClC;AAEA,UAAM,OAAO,QAAQ,sBAAsB;AAE3C,SAAK,aAAc,MAAM,UAAU;AACnC,SAAK,aAAc,MAAM,OAAO,GAAG,KAAK,IAAI;AAC5C,SAAK,aAAc,MAAM,MAAM,GAAG,KAAK,GAAG;AAC1C,SAAK,aAAc,MAAM,QAAQ,GAAG,KAAK,KAAK;AAC9C,SAAK,aAAc,MAAM,SAAS,GAAG,KAAK,MAAM;AAEhD,UAAM,WAAW,KAAK,mBAAmB,SAAS,IAAI;AACtD,SAAK,aAAc,MAAM,UAAU;AACnC,SAAK,aAAc,cAAc;AAEjC,UAAM,UAAU,KAAK,MAAM,KAAK,KAAK,MAAM,KAAK,KAAK,SAAS;AAC9D,SAAK,aAAc,MAAM,OAAO,GAAG,KAAK,IAAI;AAC5C,SAAK,aAAc,MAAM,MAAM,GAAG,OAAO;AAIzC,SAAK,QAAS,MAAM,UAAU,KAAK,QAAQ,MAAM,KAAK,SAAS,KAAK,SAAS;AAC7E,QAAI,KAAK,QAAS,MAAM,YAAY,SAAS;AAC3C,WAAK,QAAS,MAAM,OAAO,GAAG,KAAK,OAAO,EAAE;AAC5C,WAAK,QAAS,MAAM,MAAM,GAAG,KAAK,MAAM,EAAE;AAAA,IAC5C;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACZ,QAAI,KAAK,cAAc;AACrB,WAAK,aAAa,MAAM,UAAU;AAAA,IACpC;AACA,QAAI,KAAK,cAAc;AACrB,WAAK,aAAa,MAAM,UAAU;AAAA,IACpC;AACA,QAAI,KAAK,SAAS;AAChB,WAAK,QAAQ,MAAM,UAAU;AAAA,IAC/B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,UAAgB;AACd,SAAK,cAAc,OAAO;AAC1B,SAAK,cAAc,OAAO;AAC1B,SAAK,SAAS,OAAO;AACrB,SAAK,eAAe;AACpB,SAAK,eAAe;AACpB,SAAK,UAAU;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA,EAKQ,mBAAmB,SAAkB,MAAuB;AAClE,UAAM,MAAM,QAAQ,QAAQ,YAAY;AACxC,QAAI,KAAK,QAAQ,KAAK,IAAI,QAAQ,EAAE,KAAK;AAEzC,QAAI,WAAW;AACf,QAAI,QAAQ,UAAU,SAAS,GAAG;AAChC,iBAAW,MAAM,MAAM,KAAK,QAAQ,SAAS,EAAE,KAAK,GAAG;AACvD,UAAI,SAAS,SAAS,IAAI;AACxB,mBAAW,SAAS,UAAU,GAAG,EAAE,IAAI;AAAA,MACzC;AAAA,IACF;AAEA,UAAM,aAAc,MAAM,WAAY,GAAG,EAAE,GAAG,QAAQ,KAAK;AAC3D,UAAM,aAAa,GAAG,KAAK,MAAM,KAAK,KAAK,CAAC,OAAI,KAAK,MAAM,KAAK,MAAM,CAAC;AAEvE,WAAO,GAAG,UAAU,WAAQ,UAAU;AAAA,EACxC;AACF;;;AC7GA,SAAS,aAAa,MAAc,KAAqB;AACvD,QAAM,UAAU,KAAK,KAAK,EAAE,QAAQ,QAAQ,GAAG;AAC/C,MAAI,QAAQ,UAAU,IAAK,QAAO;AAClC,SAAO,QAAQ,UAAU,GAAG,GAAG,IAAI;AACrC;AAIA,IAAM,cAAc,oBAAI,IAAI;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAQD,SAAS,kBAAkB,SAA0C;AACnE,QAAM,MAA8B,CAAC;AAGrC,WAAS,IAAI,GAAG,IAAI,QAAQ,WAAW,QAAQ,KAAK;AAClD,UAAM,OAAO,QAAQ,WAAW,CAAC;AACjC,UAAM,OAAO,KAAK,KAAK,YAAY;AACnC,QAAI,YAAY,IAAI,IAAI,EAAG;AAC3B,UAAM,QAAQ,KAAK;AACnB,QAAI,MAAM,SAAS,IAAK;AACxB,QAAI,IAAI,IAAI;AAAA,EACd;AACA,SAAO;AACT;AAWA,SAAS,mBAAmB,SAA2E;AACrG,QAAM,QAAiE,CAAC;AACxE,MAAI,UAA0B,QAAQ;AACtC,SAAO,WAAW,QAAQ,aAAa,GAAG;AACxC,UAAM,KAAK,QAAQ,MAAM;AAGzB,UAAM,OAAO,QAAQ,aAAa,OAAO,KAAK,IAAI,KAAK,KAAK;AAE5D,QAAI,MAAM,KAAK;AACb,YAAM,QAA0D;AAAA,QAC9D,KAAK,QAAQ,QAAQ,YAAY;AAAA,MACnC;AACA,UAAI,GAAI,OAAM,KAAK;AACnB,UAAI,IAAK,OAAM,YAAY;AAC3B,YAAM,QAAQ,KAAK;AAAA,IACrB;AAEA,QAAI,QAAQ,YAAY,OAAQ;AAChC,cAAU,QAAQ;AAAA,EACpB;AACA,SAAO;AACT;AAMA,SAAS,aAAa,MAAc,KAAqB;AACvD,MAAI,KAAK,UAAU,IAAK,QAAO;AAE/B,QAAM,QAAQ,KAAK,UAAU,GAAG,MAAM,CAAC;AACvC,QAAM,SAAS,MAAM,YAAY,GAAG;AAGpC,QAAM,UAAU,SAAS,MAAM,MAAM,CAAC,MAAM,MAAM,MAAM,EAAE,SAAS,GAAG,IAClE,MAAM,UAAU,GAAG,MAAM,IACzB;AACJ,SAAO,UAAU;AACnB;AAKO,SAAS,QAAQ,SAAoC;AAC1D,QAAM,OAAO,QAAQ,sBAAsB;AAE3C,SAAO;AAAA,IACL,SAAS,QAAQ,QAAQ,YAAY;AAAA,IACrC,aAAa,aAAa,QAAQ,eAAe,IAAI,GAAG;AAAA,IACxD,aAAa,aAAa,QAAQ,WAAW,IAAI;AAAA,IACjD,mBAAmB,kBAAkB,OAAO;AAAA,IAC5C,aAAa,mBAAmB,OAAO;AAAA,IACvC,cAAc;AAAA,MACZ,MAAM,KAAK,MAAM,KAAK,IAAI;AAAA,MAC1B,KAAK,KAAK,MAAM,KAAK,GAAG;AAAA,MACxB,OAAO,KAAK,MAAM,KAAK,KAAK;AAAA,MAC5B,QAAQ,KAAK,MAAM,KAAK,MAAM;AAAA,IAChC;AAAA,EACF;AACF;;;ACvGO,IAAM,gBAAN,MAAoB;AAAA,EAOzB,YAAY,SAA+B;AAL3C,SAAQ,cAAsC;AAC9C,SAAQ,UAAU;AAClB,SAAQ,SAAwB;AAChC,SAAQ,gBAAmC;AAkD3C;AAAA,SAAQ,cAAc,CAAC,MAAwB;AAC7C,WAAK,gBAAgB;AAErB,UAAI,KAAK,WAAW,KAAM;AAE1B,WAAK,SAAS,sBAAsB,MAAM;AACxC,aAAK,SAAS;AACd,cAAM,MAAM,KAAK;AACjB,YAAI,CAAC,IAAK;AAEV,cAAM,SAAS,IAAI;AACnB,YAAI,CAAC,UAAU,OAAO,aAAa,EAAG;AAGtC,YAAI,KAAK,SAAS,mBAAmB,SAAS,MAAM,EAAG;AACvD,YAAI,WAAW,SAAS,QAAQ,WAAW,SAAS,gBAAiB;AAErE,aAAK,SAAS,YAAY,UAAU,MAAM;AAAA,MAC5C,CAAC;AAAA,IACH;AAEA,SAAQ,UAAU,CAAC,MAAwB;AAEzC,QAAE,eAAe;AACjB,QAAE,gBAAgB;AAElB,YAAM,SAAS,EAAE;AAEjB,UAAI,CAAC,UAAU,OAAO,aAAa,EAAG;AACtC,UAAI,KAAK,SAAS,mBAAmB,SAAS,MAAM,EAAG;AACvD,UAAI,CAAC,OAAO,YAAa;AAEzB,UAAI;AACJ,UAAI;AACF,kBAAU,QAAQ,MAAM;AAAA,MAC1B,QAAQ;AACN;AAAA,MACF;AAEA,WAAK,SAAS,eAAe,QAAQ,OAAO;AAAA,IAC9C;AAEA,SAAQ,YAAY,CAAC,MAA2B;AAC9C,UAAI,EAAE,QAAQ,UAAU;AACtB,UAAE,eAAe;AACjB,UAAE,gBAAgB;AAClB,aAAK,SAAS,SAAS;AAAA,MACzB;AAAA,IACF;AAEA,SAAQ,WAAW,MAAY;AAC7B,UAAI,KAAK,WAAW,KAAM;AAE1B,WAAK,SAAS,sBAAsB,MAAM;AACxC,aAAK,SAAS;AACd,cAAM,MAAM,KAAK;AACjB,YAAI,CAAC,IAAK;AAEV,cAAM,SAAS,IAAI;AACnB,YAAI,UAAU,OAAO,eAAe,CAAC,KAAK,SAAS,mBAAmB,SAAS,MAAM,GAAG;AACtF,eAAK,SAAS,YAAY,UAAU,MAAM;AAAA,QAC5C;AAAA,MACF,CAAC;AAAA,IACH;AA9GE,SAAK,WAAW;AAAA,EAClB;AAAA,EAEA,IAAI,WAAoB;AACtB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,WAAiB;AACf,QAAI,KAAK,QAAS;AAElB,SAAK,cAAc,IAAI,gBAAgB;AACvC,SAAK,UAAU;AAEf,UAAM,SAAS,KAAK,YAAY;AAEhC,aAAS,iBAAiB,aAAa,KAAK,aAAa,EAAE,SAAS,MAAM,OAAO,CAAC;AAClF,aAAS,iBAAiB,SAAS,KAAK,SAAS,EAAE,SAAS,MAAM,OAAO,CAAC;AAC1E,aAAS,iBAAiB,WAAW,KAAK,WAAW,EAAE,SAAS,MAAM,OAAO,CAAC;AAC9E,aAAS,iBAAiB,UAAU,KAAK,UAAU,EAAE,SAAS,MAAM,OAAO,CAAC;AAAA,EAC9E;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAAmB;AACjB,QAAI,CAAC,KAAK,QAAS;AAEnB,SAAK,aAAa,MAAM;AACxB,SAAK,cAAc;AACnB,SAAK,UAAU;AAEf,QAAI,KAAK,WAAW,MAAM;AACxB,2BAAqB,KAAK,MAAM;AAChC,WAAK,SAAS;AAAA,IAChB;AACA,SAAK,gBAAgB;AAErB,SAAK,SAAS,YAAY,MAAM;AAAA,EAClC;AAoEF;;;ACrGA,IAAM,kBAAkB;AAExB,IAAM,gBAAgC;AAAA,EACpC,MAAM;AAAA,EACN,OAAO;AAAA,EACP,KAAK;AACP;AAEA,SAAS,WAAW,UAAmC;AACrD,MAAI,CAAC,SAAU,QAAO;AAEtB,QAAM,QAAQ,SAAS,YAAY;AACnC,QAAM,QAAQ,MAAM,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC;AAElD,QAAM,SAAyB,EAAE,KAAK,GAAG;AAEzC,aAAW,QAAQ,OAAO;AACxB,YAAQ,MAAM;AAAA,MACZ,KAAK;AAAA,MACL,KAAK;AACH,eAAO,OAAO;AACd;AAAA,MACF,KAAK;AACH,eAAO,QAAQ;AACf;AAAA,MACF,KAAK;AACH,eAAO,MAAM;AACb;AAAA,MACF;AACE,eAAO,MAAM;AAAA,IACjB;AAAA,EACF;AAEA,MAAI,CAAC,OAAO,IAAK,QAAO;AAExB,SAAO;AACT;AAEO,IAAM,mBAAN,MAAuB;AAAA,EAY5B,YAAY,UAA8B,WAA8B,SAAiB,iBAAiB;AAR1G,SAAQ,cAAsC;AAG9C;AAAA,SAAQ,eAAe;AACvB,SAAQ,YAAY;AACpB,SAAQ,WAAW;AACnB,SAAQ,SAAwB;AAuChC;AAAA,SAAQ,YAAY,CAAC,MAA2B;AAC9C,UAAI,CAAC,KAAK,aAAa,CAAC,EAAG;AAG3B,UAAI,KAAK,aAAa,KAAK,SAAU;AAErC,WAAK,YAAY;AACjB,WAAK,WAAW;AAChB,WAAK,eAAe,YAAY,IAAI;AACpC,WAAK,WAAW,gBAAgB;AAChC,WAAK,KAAK;AAAA,IACZ;AAEA,SAAQ,UAAU,CAAC,MAA2B;AAC5C,UAAI,CAAC,KAAK,WAAW,EAAE,IAAI,YAAY,CAAC,EAAG;AAI3C,YAAM,cAAc,KAAK;AACzB,YAAM,aAAa,KAAK;AAExB,WAAK,UAAU;AACf,WAAK,YAAY;AACjB,WAAK,WAAW;AAChB,WAAK,eAAe;AAKpB,UAAI,eAAe,YAAY;AAC7B,aAAK,WAAW,iBAAiB;AAAA,MACnC;AAAA,IACF;AAEA,SAAQ,OAAO,MAAY;AACzB,UAAI,CAAC,KAAK,UAAW;AAErB,YAAM,UAAU,YAAY,IAAI,IAAI,KAAK;AACzC,YAAM,OAAO,KAAK,IAAI,GAAG,UAAU,KAAK,OAAO;AAC/C,WAAK,WAAW,mBAAmB,IAAI;AAEvC,UAAI,QAAQ,GAAG;AACb,aAAK,YAAY;AACjB,aAAK,WAAW;AAChB,aAAK,UAAU;AACf,aAAK,WAAW,YAAY;AAC5B;AAAA,MACF;AAEA,WAAK,SAAS,sBAAsB,KAAK,IAAI;AAAA,IAC/C;AAtFE,SAAK,SAAS,WAAW,QAAQ;AACjC,SAAK,UAAU;AACf,SAAK,aAAa;AAAA,EACpB;AAAA;AAAA,EAGA,QAAc;AACZ,QAAI,KAAK,YAAa;AAEtB,SAAK,cAAc,IAAI,gBAAgB;AACvC,UAAM,SAAS,KAAK,YAAY;AAEhC,aAAS,iBAAiB,WAAW,KAAK,WAAW,EAAE,SAAS,MAAM,OAAO,CAAC;AAC9E,aAAS,iBAAiB,SAAS,KAAK,SAAS,EAAE,SAAS,MAAM,OAAO,CAAC;AAAA,EAC5E;AAAA;AAAA,EAGA,OAAa;AACX,SAAK,aAAa,MAAM;AACxB,SAAK,cAAc;AACnB,SAAK,UAAU;AACf,SAAK,YAAY;AACjB,SAAK,WAAW;AAChB,SAAK,eAAe;AAAA,EACtB;AAAA;AAAA,EAGA,aAAa,GAA2B;AACtC,QAAI,KAAK,OAAO,QAAQ,CAAC,EAAE,QAAS,QAAO;AAC3C,QAAI,KAAK,OAAO,SAAS,CAAC,EAAE,SAAU,QAAO;AAC7C,QAAI,KAAK,OAAO,OAAO,CAAC,EAAE,OAAQ,QAAO;AACzC,WAAO,EAAE,IAAI,YAAY,MAAM,KAAK,OAAO;AAAA,EAC7C;AAAA,EAwDQ,YAAkB;AACxB,QAAI,KAAK,WAAW,MAAM;AACxB,2BAAqB,KAAK,MAAM;AAChC,WAAK,SAAS;AAAA,IAChB;AAAA,EACF;AAAA,EAEQ,WAAW,KAAsB;AACvC,QAAI,QAAQ,KAAK,OAAO,IAAK,QAAO;AACpC,QAAI,KAAK,OAAO,QAAQ,QAAQ,UAAW,QAAO;AAClD,QAAI,KAAK,OAAO,SAAS,QAAQ,QAAS,QAAO;AACjD,QAAI,KAAK,OAAO,OAAO,QAAQ,MAAO,QAAO;AAC7C,WAAO;AAAA,EACT;AACF;;;AC7KA,IAAM,cAAc;AACpB,IAAM,kBAAkB;AAEjB,IAAM,eAAN,MAAmB;AAAA,EAaxB,YAAY,OAA0B,WAAkC;AAZxE,SAAQ,QAA+B;AACvC,SAAQ,SAAgC;AAIxC;AAAA,SAAQ,YAAY;AACpB,SAAQ,UAAU;AAClB,SAAQ,UAAU;AAClB,SAAQ,SAAS;AACjB,SAAQ,SAAS;AACjB,SAAQ,SAAS;AAkFjB;AAAA,SAAQ,gBAAgB,CAAC,MAA0B;AACjD,UAAI,CAAC,KAAK,MAAO;AAEjB,QAAE,eAAe;AACjB,QAAE,gBAAgB;AAElB,WAAK,YAAY;AACjB,WAAK,SAAS;AACd,WAAK,UAAU,EAAE;AACjB,WAAK,UAAU,EAAE;AAEjB,UAAI;AACF,aAAK,MAAM,kBAAkB,EAAE,SAAS;AAAA,MAC1C,QAAQ;AAAA,MAER;AACA,WAAK,MAAM,iBAAiB,eAAe,KAAK,aAAa;AAC7D,WAAK,MAAM,iBAAiB,aAAa,KAAK,WAAW;AACzD,WAAK,MAAM,iBAAiB,iBAAiB,KAAK,WAAW;AAAA,IAC/D;AAEA,SAAQ,gBAAgB,CAAC,MAA0B;AACjD,UAAI,CAAC,KAAK,aAAa,CAAC,KAAK,MAAO;AAEpC,YAAM,KAAK,EAAE,UAAU,KAAK;AAC5B,YAAM,KAAK,EAAE,UAAU,KAAK;AAE5B,UAAI,CAAC,KAAK,UAAU,KAAK,IAAI,EAAE,IAAI,KAAK,IAAI,EAAE,IAAI,iBAAiB;AACjE,aAAK,SAAS;AAAA,MAChB;AAEA,UAAI,CAAC,KAAK,OAAQ;AAElB,YAAM,OAAO,KAAK,SAAS;AAC3B,YAAM,OAAO,KAAK,SAAS;AAE3B,YAAM,WAAW,KAAK,IAAI,GAAG,KAAK,IAAI,MAAM,OAAO,aAAa,EAAE,CAAC;AACnE,YAAM,WAAW,KAAK,IAAI,GAAG,KAAK,IAAI,MAAM,OAAO,cAAc,EAAE,CAAC;AAEpE,WAAK,MAAM,MAAM,OAAO,GAAG,QAAQ;AACnC,WAAK,MAAM,MAAM,MAAM,GAAG,QAAQ;AAAA,IACpC;AAEA,SAAQ,cAAc,CAAC,MAA0B;AAC/C,UAAI,CAAC,KAAK,MAAO;AAEjB,WAAK,YAAY;AAEjB,UAAI;AACF,aAAK,MAAM,sBAAsB,EAAE,SAAS;AAAA,MAC9C,QAAQ;AAAA,MAER;AAEA,WAAK,MAAM,oBAAoB,eAAe,KAAK,aAAa;AAChE,WAAK,MAAM,oBAAoB,aAAa,KAAK,WAAW;AAC5D,WAAK,MAAM,oBAAoB,iBAAiB,KAAK,WAAW;AAEhE,UAAI,KAAK,QAAQ;AACf,cAAM,OAAO,WAAW,KAAK,MAAM,MAAM,QAAQ,GAAG;AACpD,cAAM,MAAM,WAAW,KAAK,MAAM,MAAM,OAAO,GAAG;AAClD,aAAK,SAAS;AACd,aAAK,SAAS;AACd,aAAK,aAAa,MAAM,GAAG;AAAA,MAC7B,OAAO;AACL,aAAK,WAAW,QAAQ;AAAA,MAC1B;AAAA,IACF;AAEA,SAAQ,YAAY,CAAC,MAA2B;AAC9C,UAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,KAAK;AACtC,UAAE,eAAe;AACjB,aAAK,WAAW,QAAQ;AAAA,MAC1B;AAAA,IACF;AAEA,SAAQ,gBAAgB,CAAC,MAAwB;AAK/C,QAAE,eAAe;AACjB,QAAE,gBAAgB;AAClB,WAAK,WAAW,gBAAgB,EAAE,SAAS,EAAE,OAAO;AAAA,IACtD;AAnKE,SAAK;AACL,SAAK,aAAa;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,SAA4B;AAChC,QAAI,KAAK,MAAO;AAEhB,UAAMC,QAAO,SAAS,cAAc,KAAK;AACzC,IAAAA,MAAK,YAAY;AACjB,IAAAA,MAAK,YAAY,gBAAgB,EAAE,CAAC;AACpC,IAAAA,MAAK,aAAa,QAAQ,QAAQ;AAClC,IAAAA,MAAK,aAAa,cAAc,2CAAsC;AACtE,IAAAA,MAAK,aAAa,YAAY,GAAG;AAGjC,UAAM,MAAM,KAAK,aAAa;AAC9B,IAAAA,MAAK,MAAM,OAAO,GAAG,IAAI,CAAC;AAC1B,IAAAA,MAAK,MAAM,MAAM,GAAG,IAAI,CAAC;AAEzB,SAAK,SAAS,IAAI;AAClB,SAAK,SAAS,IAAI;AAGlB,UAAM,QAAQ,SAAS,cAAc,KAAK;AAC1C,UAAM,YAAY;AAClB,UAAM,MAAM,UAAU;AACtB,UAAM,aAAa,aAAa,QAAQ;AACxC,IAAAA,MAAK,YAAY,KAAK;AAGtB,IAAAA,MAAK,iBAAiB,eAAe,KAAK,aAAa;AACvD,IAAAA,MAAK,iBAAiB,WAAW,KAAK,SAAS;AAE/C,IAAAA,MAAK,iBAAiB,eAAe,KAAK,aAAa;AAEvD,YAAQ,YAAYA,KAAI;AAExB,SAAK,QAAQA;AACb,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA,EAKA,UAAgB;AACd,SAAK,OAAO,oBAAoB,eAAe,KAAK,aAAa;AACjE,SAAK,OAAO,oBAAoB,WAAW,KAAK,SAAS;AACzD,SAAK,OAAO,oBAAoB,eAAe,KAAK,aAAa;AACjE,SAAK,OAAO,OAAO;AACnB,SAAK,QAAQ;AACb,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,OAAqB;AAC/B,QAAI,CAAC,KAAK,UAAU,CAAC,KAAK,MAAO;AAEjC,QAAI,QAAQ,GAAG;AACb,WAAK,OAAO,MAAM,UAAU;AAC5B,WAAK,OAAO,cAAc,OAAO,KAAK;AACtC,WAAK,OAAO,aAAa,cAAc,GAAG,KAAK,IAAI,UAAU,IAAI,YAAY,UAAU,SAAS;AAEhG,WAAK,OAAO,UAAU,OAAO,2BAA2B;AACxD,WAAK,KAAK,OAAO;AACjB,WAAK,OAAO,UAAU,IAAI,2BAA2B;AAAA,IACvD,OAAO;AACL,WAAK,OAAO,MAAM,UAAU;AAAA,IAC9B;AAEA,SAAK,WAAW,gBAAgB,KAAK;AAAA,EACvC;AAAA;AAAA,EA4FQ,eAAyC;AAC/C,QAAI;AACF,YAAM,MAAM,aAAa,QAAQ,WAAW;AAC5C,UAAI,KAAK;AACP,cAAM,MAAM,KAAK,MAAM,GAAG;AAC1B,YAAI,OAAO,IAAI,MAAM,YAAY,OAAO,IAAI,MAAM,UAAU;AAC1D,iBAAO;AAAA,QACT;AAAA,MACF;AAAA,IACF,QAAQ;AAAA,IAER;AAEA,WAAO;AAAA,MACL,GAAG,KAAK,IAAI,GAAG,OAAO,aAAa,EAAE;AAAA,MACrC,GAAG,KAAK,IAAI,GAAG,OAAO,cAAc,EAAE;AAAA,IACxC;AAAA,EACF;AAAA,EAEQ,aAAa,GAAW,GAAiB;AAC/C,QAAI;AACF,mBAAa,QAAQ,aAAa,KAAK,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC;AAAA,IAC5D,QAAQ;AAAA,IAER;AAAA,EACF;AACF;;;AC9MO,IAAM,aAAa;AAenB,SAAS,yBACd,YACA,aACA,UACA,SAAiB,YACD;AAChB,QAAM,aAAa,WAAW,MAAM,WAAW;AAC/C,QAAM,aAAa,SAAS,cAAc;AAE1C,MAAI;AAGJ,MAAI,aAAa,YAAY,SAAS,QAAQ;AAE5C,UAAM,WAAW,MAAM,YAAY,SAAS;AAAA,EAC9C,OAAO;AAEL,UAAM,aAAa;AAAA,EACrB;AAGA,MAAI,OAAO,WAAW;AAGtB,MAAI,OAAO,YAAY,QAAQ,SAAS,SAAS,YAAY;AAC3D,WAAO,SAAS,aAAa,YAAY,QAAQ,SAAS;AAAA,EAC5D;AAGA,QAAM,KAAK,IAAI,KAAK,UAAU;AAC9B,SAAO,KAAK,IAAI,MAAM,UAAU;AAEhC,SAAO,EAAE,MAAM,IAAI;AACrB;;;AC3DO,IAAM,uBAAuB;AAUpC,eAAsB,QAAQ,SAA0C;AACtE,MAAI;AACF,UAAM,MAAM,MAAM,OAAO,mBAAmB;AAC5C,UAAM,YAAa,IAAY,aAAc,IAAY,SAAS;AAElE,QAAI,OAAO,cAAc,YAAY;AACnC,cAAQ,KAAK,8EAA8E;AAC3F,aAAO;AAAA,IACT;AAEA,UAAM,UAAkB,MAAM,UAAU,SAAS,EAAE,SAAS,KAAK,OAAO,IAAI,CAAC;AAE7E,QAAI,OAAO,YAAY,YAAY,QAAQ,WAAW,GAAG;AACvD,cAAQ,KAAK,8DAA8D;AAC3E,aAAO;AAAA,IACT;AAGA,QAAI,QAAQ,SAAS,sBAAsB;AACzC,cAAQ;AAAA,QACN,oDAAoD,QAAQ,MAAM,YAAY,oBAAoB;AAAA,MACpG;AACA,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,YAAQ,KAAK,+CAA+C,GAAG;AAC/D,WAAO;AAAA,EACT;AACF;;;AC5BA,IAAM,aAAa;AAEZ,IAAM,YAAN,MAAgB;AAAA,EAarB,YAAY,SAAsB,WAA+B;AAVjE,SAAQ,UAAiC;AACzC,SAAQ,kBAA2C;AACnD,SAAQ,qBAAoC;AAC5C,SAAQ,qBAAqC;AAC7C,SAAQ,qBAAqC;AAC7C,SAAQ,gBAA4C;AACpD,SAAQ,YAA6B;AACrC,SAAQ,oBAA6C;AACrD,SAAQ,mBAA4C;AAmTpD,SAAQ,YAAY,CAAC,MAA2B;AAC9C,UAAI,EAAE,QAAQ,UAAU;AACtB,UAAE,eAAe;AACjB,UAAE,gBAAgB;AAClB,aAAK,WAAW,SAAS;AACzB;AAAA,MACF;AAEA,UAAI,EAAE,QAAQ,SAAS,KAAK,SAAS;AACnC,cAAM,YAAY,KAAK,qBAAqB;AAC5C,YAAI,UAAU,WAAW,EAAG;AAE5B,cAAM,QAAQ,UAAU,CAAC;AACzB,cAAM,OAAO,UAAU,UAAU,SAAS,CAAC;AAE3C,YAAI,EAAE,UAAU;AACd,cAAI,SAAS,kBAAkB,OAAO;AACpC,cAAE,eAAe;AACjB,iBAAK,MAAM;AAAA,UACb;AAAA,QACF,OAAO;AACL,cAAI,SAAS,kBAAkB,MAAM;AACnC,cAAE,eAAe;AACjB,kBAAM,MAAM;AAAA,UACd;AAAA,QACF;AAAA,MACF;AAEA,UAAI,EAAE,WAAW,EAAE,QAAQ,SAAS;AAClC,UAAE,eAAe;AACjB,aAAK,aAAa,EAAE,WAAW,SAAS,KAAK;AAAA,MAC/C;AAAA,IACF;AAhVE,SAAK,WAAW;AAChB,SAAK,aAAa;AAAA,EACpB;AAAA,EAEA,KAAK,SAA2B,SAAyB;AACvD,SAAK,KAAK;AAEV,SAAK,kBAAkB;AACvB,SAAK,qBAAqB,WAAW;AACrC,SAAK,qBAAqB;AAC1B,SAAK,YAAY;AAEjB,SAAK,qBAAqB,SAAS;AAEnC,UAAM,OAAO,KAAK,UAAU;AAC5B,SAAK,UAAU;AAEf,UAAM,kBAAkB;AACxB,UAAM,MAAM;AAAA,MACV,QAAQ;AAAA,MACR,EAAE,OAAO,YAAY,QAAQ,gBAAgB;AAAA,MAC7C,EAAE,YAAY,OAAO,YAAY,aAAa,OAAO,YAAY;AAAA,IACnE;AAEA,SAAK,MAAM,OAAO,GAAG,IAAI,IAAI;AAC7B,SAAK,MAAM,MAAM,GAAG,IAAI,GAAG;AAE3B,SAAK,SAAS,YAAY,IAAI;AAE9B,0BAAsB,MAAM;AAC1B,UAAI,CAAC,KAAK,QAAS;AACnB,YAAM,eAAe,KAAK,QAAQ;AAClC,UAAI,KAAK,IAAI,eAAe,eAAe,IAAI,IAAI;AACjD,cAAM,cAAc;AAAA,UAClB,QAAQ;AAAA,UACR,EAAE,OAAO,YAAY,QAAQ,aAAa;AAAA,UAC1C,EAAE,YAAY,OAAO,YAAY,aAAa,OAAO,YAAY;AAAA,QACnE;AACA,aAAK,QAAQ,MAAM,OAAO,GAAG,YAAY,IAAI;AAC7C,aAAK,QAAQ,MAAM,MAAM,GAAG,YAAY,GAAG;AAAA,MAC7C;AAAA,IACF,CAAC;AAED,0BAAsB,MAAM;AAC1B,WAAK,eAAe,MAAM;AAAA,IAC5B,CAAC;AAED,SAAK,iBAAiB,WAAW,KAAK,SAAS;AAAA,EACjD;AAAA,EAEA,OAAa;AACX,SAAK,SAAS,oBAAoB,WAAW,KAAK,SAAS;AAC3D,SAAK,SAAS,OAAO;AACrB,SAAK,UAAU;AACf,SAAK,kBAAkB;AACvB,SAAK,qBAAqB;AAC1B,SAAK,qBAAqB;AAE1B,QAAI,KAAK,sBAAsB,KAAK,mBAAmB,aAAa;AAClE,MAAC,KAAK,mBAAmC,QAAQ;AAAA,IACnD;AACA,SAAK,qBAAqB;AAAA,EAC5B;AAAA,EAEA,IAAI,YAAqB;AACvB,WAAO,KAAK,YAAY,QAAQ,KAAK,QAAQ;AAAA,EAC/C;AAAA,EAEQ,YAA4B;AAClC,UAAM,OAAO,SAAS,cAAc,KAAK;AACzC,SAAK,YAAY;AACjB,SAAK,aAAa,QAAQ,QAAQ;AAClC,SAAK,aAAa,cAAc,MAAM;AACtC,SAAK,aAAa,cAAc,gBAAgB;AAGhD,UAAM,SAAS,SAAS,cAAc,KAAK;AAC3C,WAAO,YAAY;AAEnB,UAAM,aAAa,SAAS,cAAc,KAAK;AAC/C,eAAW,YAAY;AACvB,eAAW,YAAY,gBAAgB,EAAE,CAAC;AAE1C,UAAM,aAAa,SAAS,cAAc,KAAK;AAC/C,eAAW,YAAY;AAIvB,UAAM,MAAM,KAAK;AACjB,UAAM,KAAK,IAAI,oBAAoB,IAAI;AACvC,UAAM,MAAM,IAAI,oBAAoB,OAAO,GAAG,KAAK;AACnD,QAAI,aAAa,IAAI;AACrB,QAAI,GAAI,eAAc,IAAI,EAAE;AAC5B,QAAI,IAAK,eAAc,MAAM,IAAI,MAAM,KAAK,EAAE,KAAK,GAAG;AACtD,QAAI,WAAW,SAAS,GAAI,cAAa,WAAW,UAAU,GAAG,EAAE,IAAI;AACvE,eAAW,cAAc,GAAG,UAAU,SAAM,IAAI,aAAa,KAAK,OAAI,IAAI,aAAa,MAAM;AAE7F,UAAM,WAAW,SAAS,cAAc,QAAQ;AAChD,aAAS,YAAY;AACrB,aAAS,cAAc;AACvB,aAAS,aAAa,cAAc,QAAQ;AAC5C,aAAS,iBAAiB,SAAS,MAAM,KAAK,WAAW,SAAS,CAAC;AAEnE,WAAO,YAAY,UAAU;AAC7B,WAAO,YAAY,UAAU;AAC7B,WAAO,YAAY,QAAQ;AAG3B,UAAM,OAAO,SAAS,cAAc,KAAK;AACzC,SAAK,YAAY;AAGjB,UAAM,YAAY,SAAS,cAAc,KAAK;AAC9C,cAAU,YAAY;AACtB,UAAM,YAAY,SAAS,cAAc,OAAO;AAChD,cAAU,YAAY;AACtB,cAAU,cAAc;AACxB,cAAU,aAAa,OAAO,eAAe;AAC7C,UAAM,eAAe,SAAS,cAAc,UAAU;AACtD,iBAAa,YAAY;AACzB,iBAAa,KAAK;AAClB,iBAAa,OAAO;AACpB,iBAAa,YAAY;AACzB,iBAAa,aAAa,cAAc,mBAAmB;AAC3D,iBAAa,aAAa,eAAe,kCAAwB;AACjE,iBAAa,aAAa,YAAY,EAAE;AACxC,SAAK,gBAAgB;AACrB,cAAU,YAAY,SAAS;AAC/B,cAAU,YAAY,YAAY;AAGlC,UAAM,WAAW,SAAS,cAAc,KAAK;AAC7C,aAAS,YAAY;AACrB,UAAM,WAAW,SAAS,cAAc,KAAK;AAC7C,aAAS,YAAY;AACrB,aAAS,cAAc;AACvB,UAAM,WAAW,SAAS,cAAc,KAAK;AAC7C,aAAS,YAAY;AACrB,aAAS,aAAa,QAAQ,OAAO;AACrC,aAAS,aAAa,cAAc,UAAU;AAE9C,UAAM,aAAgC,CAAC,OAAO,UAAU,MAAM;AAC9D,eAAW,OAAO,YAAY;AAC5B,YAAM,MAAM,SAAS,cAAc,QAAQ;AAC3C,UAAI,YAAY;AAChB,UAAI,QAAQ,MAAM;AAClB,UAAI,cAAc,IAAI,OAAO,CAAC,EAAE,YAAY,IAAI,IAAI,MAAM,CAAC;AAC3D,UAAI,aAAa,gBAAgB,QAAQ,KAAK,YAAY,SAAS,OAAO;AAC1E,UAAI,QAAQ,KAAK,UAAW,KAAI,UAAU,IAAI,QAAQ;AACtD,UAAI,iBAAiB,SAAS,CAAC,MAAM;AACnC,UAAE,eAAe;AACjB,aAAK,YAAY;AACjB,iBAAS,iBAAiB,mBAAmB,EAAE,QAAQ,CAAC,MAAM;AAC5D,gBAAM,SAAU,EAAwB,QAAQ;AAChD,YAAE,aAAa,gBAAgB,WAAW,MAAM,SAAS,OAAO;AAChE,YAAE,UAAU,OAAO,UAAU,WAAW,GAAG;AAAA,QAC7C,CAAC;AAAA,MACH,CAAC;AACD,UAAI,iBAAiB,WAAW,CAAC,MAAM;AACrC,YAAI,EAAE,QAAQ,gBAAgB,EAAE,QAAQ,aAAa;AACnD,YAAE,eAAe;AACjB,gBAAM,OAAO,MAAM,KAAK,SAAS,iBAAiB,mBAAmB,CAAC;AACtE,gBAAM,MAAM,KAAK,QAAQ,EAAE,aAAkC;AAC7D,gBAAM,UAAU,EAAE,QAAQ,gBACrB,MAAM,KAAK,KAAK,UAChB,MAAM,IAAI,KAAK,UAAU,KAAK;AACnC,eAAK,OAAO,EAAE,MAAM;AACpB,eAAK,OAAO,EAAE,MAAM;AAAA,QACtB;AAAA,MACF,CAAC;AACD,eAAS,YAAY,GAAG;AAAA,IAC1B;AACA,aAAS,YAAY,QAAQ;AAC7B,aAAS,YAAY,QAAQ;AAG7B,UAAM,UAAU,SAAS,cAAc,KAAK;AAC5C,YAAQ,YAAY;AACpB,UAAM,WAAW,SAAS,cAAc,OAAO;AAC/C,aAAS,YAAY;AACrB,UAAM,aAAa,SAAS,cAAc,OAAO;AACjD,eAAW,OAAO;AAClB,eAAW,iBAAiB,UAAU,MAAM,KAAK,mBAAmB,WAAW,OAAO,CAAC;AACvF,SAAK,oBAAoB;AACzB,UAAM,UAAU,SAAS,cAAc,MAAM;AAC7C,YAAQ,cAAc;AACtB,SAAK,mBAAmB;AACxB,aAAS,YAAY,UAAU;AAC/B,aAAS,YAAY,OAAO;AAC5B,YAAQ,YAAY,QAAQ;AAG5B,UAAM,UAAU,SAAS,cAAc,KAAK;AAC5C,YAAQ,YAAY;AACpB,UAAM,SAAS,SAAS,cAAc,QAAQ;AAC9C,WAAO,YAAY;AACnB,WAAO,cAAc;AACrB,WAAO,iBAAiB,SAAS,CAAC,MAAM;AACtC,QAAE,eAAe;AACjB,WAAK,aAAa,KAAK;AAAA,IACzB,CAAC;AAED,UAAM,UAAU,SAAS,cAAc,QAAQ;AAC/C,YAAQ,YAAY;AACpB,YAAQ,cAAc;AACtB,YAAQ,iBAAiB,SAAS,CAAC,MAAM;AACvC,QAAE,eAAe;AACjB,WAAK,aAAa,MAAM;AAAA,IAC1B,CAAC;AAED,YAAQ,YAAY,MAAM;AAC1B,YAAQ,YAAY,OAAO;AAE3B,SAAK,YAAY,SAAS;AAC1B,SAAK,YAAY,QAAQ;AACzB,SAAK,YAAY,OAAO;AACxB,SAAK,YAAY,OAAO;AAExB,SAAK,YAAY,MAAM;AACvB,SAAK,YAAY,IAAI;AAErB,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,mBAAmB,SAAiC;AAChE,QAAI,CAAC,SAAS;AACZ,WAAK,qBAAqB;AAC1B;AAAA,IACF;AAEA,QAAI,CAAC,KAAK,oBAAoB;AAC5B,WAAK,0BAA0B;AAC/B;AAAA,IACF;AAEA,QAAI,KAAK,kBAAkB;AACzB,YAAM,YAAY,KAAK,iBAAiB,cAAc,MAAM;AAC5D,UAAI,UAAW,WAAU,cAAc;AAAA,IACzC;AAEA,UAAM,UAAU,MAAM,QAAkB,KAAK,kBAAkB;AAE/D,QAAI,SAAS;AACX,WAAK,qBAAqB;AAC1B,UAAI,KAAK,kBAAkB;AACzB,cAAM,YAAY,KAAK,iBAAiB,cAAc,MAAM;AAC5D,YAAI,UAAW,WAAU,cAAc;AAAA,MACzC;AAAA,IACF,OAAO;AACL,WAAK,0BAA0B;AAAA,IACjC;AAAA,EACF;AAAA,EAEQ,4BAAkC;AACxC,SAAK,qBAAqB;AAC1B,QAAI,KAAK,mBAAmB;AAC1B,WAAK,kBAAkB,UAAU;AACjC,WAAK,kBAAkB,WAAW;AAAA,IACpC;AACA,QAAI,KAAK,kBAAkB;AACzB,WAAK,iBAAiB,UAAU,IAAI,YAAY,aAAa;AAC7D,YAAM,YAAY,KAAK,iBAAiB,cAAc,MAAM;AAC5D,UAAI,UAAW,WAAU,cAAc;AAAA,IACzC;AAAA,EACF;AAAA,EAEQ,aAAa,QAA8B;AACjD,QAAI,CAAC,KAAK,mBAAmB,CAAC,KAAK,cAAe;AAElD,UAAM,cAAc,KAAK,cAAc,MAAM,KAAK;AAClD,QAAI,CAAC,YAAa;AAElB,UAAM,cAAkC;AAAA,MACtC,cAAc,KAAK,gBAAgB;AAAA,MACnC,cAAc,KAAK,gBAAgB;AAAA,MACnC,oBAAoB,KAAK,gBAAgB;AAAA,MACzC,cAAc,KAAK,gBAAgB;AAAA,MACnC,KAAK,OAAO,WAAW,cAAc,OAAO,SAAS,OAAO;AAAA;AAAA;AAAA,MAG5D,OAAO,OAAO,WAAW,cACrB,OAAO,SAAS,WAAW,OAAO,SAAS,SAAS,OAAO,SAAS,OACpE;AAAA,MACJ,YAAY,OAAO,aAAa,cAAc,SAAS,QAAQ;AAAA,MAC/D,UAAU,OAAO,WAAW,cACxB,EAAE,OAAO,OAAO,YAAY,QAAQ,OAAO,YAAY,IACvD;AAAA,MACJ,YAAY,OAAO,cAAc,cAAc,UAAU,YAAY;AAAA,MACrE,GAAI,KAAK,qBAAqB,EAAE,qBAAqB,KAAK,mBAAmB,IAAI,CAAC;AAAA,IACpF;AAEA,UAAM,SAA0B;AAAA,MAC9B;AAAA,MACA,UAAU,KAAK;AAAA,MACf;AAAA,IACF;AAEA,QAAI,WAAW,OAAO;AACpB,WAAK,WAAW,YAAY,MAAM;AAAA,IACpC,OAAO;AACL,WAAK,WAAW,UAAU,MAAM;AAAA,IAClC;AAAA,EACF;AAAA,EAoCQ,uBAAsC;AAC5C,QAAI,CAAC,KAAK,QAAS,QAAO,CAAC;AAC3B,WAAO,MAAM;AAAA,MACX,KAAK,QAAQ;AAAA,QACX;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;ACnXO,SAAS,eAAe,OAA2C;AACxE,SAAO,CAAC,SAAS,MAAM,KAAK,MAAM;AACpC;AAOO,SAAS,UAAU,aAA6B;AACrD,QAAM,WAAW,eAAe,IAAI,KAAK,EAAE,QAAQ,QAAQ,GAAG;AAC9D,MAAI,QAAQ,UAAU,GAAI,QAAO;AAEjC,QAAM,QAAQ,QAAQ,MAAM,GAAG,EAAE;AACjC,QAAM,YAAY,MAAM,YAAY,GAAG;AACvC,QAAM,OAAO,YAAY,KAAK,MAAM,MAAM,GAAG,SAAS,IAAI;AAC1D,SAAO,OAAO;AAChB;;;ACbA,IAAM,SAAS;AACf,IAAM,iBAAiB;AAWvB,SAAS,kBAAkB,SAAyC;AAClE,MAAI,CAAC,QAAQ,cAAc,oBAAqB,QAAO;AACvD,QAAM,EAAE,qBAAqB,OAAO,GAAG,KAAK,IAAI,QAAQ;AACxD,SAAO,EAAE,GAAG,SAAS,cAAc,KAAK;AAC1C;AAOA,SAAS,aAAa,YAA4B;AAChD,QAAM,OAAO,IAAI,YAAY,EAAE,OAAO,UAAU;AAEhD,MAAI,SAAS;AACb,aAAW,KAAK,KAAM,WAAU,OAAO,aAAa,CAAC;AACrD,QAAM,WAAW,KAAK,MAAM;AAE5B,SAAO,SAAS,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,EAAE;AAC3E;AAEA,SAAS,aAAa,SAAyB;AAC7C,QAAM,WAAW,QAAQ,QAAQ,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG;AAC7D,QAAM,SAAS,WAAW,IAAI,QAAQ,IAAK,SAAS,SAAS,KAAM,CAAC;AACpE,QAAM,SAAS,KAAK,MAAM;AAC1B,QAAM,QAAQ,IAAI,WAAW,OAAO,MAAM;AAC1C,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,IAAK,OAAM,CAAC,IAAI,OAAO,WAAW,CAAC;AACtE,SAAO,IAAI,YAAY,EAAE,OAAO,KAAK;AACvC;AAUO,SAAS,0BAA0B,UAAoC;AAC5E,QAAM,WAAW,SAAS,IAAI,iBAAiB;AAC/C,QAAM,UAA4B,EAAE,GAAG,gBAAgB,UAAU,SAAS;AAC1E,QAAM,OAAO,KAAK,UAAU,OAAO;AACnC,SAAO,SAAS,aAAa,IAAI;AACnC;AAYO,SAAS,4BACd,KACyE;AACzE,MAAI,OAAO,QAAQ,YAAY,IAAI,WAAW,GAAG;AAC/C,WAAO,EAAE,IAAI,OAAO,OAAO,kBAAkB;AAAA,EAC/C;AACA,QAAM,UAAU,IAAI,KAAK;AACzB,MAAI,CAAC,QAAQ,WAAW,MAAM,GAAG;AAC/B,WAAO,EAAE,IAAI,OAAO,OAAO,6CAA6C;AAAA,EAC1E;AACA,QAAM,MAAM,QAAQ,MAAM,OAAO,MAAM;AAEvC,MAAI;AACJ,MAAI;AACF,WAAO,aAAa,GAAG;AAAA,EACzB,QAAQ;AACN,WAAO,EAAE,IAAI,OAAO,OAAO,mBAAmB;AAAA,EAChD;AAEA,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,IAAI;AAAA,EAC1B,QAAQ;AACN,WAAO,EAAE,IAAI,OAAO,OAAO,yBAAyB;AAAA,EACtD;AAEA,MAAI,CAAC,UAAU,OAAO,WAAW,UAAU;AACzC,WAAO,EAAE,IAAI,OAAO,OAAO,2BAA2B;AAAA,EACxD;AACA,QAAM,MAAM;AACZ,MAAI,IAAI,MAAM,gBAAgB;AAC5B,WAAO,EAAE,IAAI,OAAO,OAAO,8BAA8B,IAAI,CAAC,GAAG;AAAA,EACnE;AACA,MAAI,CAAC,MAAM,QAAQ,IAAI,QAAQ,GAAG;AAChC,WAAO,EAAE,IAAI,OAAO,OAAO,mCAAmC;AAAA,EAChE;AAEA,QAAM,kBAAqC,CAAC,OAAO,UAAU,MAAM;AACnE,QAAM,WAA6B,CAAC;AACpC,WAAS,IAAI,GAAG,IAAI,IAAI,SAAS,QAAQ,KAAK;AAC5C,UAAM,IAAI,IAAI,SAAS,CAAC;AACxB,QAAI,OAAO,GAAG,gBAAgB,YAAY,EAAE,YAAY,KAAK,MAAM,IAAI;AACrE,aAAO,EAAE,IAAI,OAAO,OAAO,WAAW,CAAC,wBAAwB;AAAA,IACjE;AACA,QAAI,OAAO,EAAE,aAAa,YAAY,CAAC,gBAAgB,SAAS,EAAE,QAA2B,GAAG;AAC9F,aAAO,EAAE,IAAI,OAAO,OAAO,WAAW,CAAC,2BAA2B,EAAE,QAAQ,IAAI;AAAA,IAClF;AACA,QAAI,CAAC,EAAE,gBAAgB,OAAO,EAAE,iBAAiB,UAAU;AACzD,aAAO,EAAE,IAAI,OAAO,OAAO,WAAW,CAAC,yBAAyB;AAAA,IAClE;AACA,aAAS,KAAK;AAAA,MACZ,OAAO,OAAO,EAAE,UAAU,WAAW,EAAE,QAAQ;AAAA,MAC/C,aAAa,EAAE;AAAA,MACf,UAAU,EAAE;AAAA,MACZ,cAAc,EAAE;AAAA,MAChB,GAAI,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,iBAAiB,IAAI,CAAC;AAAA,IACvE,CAAC;AAAA,EACH;AAEA,SAAO,EAAE,IAAI,MAAM,SAAS;AAC9B;;;ACpHO,IAAM,aAAN,MAAiB;AAAA,EAUtB,YAAY,SAAsB,WAAgC;AAPlE,SAAQ,WAAkC;AAC1C,SAAQ,UAAiC;AACzC,SAAQ,YAAmC;AAC3C,SAAQ,WAAqC;AAC7C,SAAQ,UAAU;AAClB,SAAQ,kBAAyC;AAG/C,SAAK,WAAW;AAChB,SAAK,aAAa;AAAA,EACpB;AAAA,EAEA,IAAI,SAAkB;AACpB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,OAAa;AACX,QAAI,KAAK,QAAS;AAElB,UAAM,QAAQ,KAAK,WAAW;AAC9B,SAAK,WAAW;AAChB,SAAK,SAAS,YAAY,KAAK;AAC/B,SAAK,UAAU;AAEf,SAAK,YAAY;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA,EAKA,OAAa;AACX,SAAK,UAAU,OAAO;AACtB,SAAK,WAAW;AAChB,SAAK,UAAU;AACf,SAAK,YAAY;AACjB,SAAK,WAAW;AAChB,SAAK,UAAU;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA,EAKA,UAAgB;AACd,QAAI,CAAC,KAAK,QAAS;AACnB,SAAK,YAAY;AAAA,EACnB;AAAA;AAAA,EAIQ,aAA6B;AACnC,UAAM,QAAQ,SAAS,cAAc,KAAK;AAC1C,UAAM,YAAY;AAClB,UAAM,aAAa,QAAQ,QAAQ;AACnC,UAAM,aAAa,cAAc,MAAM;AACvC,UAAM,aAAa,cAAc,yBAAyB;AAG1D,UAAM,SAAS,SAAS,cAAc,KAAK;AAC3C,WAAO,YAAY;AACnB,UAAM,aAAa,SAAS,cAAc,KAAK;AAC/C,eAAW,YAAY;AACvB,eAAW,YAAY,gBAAgB,EAAE,CAAC;AAC1C,UAAM,QAAQ,SAAS,cAAc,KAAK;AAC1C,UAAM,YAAY;AAClB,UAAM,cAAc;AACpB,UAAM,WAAW,SAAS,cAAc,QAAQ;AAChD,aAAS,YAAY;AACrB,aAAS,cAAc;AACvB,aAAS,aAAa,cAAc,mBAAmB;AACvD,aAAS,iBAAiB,SAAS,MAAM,KAAK,KAAK,CAAC;AACpD,WAAO,YAAY,UAAU;AAC7B,WAAO,YAAY,KAAK;AACxB,WAAO,YAAY,QAAQ;AAG3B,UAAM,SAAS,SAAS,cAAc,KAAK;AAC3C,WAAO,YAAY;AACnB,WAAO,MAAM,UAAU;AACvB,SAAK,YAAY;AAGjB,UAAM,OAAO,SAAS,cAAc,KAAK;AACzC,SAAK,YAAY;AACjB,SAAK,UAAU;AAGf,UAAM,SAAS,SAAS,cAAc,KAAK;AAC3C,WAAO,YAAY;AAEnB,UAAM,WAAW,SAAS,cAAc,QAAQ;AAChD,aAAS,YAAY;AACrB,aAAS,cAAc;AACvB,aAAS,iBAAiB,SAAS,MAAM,KAAK,eAAe,CAAC;AAE9D,UAAM,UAAU,SAAS,cAAc,QAAQ;AAC/C,YAAQ,YAAY;AACpB,YAAQ,aAAa,cAAc,gCAAgC;AACnE,YAAQ,QAAQ;AAChB,YAAQ,cAAc;AACtB,YAAQ,iBAAiB,SAAS,MAAM,KAAK,sBAAsB,CAAC;AAEpE,UAAM,UAAU,SAAS,cAAc,QAAQ;AAC/C,YAAQ,YAAY;AACpB,YAAQ,cAAc;AACtB,YAAQ,iBAAiB,SAAS,MAAM,KAAK,gBAAgB,CAAC;AAC9D,SAAK,WAAW;AAEhB,WAAO,YAAY,QAAQ;AAC3B,WAAO,YAAY,OAAO;AAC1B,WAAO,YAAY,OAAO;AAE1B,UAAM,YAAY,MAAM;AACxB,UAAM,YAAY,MAAM;AACxB,UAAM,YAAY,IAAI;AACtB,UAAM,YAAY,MAAM;AAGxB,UAAM,iBAAiB,WAAW,CAAC,MAAM;AACvC,UAAI,EAAE,QAAQ,UAAU;AACtB,UAAE,eAAe;AACjB,aAAK,KAAK;AAAA,MACZ;AAAA,IACF,CAAC;AAED,WAAO;AAAA,EACT;AAAA,EAEQ,cAAoB;AAC1B,QAAI,CAAC,KAAK,QAAS;AAEnB,UAAM,QAAQ,KAAK,WAAW,iBAAiB;AAG/C,SAAK,QAAQ,YAAY;AAEzB,QAAI,MAAM,WAAW,GAAG;AACtB,YAAM,QAAQ,SAAS,cAAc,KAAK;AAC1C,YAAM,YAAY;AAClB,YAAM,cAAc;AACpB,WAAK,QAAQ,YAAY,KAAK;AAE9B,UAAI,KAAK,UAAU;AACjB,aAAK,SAAS,cAAc;AAC5B,aAAK,SAAS,WAAW;AAAA,MAC3B;AACA;AAAA,IACF;AAEA,QAAI,KAAK,UAAU;AACjB,WAAK,SAAS,cAAc,eAAe,MAAM,MAAM;AACvD,WAAK,SAAS,WAAW;AAAA,IAC3B;AAEA,UAAM,QAAQ,CAAC,MAAM,UAAU;AAC7B,YAAM,MAAM,KAAK,aAAa,MAAM,KAAK;AACzC,WAAK,QAAS,YAAY,GAAG;AAAA,IAC/B,CAAC;AAAA,EACH;AAAA,EAEQ,aAAa,MAAsB,OAA+B;AACxE,UAAM,MAAM,SAAS,cAAc,KAAK;AACxC,QAAI,YAAY;AAChB,QAAI,QAAQ,QAAQ,OAAO,KAAK;AAGhC,UAAM,QAAQ,SAAS,cAAc,KAAK;AAC1C,UAAM,YAAY;AAClB,QAAI,KAAK,aAAa,qBAAqB;AACzC,YAAM,MAAM,SAAS,cAAc,KAAK;AACxC,UAAI,MAAM,KAAK,aAAa;AAC5B,UAAI,MAAM;AACV,YAAM,YAAY,GAAG;AAAA,IACvB;AAGA,UAAM,UAAU,SAAS,cAAc,KAAK;AAC5C,YAAQ,YAAY;AAEpB,UAAM,UAAU,SAAS,cAAc,KAAK;AAC5C,YAAQ,YAAY;AACpB,QAAI,eAAe,KAAK,KAAK,GAAG;AAG9B,cAAQ,UAAU,IAAI,kBAAkB;AACxC,cAAQ,cAAc,UAAU,KAAK,WAAW,KAAK;AAAA,IACvD,OAAO;AACL,cAAQ,cAAc,KAAK;AAAA,IAC7B;AAEA,UAAM,SAAS,SAAS,cAAc,KAAK;AAC3C,WAAO,YAAY;AACnB,UAAM,mBAAmB,KAAK,aAAa,gBAAgB,IAAI,UAAU,GAAG,EAAE;AAC9E,WAAO,cAAc,GAAG,eAAe,SAAM,KAAK,QAAQ;AAE1D,YAAQ,YAAY,OAAO;AAC3B,YAAQ,YAAY,MAAM;AAG1B,UAAM,UAAU,SAAS,cAAc,KAAK;AAC5C,YAAQ,YAAY;AAEpB,UAAM,UAAU,SAAS,cAAc,QAAQ;AAC/C,YAAQ,YAAY;AACpB,YAAQ,cAAc;AACtB,YAAQ,aAAa,cAAc,gBAAgB,QAAQ,CAAC,EAAE;AAC9D,YAAQ,iBAAiB,SAAS,CAAC,MAAM;AACvC,QAAE,gBAAgB;AAClB,WAAK,cAAc,KAAK,MAAM,KAAK;AAAA,IACrC,CAAC;AAED,UAAM,YAAY,SAAS,cAAc,QAAQ;AACjD,cAAU,YAAY;AACtB,cAAU,cAAc;AACxB,cAAU,aAAa,cAAc,kBAAkB,QAAQ,CAAC,EAAE;AAClE,cAAU,iBAAiB,SAAS,CAAC,MAAM;AACzC,QAAE,gBAAgB;AAClB,WAAK,WAAW,aAAa,KAAK;AAClC,WAAK,QAAQ;AAAA,IACf,CAAC;AAED,YAAQ,YAAY,OAAO;AAC3B,YAAQ,YAAY,SAAS;AAE7B,QAAI,YAAY,KAAK;AACrB,QAAI,YAAY,OAAO;AACvB,QAAI,YAAY,OAAO;AAEvB,WAAO;AAAA,EACT;AAAA,EAEQ,cAAc,KAAqB,MAAsB,OAAqB;AAEpF,UAAM,eAAe,IAAI,cAAc,qBAAqB;AAC5D,QAAI,cAAc;AAChB,mBAAa,OAAO;AACpB;AAAA,IACF;AAEA,UAAM,WAAW,SAAS,cAAc,KAAK;AAC7C,aAAS,YAAY;AACrB,aAAS,MAAM,UAAU;AAEzB,UAAM,aAAa,SAAS,cAAc,OAAO;AACjD,eAAW,YAAY;AACvB,eAAW,QAAQ,KAAK;AACxB,eAAW,MAAM,eAAe;AAEhC,UAAM,YAAY,SAAS,cAAc,UAAU;AACnD,cAAU,YAAY;AACtB,cAAU,QAAQ,KAAK;AACvB,cAAU,OAAO;AACjB,cAAU,MAAM,eAAe;AAE/B,UAAM,WAAW,SAAS,cAAc,KAAK;AAC7C,aAAS,YAAY;AACrB,aAAS,aAAa,QAAQ,OAAO;AAErC,QAAI,eAAgC,KAAK;AACzC,UAAM,aAAgC,CAAC,OAAO,UAAU,MAAM;AAC9D,eAAW,OAAO,YAAY;AAC5B,YAAM,MAAM,SAAS,cAAc,QAAQ;AAC3C,UAAI,YAAY;AAChB,UAAI,QAAQ,MAAM;AAClB,UAAI,cAAc,IAAI,OAAO,CAAC,EAAE,YAAY,IAAI,IAAI,MAAM,CAAC;AAC3D,UAAI,aAAa,gBAAgB,QAAQ,eAAe,SAAS,OAAO;AACxE,UAAI,QAAQ,aAAc,KAAI,UAAU,IAAI,QAAQ;AACpD,UAAI,iBAAiB,SAAS,CAAC,MAAM;AACnC,UAAE,eAAe;AACjB,uBAAe;AACf,iBAAS,iBAAiB,mBAAmB,EAAE,QAAQ,CAAC,MAAM;AAC5D,gBAAM,OAAQ,EAAwB,QAAQ;AAC9C,YAAE,aAAa,gBAAgB,SAAS,MAAM,SAAS,OAAO;AAC9D,YAAE,UAAU,OAAO,UAAU,SAAS,GAAG;AAAA,QAC3C,CAAC;AAAA,MACH,CAAC;AACD,eAAS,YAAY,GAAG;AAAA,IAC1B;AAEA,UAAM,UAAU,SAAS,cAAc,QAAQ;AAC/C,YAAQ,YAAY;AACpB,YAAQ,cAAc;AACtB,YAAQ,MAAM,YAAY;AAC1B,YAAQ,iBAAiB,SAAS,CAAC,MAAM;AACvC,QAAE,eAAe;AACjB,WAAK,WAAW,WAAW,OAAO,WAAW,MAAM,KAAK,GAAG,UAAU,MAAM,KAAK,GAAG,YAAY;AAC/F,WAAK,QAAQ;AAAA,IACf,CAAC;AAED,aAAS,YAAY,UAAU;AAC/B,aAAS,YAAY,SAAS;AAC9B,aAAS,YAAY,QAAQ;AAC7B,aAAS,YAAY,OAAO;AAE5B,QAAI,YAAY,QAAQ;AACxB,eAAW,MAAM;AAAA,EACnB;AAAA;AAAA,EAIA,MAAc,kBAAiC;AAC7C,QAAI,CAAC,KAAK,SAAU;AAEpB,UAAM,eAAe,KAAK,SAAS;AACnC,SAAK,SAAS,WAAW;AACzB,SAAK,SAAS,cAAc;AAC5B,SAAK,WAAW;AAGhB,QAAI,KAAK,SAAS;AAChB,WAAK,QAAQ,MAAM,UAAU;AAC7B,WAAK,QAAQ,MAAM,gBAAgB;AAAA,IACrC;AAEA,QAAI;AACF,YAAM,SAAS,MAAM,KAAK,WAAW,YAAY;AACjD,WAAK,iBAAiB,MAAM;AAAA,IAC9B,SAAS,KAAK;AACZ,WAAK,gBAAgB,GAA0B;AAAA,IACjD,UAAE;AACA,UAAI,KAAK,SAAS;AAChB,aAAK,QAAQ,MAAM,UAAU;AAC7B,aAAK,QAAQ,MAAM,gBAAgB;AAAA,MACrC;AACA,UAAI,KAAK,UAAU;AACjB,aAAK,SAAS,WAAW;AACzB,aAAK,SAAS,cAAc,gBAAgB;AAAA,MAC9C;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,iBAAiB,QAA2B;AAC1C,SAAK,iBAAiB,MAAM;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAgB,KAAgC;AAC9C,SAAK,gBAAgB,GAAG;AAAA,EAC1B;AAAA,EAEQ,iBAAiB,QAA2B;AAClD,UAAM,QAAQ,OAAO,QAAQ,SAAS,OAAO,OAAO;AAEpD,QAAI,OAAO,OAAO,WAAW,KAAK,OAAO,QAAQ,SAAS,GAAG;AAE3D,WAAK,UAAU,WAAW,UAAK,OAAO,QAAQ,MAAM,IAAI,OAAO,QAAQ,WAAW,IAAI,YAAY,UAAU,gBAAgB;AAE5H,iBAAW,MAAM,KAAK,KAAK,GAAG,GAAI;AAAA,IACpC,WAAW,OAAO,QAAQ,SAAS,KAAK,OAAO,OAAO,SAAS,GAAG;AAEhE,WAAK,WAAW,QAAQ,GAAG,OAAO,QAAQ,MAAM,OAAO,KAAK,mBAAmB,OAAO,OAAO,MAAM,UAAU;AAC7G,WAAK,UAAU,QAAQ,GAAG,OAAO,QAAQ,MAAM,OAAO,KAAK,gBAAgB;AAAA,IAC7E,WAAW,OAAO,OAAO,SAAS,KAAK,OAAO,QAAQ,WAAW,GAAG;AAElE,YAAM,SAAS,OAAO,OAAO,IAAI,OAAK,EAAE,KAAK,EAAE,KAAK,IAAI;AACxD,WAAK,WAAW,SAAS,uBAAkB,MAAM,6CAAwC;AAAA,IAC3F;AAEA,SAAK,QAAQ;AAAA,EACf;AAAA,EAEQ,gBAAgB,KAAgC;AACtD,UAAM,cAAc,UAAU;AAC9B,UAAM,OAAO,cAAe,IAAoB,OAAO;AAEvD,QAAI,SAAS,cAAc;AACzB,WAAK;AAAA,QACH;AAAA,QACA;AAAA,MACF;AAAA,IACF,OAAO;AACL,YAAM,MAAM,IAAI,WAAW;AAC3B,WAAK,WAAW,SAAS,uBAAkB,GAAG,6CAAwC;AAAA,IACxF;AAAA,EACF;AAAA,EAEA,MAAc,wBAAuC;AACnD,UAAM,QAAQ,KAAK,WAAW,iBAAiB;AAC/C,QAAI,MAAM,WAAW,GAAG;AACtB,WAAK,UAAU,QAAQ,qBAAqB;AAC5C;AAAA,IACF;AAEA,UAAM,UAAU,0BAA0B,KAAK;AAE/C,QAAI;AACF,UAAI,KAAK;AACT,UAAI,KAAK,WAAW,mBAAmB;AACrC,aAAK,MAAM,KAAK,WAAW,kBAAkB,KAAK;AAAA,MACpD,WAAW,OAAO,cAAc,eAAe,UAAU,WAAW;AAClE,cAAM,UAAU,UAAU,UAAU,OAAO;AAC3C,aAAK;AAAA,MACP;AAEA,UAAI,IAAI;AACN,aAAK;AAAA,UACH;AAAA,UACA,iBAAY,MAAM,MAAM,IAAI,MAAM,WAAW,IAAI,YAAY,UAAU;AAAA,QACzE;AAAA,MACF,OAAO;AACL,aAAK,UAAU,SAAS,8CAAyC;AAAA,MACnE;AAAA,IACF,QAAQ;AACN,WAAK,UAAU,SAAS,kDAAwC;AAAA,IAClE;AAAA,EACF;AAAA,EAEQ,iBAAuB;AAC7B,UAAM,QAAQ,KAAK,WAAW,iBAAiB;AAC/C,QAAI,MAAM,SAAS,GAAG;AAEpB,UAAI,CAAC,OAAO,QAAQ,aAAa,MAAM,MAAM,YAAY,EAAG;AAAA,IAC9D;AACA,SAAK,WAAW,WAAW;AAC3B,SAAK,QAAQ;AAAA,EACf;AAAA;AAAA,EAIQ,WAAW,MAAwB,SAAuB;AAChE,QAAI,CAAC,KAAK,UAAW;AACrB,SAAK,UAAU,YAAY,yBAAyB,IAAI;AACxD,SAAK,UAAU,cAAc;AAC7B,SAAK,UAAU,MAAM,UAAU;AAAA,EACjC;AAAA,EAEQ,aAAmB;AACzB,QAAI,CAAC,KAAK,UAAW;AACrB,SAAK,UAAU,MAAM,UAAU;AAAA,EACjC;AAAA;AAAA,EAIQ,UAAU,SAAuC,SAAuB;AAE9E,QAAI,CAAC,KAAK,iBAAiB;AACzB,WAAK,kBAAkB,SAAS,cAAc,KAAK;AACnD,WAAK,gBAAgB,YAAY;AACjC,WAAK,SAAS,YAAY,KAAK,eAAe;AAAA,IAChD;AAEA,UAAM,QAAQ,SAAS,cAAc,KAAK;AAC1C,UAAM,YAAY,kBAAkB,OAAO;AAC3C,UAAM,aAAa,QAAQ,YAAY,UAAU,UAAU,QAAQ;AACnE,UAAM,aAAa,aAAa,YAAY,UAAU,cAAc,QAAQ;AAC5E,UAAM,YAAY,SAAS,cAAc,MAAM;AAC/C,cAAU,YAAY;AACtB,cAAU,YAAY,gBAAgB,EAAE,CAAC;AACzC,UAAM,WAAW,SAAS,cAAc,MAAM;AAC9C,aAAS,cAAc;AACvB,UAAM,YAAY,SAAS;AAC3B,UAAM,YAAY,QAAQ;AAE1B,SAAK,gBAAgB,YAAY,KAAK;AAGtC,WAAO,KAAK,gBAAgB,SAAS,SAAS,GAAG;AAC/C,WAAK,gBAAgB,YAAY,KAAK,gBAAgB,UAAW;AAAA,IACnE;AAGA,UAAM,WAAW,YAAY,UAAU,MAAO,YAAY,YAAY,MAAO;AAC7E,eAAW,MAAM;AACf,YAAM,OAAO;AAAA,IACf,GAAG,QAAQ;AAAA,EACb;AAAA;AAAA,EAIA,UAAgB;AACd,SAAK,KAAK;AACV,SAAK,iBAAiB,OAAO;AAC7B,SAAK,kBAAkB;AAAA,EACzB;AACF;;;ACxfA,IAAM,cAAc;AACpB,IAAM,qBAAqB,IAAI,KAAK,KAAK;AAElC,IAAM,kBAAN,MAAsB;AAAA,EAAtB;AACL,SAAQ,QAA4B;AACpC,SAAQ,UAAmC;AAC3C,SAAQ,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMnB,MAAM,SAA4B;AAChC,QAAI,KAAK,SAAU;AAEnB,UAAM,OAAO,SAAS,cAAc,KAAK;AACzC,SAAK,YAAY;AACjB,SAAK,aAAa,eAAe,MAAM;AACvC,SAAK,MAAM,UAAU;AAGrB,UAAM,QAAQ;AACd,UAAM,MAAM,SAAS,gBAAgB,OAAO,KAAK;AACjD,QAAI,aAAa,SAAS,sBAAsB;AAChD,QAAI,aAAa,WAAW,WAAW;AACvC,QAAI,aAAa,SAAS,IAAI;AAC9B,QAAI,aAAa,UAAU,IAAI;AAE/B,UAAM,WAAW,SAAS,gBAAgB,OAAO,QAAQ;AACzD,aAAS,aAAa,MAAM,IAAI;AAChC,aAAS,aAAa,MAAM,IAAI;AAChC,aAAS,aAAa,KAAK,OAAO,WAAW,CAAC;AAC9C,aAAS,aAAa,SAAS,yBAAyB;AAExD,UAAM,WAAW,SAAS,gBAAgB,OAAO,QAAQ;AACzD,aAAS,aAAa,MAAM,IAAI;AAChC,aAAS,aAAa,MAAM,IAAI;AAChC,aAAS,aAAa,KAAK,OAAO,WAAW,CAAC;AAC9C,aAAS,aAAa,SAAS,yBAAyB;AACxD,aAAS,aAAa,oBAAoB,OAAO,kBAAkB,CAAC;AAEpE,aAAS,aAAa,qBAAqB,OAAO,kBAAkB,CAAC;AAErE,QAAI,YAAY,QAAQ;AACxB,QAAI,YAAY,QAAQ;AACxB,SAAK,YAAY,GAAG;AAGpB,SAAK,YAAY,gBAAgB,EAAE,CAAC;AAEpC,YAAQ,YAAY,IAAI;AAExB,SAAK,QAAQ;AACb,SAAK,UAAU;AACf,SAAK,WAAW;AAAA,EAClB;AAAA;AAAA,EAGA,IAAI,YAAqB;AACvB,WAAO,KAAK,YAAY,KAAK,UAAU,QAAQ,KAAK,MAAM;AAAA,EAC5D;AAAA;AAAA,EAGA,OAAO,GAAW,GAAiB;AACjC,QAAI,CAAC,KAAK,MAAO;AACjB,SAAK,MAAM,MAAM,OAAO,GAAG,CAAC;AAC5B,SAAK,MAAM,MAAM,MAAM,GAAG,CAAC;AAC3B,SAAK,MAAM,MAAM,UAAU;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,YAAY,UAAwB;AAClC,QAAI,CAAC,KAAK,SAAS,CAAC,KAAK,QAAS;AAClC,UAAM,UAAU,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,QAAQ,CAAC;AACjD,UAAM,SAAS,sBAAsB,IAAI;AACzC,SAAK,QAAQ,aAAa,qBAAqB,OAAO,MAAM,CAAC;AAC7D,QAAI,WAAW,GAAG;AAChB,WAAK,MAAM,UAAU,IAAI,SAAS;AAAA,IACpC,OAAO;AACL,WAAK,MAAM,UAAU,OAAO,SAAS;AAAA,IACvC;AAAA,EACF;AAAA;AAAA,EAGA,OAAa;AACX,QAAI,CAAC,KAAK,MAAO;AACjB,SAAK,MAAM,MAAM,UAAU;AAC3B,SAAK,MAAM,UAAU,OAAO,SAAS;AAErC,QAAI,KAAK,SAAS;AAChB,WAAK,QAAQ;AAAA,QACX;AAAA,QACA,OAAO,kBAAkB;AAAA,MAC3B;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGA,UAAgB;AACd,QAAI,KAAK,SAAS,KAAK,MAAM,eAAe;AAC1C,WAAK,MAAM,cAAc,YAAY,KAAK,KAAK;AAAA,IACjD;AACA,SAAK,QAAQ;AACb,SAAK,UAAU;AACf,SAAK,WAAW;AAAA,EAClB;AACF;;;AC1GO,IAAM,kBAAN,MAAsB;AAAA,EAO3B,YAAY,WAAqC;AANjD,SAAQ,QAA+B;AAEvC,SAAQ,wBAA0D;AAClE,SAAQ,kBAAuD;AAC/D,SAAQ,mBAAwC;AAG9C,SAAK,aAAa;AAAA,EACpB;AAAA,EAEA,IAAI,SAAkB;AACpB,WAAO,KAAK,UAAU,QAAQ,KAAK,MAAM;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,SAAsB,GAAW,GAAiB;AACvD,SAAK,KAAK;AAEV,UAAM,OAAO,SAAS,cAAc,KAAK;AACzC,SAAK,YAAY;AACjB,SAAK,aAAa,QAAQ,MAAM;AAChC,SAAK,aAAa,cAAc,uBAAuB;AAGvD,UAAM,YAAY;AAClB,UAAM,aAAa;AACnB,UAAM,WAAW,KAAK,IAAI,GAAG,OAAO,aAAa,YAAY,CAAC;AAC9D,UAAM,WAAW,KAAK,IAAI,GAAG,OAAO,cAAc,aAAa,CAAC;AAChE,SAAK,MAAM,OAAO,GAAG,KAAK,IAAI,GAAG,QAAQ,CAAC;AAC1C,SAAK,MAAM,MAAM,GAAG,KAAK,IAAI,GAAG,QAAQ,CAAC;AAEzC,UAAM,WAAW,SAAS,cAAc,QAAQ;AAChD,aAAS,OAAO;AAChB,aAAS,YAAY;AACrB,aAAS,aAAa,QAAQ,UAAU;AACxC,aAAS,cAAc;AACvB,aAAS,iBAAiB,SAAS,MAAM;AACvC,WAAK,KAAK;AACV,WAAK,WAAW,OAAO;AAAA,IACzB,CAAC;AAED,SAAK,YAAY,QAAQ;AACzB,YAAQ,YAAY,IAAI;AACxB,SAAK,QAAQ;AAKb,SAAK,wBAAwB,CAAC,MAAkB;AAC9C,UAAI,CAAC,KAAK,MAAO;AAEjB,UAAI,EAAE,kBAAkB,QAAQ,KAAK,MAAM,SAAS,EAAE,MAAM,EAAG;AAC/D,WAAK,KAAK;AAAA,IACZ;AAGA,eAAW,MAAM;AACf,UAAI,CAAC,KAAK,MAAO;AACjB,eAAS,iBAAiB,SAAS,KAAK,uBAAwB,IAAI;AACpE,eAAS,iBAAiB,eAAe,KAAK,uBAAwB,IAAI;AAAA,IAC5E,GAAG,CAAC;AAEJ,SAAK,kBAAkB,CAAC,MAAqB;AAC3C,UAAI,EAAE,QAAQ,UAAU;AACtB,UAAE,gBAAgB;AAClB,aAAK,KAAK;AAAA,MACZ;AAAA,IACF;AACA,aAAS,iBAAiB,WAAW,KAAK,iBAAiB,IAAI;AAI/D,SAAK,mBAAmB,MAAM,KAAK,KAAK;AACxC,WAAO,iBAAiB,UAAU,KAAK,kBAAkB,IAAI;AAC7D,WAAO,iBAAiB,UAAU,KAAK,kBAAkB,IAAI;AAAA,EAC/D;AAAA,EAEA,OAAa;AACX,QAAI,KAAK,uBAAuB;AAC9B,eAAS,oBAAoB,SAAS,KAAK,uBAAuB,IAAI;AACtE,eAAS,oBAAoB,eAAe,KAAK,uBAAuB,IAAI;AAC5E,WAAK,wBAAwB;AAAA,IAC/B;AACA,QAAI,KAAK,iBAAiB;AACxB,eAAS,oBAAoB,WAAW,KAAK,iBAAiB,IAAI;AAClE,WAAK,kBAAkB;AAAA,IACzB;AACA,QAAI,KAAK,kBAAkB;AACzB,aAAO,oBAAoB,UAAU,KAAK,kBAAkB,IAAI;AAChE,aAAO,oBAAoB,UAAU,KAAK,kBAAkB,IAAI;AAChE,WAAK,mBAAmB;AAAA,IAC1B;AACA,SAAK,OAAO,OAAO;AACnB,SAAK,QAAQ;AAAA,EACf;AAAA,EAEA,UAAgB;AACd,SAAK,KAAK;AAAA,EACZ;AACF;;;AC9FA,IAAI,UAAkC;AACtC,IAAI,UAAkC;AACtC,IAAI,cAAkC;AACtC,IAAI,SAA+B;AACnC,IAAI,gBAAyC;AAC7C,IAAI,OAA4B;AAChC,IAAI,YAA8B;AAClC,IAAI,aAAgC;AACpC,IAAI,kBAA0C;AAC9C,IAAI,cAAsC;AAC1C,IAAI,QAAsB;AAC1B,IAAI,mBAAwC;AAC5C,IAAI,oBAAyC;AAC7C,IAAI,uBAAgD;AACpD,IAAI,aAAa;AACjB,IAAI,aAAa;AACjB,IAAI,oBAAsD;AAOnD,SAAS,0BAAgC;AAC9C,MAAI,iBAAkB,kBAAiB;AACvC,MAAI,kBAAmB,mBAAkB;AACzC,qBAAmB;AACnB,sBAAoB;AACpB,YAAU;AACV,YAAU;AACV,gBAAc;AACd,WAAS;AACT,kBAAgB;AAChB,SAAO;AACP,cAAY;AACZ,eAAa;AACb,oBAAkB;AAClB,gBAAc;AACd,UAAQ;AACR,yBAAuB;AACvB,eAAa;AACb,eAAa;AACb,sBAAoB;AACtB;AAaO,SAAS,aACd,aACA,UACA,SACA,mBACgB;AAChB,SAAO;AAAA,IACL,OAAO;AAAA,IACP;AAAA,IACA;AAAA;AAAA;AAAA;AAAA;AAAA,IAKA,kBAAkB;AAAA,IAClB,cAAc;AAAA,MACZ,cAAc,QAAQ;AAAA,MACtB,cAAc,QAAQ;AAAA,MACtB,oBAAoB,QAAQ;AAAA,MAC5B,cAAc,QAAQ;AAAA,MACtB,KAAK,OAAO,WAAW,cAAc,OAAO,SAAS,OAAO;AAAA;AAAA;AAAA,MAG5D,OAAO,OAAO,WAAW,cACrB,OAAO,SAAS,WAAW,OAAO,SAAS,SAAS,OAAO,SAAS,OACpE;AAAA,MACJ,YAAY,OAAO,aAAa,cAAc,SAAS,QAAQ;AAAA,MAC/D,UAAU,OAAO,WAAW,cACxB,EAAE,OAAO,OAAO,YAAY,QAAQ,OAAO,YAAY,IACvD;AAAA,MACJ,YAAY,OAAO,cAAc,cAAc,UAAU,YAAY;AAAA,MACrE,GAAI,oBAAoB,EAAE,qBAAqB,kBAAkB,IAAI,CAAC;AAAA,IACxE;AAAA,EACF;AACF;AAMO,SAAS,yBAAyB,IAA2B;AAClE,MAAI,iBAAkB;AAEtB,YAAU;AAEV,qBAAmB,QAAQ,SAAS,MAAM;AACxC,QAAI,UAAU,WAAY;AAE1B,cAAU,IAAI,gBAAgB;AAC9B,YAAQ,MAAM,SAAS,IAAI;AAE3B,UAAM,UAAU,QAAQ,kBAAkB;AAE1C,kBAAc,IAAI,YAAY,OAAO;AAErC,YAAQ;AAGR,sBAAkB,IAAI,gBAAgB;AACtC,oBAAgB,MAAM,OAAO;AAI7B,kBAAc,IAAI,gBAAgB;AAAA,MAChC,QAAQ,MAAM;AACZ,sBAAc;AAAA,MAChB;AAAA,IACF,CAAC;AAKD,wBAAoB,CAAC,MAAkB;AACrC,mBAAa,EAAE;AACf,mBAAa,EAAE;AAAA,IACjB;AACA,aAAS,iBAAiB,aAAa,iBAAiB;AAGxD,oBAAgB,IAAI,iBAAiB,QAAQ,QAAQ,UAAU;AAAA,MAC7D,eAAe,MAAM;AACnB,YAAI,UAAU,OAAQ;AACtB,gBAAQ;AACR,yBAAiB,OAAO,YAAY,UAAU;AAAA,MAChD;AAAA,MACA,kBAAkB,CAAC,SAAS;AAC1B,YAAI,UAAU,WAAY;AAC1B,yBAAiB,YAAY,IAAI;AAEjC,yBAAiB,OAAO,YAAY,UAAU;AAAA,MAChD;AAAA,MACA,WAAW,MAAM;AACf,YAAI,UAAU,WAAY;AAC1B,yBAAiB,KAAK;AAKtB,wBAAgB;AAChB,0BAAkB;AAAA,MACpB;AAAA,MACA,gBAAgB,MAAM;AACpB,YAAI,UAAU,YAAY;AACxB,2BAAiB,KAAK;AACtB,kBAAQ;AAAA,QACV;AAAA,MAMF;AAAA,IACF,CAAC;AACD,kBAAc,MAAM;AAAA,EACtB,CAAC;AAED,sBAAoB,QAAQ,UAAU,MAAM;AAC1C,YAAQ,WAAW;AACnB,mBAAe,KAAK;AACpB,gBAAY,QAAQ;AACpB,eAAW,KAAK;AAChB,iBAAa,QAAQ;AACrB,UAAM,QAAQ;AACd,qBAAiB,QAAQ;AACzB,iBAAa,QAAQ;AACrB,QAAI,mBAAmB;AACrB,eAAS,oBAAoB,aAAa,iBAAiB;AAAA,IAC7D;AACA,aAAS,QAAQ;AAEjB,cAAU;AACV,kBAAc;AACd,aAAS;AACT,oBAAgB;AAChB,WAAO;AACP,gBAAY;AACZ,iBAAa;AACb,sBAAkB;AAClB,kBAAc;AACd,wBAAoB;AACpB,YAAQ;AACR,2BAAuB;AACvB,iBAAa;AACb,iBAAa;AAAA,EACf,CAAC;AACH;AAcA,SAAS,kBAAwB;AAC/B,MAAI,CAAC,WAAW,CAAC,QAAS;AAC1B,QAAM,UAAU,QAAQ,kBAAkB;AAC1C,MAAI,CAAC,QAAS;AAEd,MAAI,CAAC,MAAM;AACT,WAAO,IAAI,aAAa,QAAQ,MAAM;AAAA,MACpC,SAAS,MAAM;AACb,YAAI,UAAU,UAAU,UAAU,aAAa;AAC7C,2BAAiB;AAAA,QACnB;AAAA,MACF;AAAA,MACA,eAAe,CAAC,GAAG,MAAM;AACvB,4BAAoB,GAAG,CAAC;AAAA,MAC1B;AAAA,IACF,CAAC;AACD,SAAK,MAAM,OAAO;AAAA,EACpB;AAEA,MAAI,CAAC,WAAW;AACd,gBAAY,IAAI,UAAU,SAAS;AAAA,MACjC,aAAa,CAAC,WAAW;AACvB,cAAM,MAAM;AACZ,YAAI,CAAC,OAAO,CAAC,QAAS;AACtB,cAAM,UAAU;AAAA,UACd,OAAO;AAAA,UACP,OAAO;AAAA,UACP;AAAA,UACA,OAAO,YAAY,uBAAuB;AAAA,QAC5C;AACA,gBAAQ,MAAM,OAAO;AACrB,yBAAiB;AACjB,mBAAW,KAAK;AAChB,4BAAoB;AAAA,MACtB;AAAA,MACA,WAAW,OAAO,WAAW;AAC3B,cAAM,MAAM;AACZ,YAAI,CAAC,OAAO,CAAC,QAAS;AACtB,cAAM,UAAU;AAAA,UACd,OAAO;AAAA,UACP,OAAO;AAAA,UACP;AAAA,UACA,OAAO,YAAY,uBAAuB;AAAA,QAC5C;AACA,gBAAQ,MAAM,OAAO;AACrB,mBAAW,KAAK;AAChB,YAAI;AACF,gBAAM,cAAc,MAAM,QAAQ,UAAU;AAK5C,sBAAY,iBAAiB,WAAW;AACxC,2BAAiB;AACjB,8BAAoB;AAAA,QACtB,SAAS,KAAK;AACZ,sBAAY,gBAAgB,GAA0B;AACtD,2BAAiB;AACjB,2BAAiB;AAAA,QACnB;AAAA,MACF;AAAA,MACA,UAAU,MAAM;AACd,mBAAW,KAAK;AAChB,4BAAoB;AAAA,MACtB;AAAA,IACF,CAAC;AAAA,EACH;AAEA,MAAI,CAAC,YAAY;AACf,iBAAa,IAAI,WAAW,SAAS;AAAA,MACnC,kBAAkB,MAAM,SAAS,iBAAiB,KAAK,CAAC;AAAA,MACxD,aAAa,MAAM,QAAS,UAAU;AAAA,MACtC,YAAY,MAAM;AAChB,iBAAS,WAAW;AACpB,yBAAiB;AAAA,MACnB;AAAA,MACA,cAAc,CAAC,UAAU;AACvB,YAAI,CAAC,QAAS;AACd,cAAM,WAAW,QAAQ,iBAAiB;AAC1C,cAAM,YAAY,SAAS,OAAO,CAAC,GAAG,MAAM,MAAM,KAAK;AACvD,gBAAQ,aAAa,SAAS;AAC9B,yBAAiB;AAAA,MACnB;AAAA,MACA,YAAY,CAAC,OAAO,OAAO,aAAa,aAAa;AACnD,YAAI,CAAC,QAAS;AACd,cAAM,WAAW,QAAQ,iBAAiB;AAC1C,YAAI,SAAS,KAAK,QAAQ,SAAS,QAAQ;AACzC,mBAAS,KAAK,IAAI,EAAE,GAAG,SAAS,KAAK,GAAG,OAAO,aAAa,SAAS;AACrE,kBAAQ,aAAa,QAAQ;AAAA,QAC/B;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAIA,mBAAiB;AACnB;AASA,SAAS,gBAAsB;AAC7B,MAAI,UAAU,YAAY;AACxB,qBAAiB,KAAK;AAAA,EACxB;AACA,UAAQ,WAAW;AACnB,WAAS;AACT,eAAa,MAAM;AACnB,aAAW,KAAK;AAChB,cAAY,QAAQ;AACpB,QAAM,QAAQ;AAEd,eAAa;AACb,SAAO;AAIP,UAAQ;AACV;AAKA,SAAS,oBAAoB,GAAW,GAAiB;AACvD,MAAI,CAAC,WAAW,CAAC,YAAa;AAC9B,QAAM,UAAU,QAAQ,kBAAkB;AAC1C,MAAI,CAAC,QAAS;AACd,cAAY,OAAO,SAAS,GAAG,CAAC;AAClC;AAIA,SAAS,oBAA0B;AACjC,MAAK,UAAU,UAAU,UAAU,cAAe,CAAC,WAAW,CAAC,YAAa;AAE5E,QAAM,SAAS,QAAQ,eAAe;AACtC,MAAI,CAAC,OAAQ;AAEb,MAAI,QAAQ;AACV,WAAO,WAAW;AAAA,EACpB;AAEA,WAAS,IAAI,cAAc;AAAA,IACzB,oBAAoB;AAAA,IACpB;AAAA,IACA,gBAAgB,CAAC,SAAS,YAAY;AACpC,UAAI,UAAU,YAAa;AAC3B,6BAAuB;AACvB,cAAQ;AACR,cAAQ,WAAW;AACnB,iBAAW,KAAK,SAAS,OAAO;AAAA,IAClC;AAAA,IACA,UAAU,MAAM;AACd,UAAI,UAAU,aAAa;AACzB,4BAAoB;AAAA,MACtB,WAAW,UAAU,aAAa;AAChC,mBAAW,KAAK;AAChB,4BAAoB;AAAA,MACtB;AAAA,IACF;AAAA,EACF,CAAC;AAED,SAAO,SAAS;AAChB,UAAQ;AACV;AAEA,SAAS,sBAA4B;AACnC,UAAQ,WAAW;AACnB,eAAa,MAAM;AACnB,UAAQ;AACV;AAEA,SAAS,mBAAyB;AAChC,MAAI,CAAC,WAAY;AACjB,MAAI,WAAW,QAAQ;AACrB,eAAW,KAAK;AAAA,EAClB,OAAO;AACL,eAAW,KAAK;AAChB,eAAW,QAAQ;AAAA,EACrB;AACF;AAEA,SAAS,mBAAyB;AAChC,QAAM,YAAY,SAAS,iBAAiB,EAAE,UAAU,CAAC;AAC3D;;;AtBpaA,IAAIC,WAAU,IAAI,gBAAgB;AAIlC,yBAAyBA,QAAO;AAIzB,SAAS,KAAK,QAAuD;AAC1E,EAAAA,SAAQ,KAAK,MAAM;AAOnB,EAAAA,SAAQ,OAAO;AACjB;AAEO,SAAS,SAAe;AAC7B,EAAAA,SAAQ,OAAO;AACjB;AAEO,SAAS,UAAgB;AAC9B,EAAAA,SAAQ,QAAQ;AAClB;AAEO,SAAS,SAAS,SAAiC;AACxD,SAAOA,SAAQ,SAAS,OAAO;AACjC;AAEO,SAAS,UAAU,SAAiC;AACzD,SAAOA,SAAQ,UAAU,OAAO;AAClC;AAEA,eAAsB,YAA2D;AAC/E,SAAOA,SAAQ,UAAU;AAC3B;AAEO,SAAS,MAAM,SAAwD;AAC5E,EAAAA,SAAQ,MAAM,OAAO;AACvB;AAEO,SAAS,aAAmB;AACjC,EAAAA,SAAQ,WAAW;AACrB;AAEO,SAAS,aAAa,OAAwD;AACnF,EAAAA,SAAQ,aAAa,KAAK;AAC5B;AAEO,SAAS,YAAqB;AACnC,SAAOA,SAAQ;AACjB;AAEO,SAAS,mBAA8D;AAC5E,SAAOA,SAAQ,iBAAiB;AAClC;AAEO,SAAS,iBAAyB;AACvC,SAAOA,SAAQ;AACjB;AAwCO,SAAS,oBAA0B;AACxC,0BAAwB;AACxB,EAAAA,SAAQ,QAAQ;AAChB,EAAAA,WAAU,IAAI,gBAAgB;AAC9B,2BAAyBA,QAAO;AAClC;","names":["overlay","logo","manager"]}
|