web-remarq 0.2.5 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/hash-detect.ts","../../src/core/fingerprint.ts","../../src/core/matcher.ts","../../src/core/viewport.ts","../../src/core/storage.ts"],"sourcesContent":["import type { CSSModuleClass } from './types'\n\nconst CSS_MODULES_RE = /^(.+)__([a-zA-Z0-9]{3,})$/\nconst CSS_MODULES_3SEG_RE = /^([^_]+(?:[_-][^_]+)*)__([a-zA-Z][a-zA-Z0-9]*)__([a-zA-Z0-9]{3,})$/\nconst STYLED_COMPONENTS_RE = /^sc-/\nconst EMOTION_RE = /^css-[a-zA-Z0-9]+$/\nconst PURE_HASH_RE = /^(?=.*[a-zA-Z])(?=.*\\d)[a-zA-Z0-9]{8,}$/\n\nexport function isHashedClass(className: string): boolean {\n if (STYLED_COMPONENTS_RE.test(className)) return true\n if (EMOTION_RE.test(className)) return true\n if (CSS_MODULES_RE.test(className)) return true\n if (PURE_HASH_RE.test(className)) return true\n return false\n}\n\nexport function stripHash(className: string): string {\n const match = className.match(CSS_MODULES_RE)\n if (match) {\n const prefix = className.slice(0, className.lastIndexOf('__'))\n return prefix\n }\n return className\n}\n\nexport function filterClasses(\n classes: string[],\n classFilter?: (className: string) => boolean,\n): string[] {\n const result: string[] = []\n\n for (const cls of classes) {\n if (STYLED_COMPONENTS_RE.test(cls)) continue\n if (EMOTION_RE.test(cls)) continue\n if (PURE_HASH_RE.test(cls)) continue\n\n let stable = stripHash(cls)\n\n if (classFilter && !classFilter(stable)) continue\n\n result.push(stable)\n }\n\n return result\n}\n\nexport function decomposeCSSModules(classes: string[]): CSSModuleClass[] {\n const result: CSSModuleClass[] = []\n for (const cls of classes) {\n const match = cls.match(CSS_MODULES_3SEG_RE)\n if (match) {\n result.push({\n raw: cls,\n moduleHint: match[1],\n localName: match[2],\n })\n }\n }\n return result\n}\n","import type { ElementFingerprint, WebRemarqOptions } from './types'\nimport { filterClasses, isHashedClass, decomposeCSSModules } from './hash-detect'\n\nconst TEXT_MAX_LENGTH = 50\n\nexport function createFingerprint(\n el: HTMLElement,\n options?: Pick<WebRemarqOptions, 'classFilter' | 'dataAttribute'>,\n): ElementFingerprint {\n const dataAttr = options?.dataAttribute ?? 'data-annotate'\n\n return {\n dataAnnotate: el.getAttribute(dataAttr) ?? null,\n dataTestId: el.getAttribute('data-testid')\n ?? el.getAttribute('data-test')\n ?? el.getAttribute('data-cy')\n ?? null,\n id: getStableId(el),\n tagName: el.tagName.toLowerCase(),\n textContent: getTextContent(el),\n role: el.getAttribute('role') ?? null,\n ariaLabel: el.getAttribute('aria-label') ?? null,\n stableClasses: filterClasses(\n Array.from(el.classList),\n options?.classFilter,\n ),\n domPath: buildDomPath(el),\n siblingIndex: getSiblingIndex(el),\n parentAnchor: findParentAnchor(el, dataAttr),\n rawClasses: Array.from(el.classList),\n cssModules: decomposeCSSModules(Array.from(el.classList)),\n }\n}\n\nfunction getStableId(el: HTMLElement): string | null {\n const id = el.id\n if (!id) return null\n if (isHashedClass(id)) return null\n return id\n}\n\nfunction getTextContent(el: HTMLElement): string | null {\n // Use only direct text nodes, not nested children's text\n let text = ''\n for (const node of Array.from(el.childNodes)) {\n if (node.nodeType === Node.TEXT_NODE) {\n text += node.textContent ?? ''\n }\n }\n text = text.trim()\n\n // If no direct text, try first meaningful child's text (for wrappers like <span><b>Text</b></span>)\n if (!text && el.children.length <= 3) {\n text = el.textContent?.trim() ?? ''\n }\n\n if (!text) return null\n return text.length > TEXT_MAX_LENGTH ? text.slice(0, TEXT_MAX_LENGTH) : text\n}\n\nfunction buildDomPath(el: HTMLElement): string {\n const parts: string[] = []\n let current: HTMLElement | null = el\n\n while (current && current !== document.body && parts.length < 5) {\n let segment = current.tagName.toLowerCase()\n const stable = filterClasses(Array.from(current.classList))\n if (stable.length > 0) {\n segment += '.' + stable.slice(0, 2).join('.')\n }\n parts.unshift(segment)\n current = current.parentElement\n }\n\n return parts.join(' > ')\n}\n\nfunction getSiblingIndex(el: HTMLElement): number {\n const parent = el.parentElement\n if (!parent) return 0\n const children = Array.from(parent.children)\n return children.indexOf(el)\n}\n\nfunction findParentAnchor(el: HTMLElement, dataAttr: string): string | null {\n let current = el.parentElement\n while (current && current !== document.body) {\n const value = current.getAttribute(dataAttr)\n if (value) return value\n current = current.parentElement\n }\n return null\n}\n","import type { ElementFingerprint, WebRemarqOptions } from './types'\nimport { filterClasses } from './hash-detect'\n\nconst MATCH_THRESHOLD = 50\n\nexport function levenshteinSimilarity(a: string, b: string): number {\n if (a === b) return 1\n if (!a.length || !b.length) return 0\n\n const matrix: number[][] = []\n for (let i = 0; i <= a.length; i++) {\n matrix[i] = [i]\n }\n for (let j = 0; j <= b.length; j++) {\n matrix[0][j] = j\n }\n\n for (let i = 1; i <= a.length; i++) {\n for (let j = 1; j <= b.length; j++) {\n const cost = a[i - 1] === b[j - 1] ? 0 : 1\n matrix[i][j] = Math.min(\n matrix[i - 1][j] + 1,\n matrix[i][j - 1] + 1,\n matrix[i - 1][j - 1] + cost,\n )\n }\n }\n\n const distance = matrix[a.length][b.length]\n return 1 - distance / Math.max(a.length, b.length)\n}\n\nfunction textSimilarity(a: string | null, b: string | null): number {\n if (!a || !b) return 0\n const na = a.trim().toLowerCase()\n const nb = b.trim().toLowerCase()\n if (na === nb) return 1\n if (na.includes(nb) || nb.includes(na)) return 1\n return levenshteinSimilarity(na, nb)\n}\n\nfunction jaccardSimilarity(a: string[], b: string[]): number {\n if (!a.length && !b.length) return 0\n const setA = new Set(a)\n const setB = new Set(b)\n let intersection = 0\n for (const item of setA) {\n if (setB.has(item)) intersection++\n }\n const union = new Set([...a, ...b]).size\n return union === 0 ? 0 : intersection / union\n}\n\nfunction scoreCandidate(el: HTMLElement, fp: ElementFingerprint, dataAttr: string): number {\n let score = 0\n\n // dataAnnotate match (+100)\n const elAnnotate = el.getAttribute(dataAttr)\n if (fp.dataAnnotate && elAnnotate === fp.dataAnnotate) {\n score += 100\n }\n\n // textContent match (+35 scaled)\n const elText = el.textContent?.trim().slice(0, 50) ?? null\n const textSim = textSimilarity(fp.textContent, elText)\n if (textSim > 0.7) {\n score += textSim * 35\n }\n\n // role + ariaLabel match (+30)\n if (fp.role && el.getAttribute('role') === fp.role &&\n fp.ariaLabel && el.getAttribute('aria-label') === fp.ariaLabel) {\n score += 30\n }\n\n // parentAnchor match (+15)\n if (fp.parentAnchor) {\n let parent = el.parentElement\n while (parent && parent !== document.body) {\n if (parent.getAttribute(dataAttr) === fp.parentAnchor) {\n score += 15\n break\n }\n parent = parent.parentElement\n }\n }\n\n // stableClasses overlap (+30 scaled)\n if (fp.stableClasses.length > 0) {\n const elClasses = filterClasses(Array.from(el.classList))\n const jaccard = jaccardSimilarity(fp.stableClasses, elClasses)\n score += jaccard * 30\n }\n\n // domPath match (+20 scaled)\n if (fp.domPath) {\n const elPath = buildDomPath(el)\n const pathSim = levenshteinSimilarity(fp.domPath, elPath)\n score += pathSim * 20\n }\n\n // siblingIndex match (+5)\n const parent = el.parentElement\n if (parent) {\n const idx = Array.from(parent.children).indexOf(el)\n if (idx === fp.siblingIndex) {\n score += 5\n }\n }\n\n return score\n}\n\nfunction buildDomPath(el: HTMLElement): string {\n const parts: string[] = []\n let current: HTMLElement | null = el\n while (current && current !== document.body && parts.length < 5) {\n let segment = current.tagName.toLowerCase()\n const stable = filterClasses(Array.from(current.classList))\n if (stable.length > 0) {\n segment += '.' + stable.slice(0, 2).join('.')\n }\n parts.unshift(segment)\n current = current.parentElement\n }\n return parts.join(' > ')\n}\n\nexport function matchElement(\n fp: ElementFingerprint,\n options?: Pick<WebRemarqOptions, 'dataAttribute'>,\n): HTMLElement | null {\n const dataAttr = options?.dataAttribute ?? 'data-annotate'\n\n // 1. Exact match by data-annotate\n if (fp.dataAnnotate) {\n const el = document.querySelector<HTMLElement>(`[${dataAttr}=\"${fp.dataAnnotate}\"]`)\n if (el) return el\n }\n\n // 2. Exact match by data-testid\n if (fp.dataTestId) {\n const el = document.querySelector<HTMLElement>(\n `[data-testid=\"${fp.dataTestId}\"], [data-test=\"${fp.dataTestId}\"], [data-cy=\"${fp.dataTestId}\"]`,\n )\n if (el) return el\n }\n\n // 3. Exact match by id\n if (fp.id) {\n const el = document.getElementById(fp.id) as HTMLElement | null\n if (el) return el\n }\n\n // 4. Fuzzy match by tagName + weighted scoring\n const candidates = document.querySelectorAll<HTMLElement>(fp.tagName)\n let bestEl: HTMLElement | null = null\n let bestScore = 0\n\n for (const candidate of candidates) {\n const score = scoreCandidate(candidate, fp, dataAttr)\n if (score > bestScore) {\n bestScore = score\n bestEl = candidate\n }\n }\n\n return bestScore >= MATCH_THRESHOLD ? bestEl : null\n}\n","type BucketChangeCallback = () => void\n\nlet currentBucket: number = 0\nlet onBucketChange: BucketChangeCallback | null = null\nlet resizeHandler: (() => void) | null = null\n\nexport function toBucket(width: number): number {\n return Math.floor(width / 100) * 100\n}\n\nfunction debounce(fn: () => void, ms: number): () => void {\n let timer: ReturnType<typeof setTimeout>\n return () => {\n clearTimeout(timer)\n timer = setTimeout(fn, ms)\n }\n}\n\nexport function initViewportListener(callback: BucketChangeCallback): void {\n currentBucket = toBucket(window.innerWidth)\n onBucketChange = callback\n\n resizeHandler = debounce(() => {\n const newBucket = toBucket(window.innerWidth)\n if (newBucket !== currentBucket) {\n currentBucket = newBucket\n onBucketChange?.()\n }\n }, 300)\n\n window.addEventListener('resize', resizeHandler)\n}\n\nexport function destroyViewportListener(): void {\n if (resizeHandler) {\n window.removeEventListener('resize', resizeHandler)\n resizeHandler = null\n }\n onBucketChange = null\n}\n","import type { Annotation, AnnotationStore } from './types'\nimport { toBucket } from './viewport'\n\nconst STORAGE_KEY = 'remarq:annotations'\n\nexport class AnnotationStorage {\n private annotations: Annotation[] = []\n private extraFields: Record<string, unknown> = {}\n isMemoryOnly = false\n\n constructor() {\n this.load()\n }\n\n getAll(): Annotation[] {\n return [...this.annotations]\n }\n\n getByRoute(route: string): Annotation[] {\n return this.annotations.filter((a) => a.route === route)\n }\n\n add(annotation: Annotation): void {\n this.annotations.push(annotation)\n this.save()\n }\n\n remove(id: string): void {\n this.annotations = this.annotations.filter((a) => a.id !== id)\n this.save()\n }\n\n update(id: string, changes: Partial<Annotation>): void {\n const idx = this.annotations.findIndex((a) => a.id === id)\n if (idx !== -1) {\n this.annotations[idx] = { ...this.annotations[idx], ...changes }\n this.save()\n }\n }\n\n clearAll(): void {\n this.annotations = []\n this.save()\n }\n\n exportJSON(): AnnotationStore {\n return {\n version: 1,\n annotations: [...this.annotations],\n }\n }\n\n importJSON(data: AnnotationStore): void {\n this.annotations = [...data.annotations]\n this.migrateViewportBuckets()\n this.save()\n }\n\n private migrateViewportBuckets(): void {\n for (const ann of this.annotations) {\n if (ann.viewportBucket == null && ann.viewport) {\n const width = parseInt(ann.viewport.split('x')[0], 10)\n ann.viewportBucket = toBucket(width)\n }\n }\n }\n\n private load(): void {\n try {\n const raw = localStorage.getItem(STORAGE_KEY)\n if (raw) {\n const parsed = JSON.parse(raw)\n const { version, annotations, ...rest } = parsed\n this.annotations = annotations ?? []\n this.extraFields = rest\n this.migrateViewportBuckets()\n }\n } catch {\n this.isMemoryOnly = true\n }\n }\n\n private save(): void {\n if (this.isMemoryOnly) return\n try {\n const data = {\n version: 1,\n ...this.extraFields,\n annotations: this.annotations,\n }\n localStorage.setItem(STORAGE_KEY, JSON.stringify(data))\n } catch {\n this.isMemoryOnly = true\n }\n }\n}\n"],"mappings":"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","names":["parent","buildDomPath"]}
1
+ {"version":3,"sources":["../../src/core/hash-detect.ts","../../src/core/source-detect.ts","../../src/core/fingerprint.ts","../../src/core/matcher.ts","../../src/core/viewport.ts","../../src/core/storage.ts","../../src/core/agent-export.ts"],"sourcesContent":["import type { CSSModuleClass } from './types'\n\nconst CSS_MODULES_RE = /^(.+)__([a-zA-Z0-9]{3,})$/\nconst CSS_MODULES_3SEG_RE = /^([^_]+(?:[_-][^_]+)*)__([a-zA-Z][a-zA-Z0-9]*)__([a-zA-Z0-9]{3,})$/\nconst STYLED_COMPONENTS_RE = /^sc-/\nconst EMOTION_RE = /^css-[a-zA-Z0-9]+$/\nconst PURE_HASH_RE = /^(?=.*[a-zA-Z])(?=.*\\d)[a-zA-Z0-9]{8,}$/\n\nexport function isHashedClass(className: string): boolean {\n if (STYLED_COMPONENTS_RE.test(className)) return true\n if (EMOTION_RE.test(className)) return true\n if (CSS_MODULES_RE.test(className)) return true\n if (PURE_HASH_RE.test(className)) return true\n return false\n}\n\nexport function stripHash(className: string): string {\n const match = className.match(CSS_MODULES_RE)\n if (match) {\n const prefix = className.slice(0, className.lastIndexOf('__'))\n return prefix\n }\n return className\n}\n\nexport function filterClasses(\n classes: string[],\n classFilter?: (className: string) => boolean,\n): string[] {\n const result: string[] = []\n\n for (const cls of classes) {\n if (STYLED_COMPONENTS_RE.test(cls)) continue\n if (EMOTION_RE.test(cls)) continue\n if (PURE_HASH_RE.test(cls)) continue\n\n let stable = stripHash(cls)\n\n if (classFilter && !classFilter(stable)) continue\n\n result.push(stable)\n }\n\n return result\n}\n\nexport function decomposeCSSModules(classes: string[]): CSSModuleClass[] {\n const result: CSSModuleClass[] = []\n for (const cls of classes) {\n const match = cls.match(CSS_MODULES_3SEG_RE)\n if (match) {\n result.push({\n raw: cls,\n moduleHint: match[1],\n localName: match[2],\n })\n }\n }\n return result\n}\n","import type { SourceDetectionResult } from './types'\n\n/**\n * Level 1: Read data-remarq-source/data-remarq-component attrs\n * injected by @web-remarq/babel-plugin or @web-remarq/unplugin.\n */\nexport function detectRemarqPlugin(el: HTMLElement): SourceDetectionResult {\n const source = el.getAttribute('data-remarq-source')\n if (!source) return { source: null, component: null }\n return {\n source,\n component: el.getAttribute('data-remarq-component'),\n }\n}\n\n/**\n * Level 2a: Read data-source or data-locator attrs\n * from locator.js or similar external tools.\n */\nexport function detectExternalSource(el: HTMLElement): SourceDetectionResult {\n const source = el.dataset.source ?? el.getAttribute('data-locator')\n if (!source) return { source: null, component: null }\n return { source, component: null }\n}\n\n/**\n * Level 2b: Read React fiber _debugSource (dev mode only).\n * Unstable/best-effort — React internals are not public API.\n * Walks up the fiber tree because _debugSource lives on component fibers,\n * not on host (DOM element) fibers.\n */\nexport function detectReactFiber(el: HTMLElement): SourceDetectionResult {\n const key = Object.keys(el).find(k => k.startsWith('__reactFiber$'))\n if (!key) return { source: null, component: null }\n\n let current = (el as unknown as Record<string, unknown>)[key] as Record<string, unknown> | null\n // Walk up fiber tree to find nearest fiber with _debugSource (max 15 levels)\n let depth = 0\n while (current && depth < 15) {\n const debugSource = current._debugSource as { fileName?: string; lineNumber?: number; columnNumber?: number } | undefined\n if (debugSource?.fileName) {\n const source = `${debugSource.fileName}:${debugSource.lineNumber ?? 0}:${debugSource.columnNumber ?? 0}`\n\n // Try to get component name from fiber.type\n const fiberType = current.type as { displayName?: string; name?: string } | string | undefined\n const component = typeof fiberType === 'object' && fiberType\n ? (fiberType.displayName ?? fiberType.name ?? null)\n : null\n\n return { source, component }\n }\n current = current.return as Record<string, unknown> | null\n depth++\n }\n\n return { source: null, component: null }\n}\n\n/**\n * Runs Level 2 detectors in order. Returns first non-null result.\n */\nexport function detectSource(el: HTMLElement): SourceDetectionResult {\n const external = detectExternalSource(el)\n if (external.source) return external\n\n const fiber = detectReactFiber(el)\n if (fiber.source) return fiber\n\n return { source: null, component: null }\n}\n","import type { ElementFingerprint, WebRemarqOptions } from './types'\nimport { filterClasses, isHashedClass, decomposeCSSModules } from './hash-detect'\nimport { detectRemarqPlugin, detectSource } from './source-detect'\n\nconst TEXT_MAX_LENGTH = 50\n\nexport function createFingerprint(\n el: HTMLElement,\n options?: Pick<WebRemarqOptions, 'classFilter' | 'dataAttribute'>,\n): ElementFingerprint {\n const dataAttr = options?.dataAttribute ?? 'data-annotate'\n\n return {\n dataAnnotate: el.getAttribute(dataAttr) ?? null,\n dataTestId: el.getAttribute('data-testid')\n ?? el.getAttribute('data-test')\n ?? el.getAttribute('data-cy')\n ?? null,\n id: getStableId(el),\n tagName: el.tagName.toLowerCase(),\n textContent: getTextContent(el),\n role: el.getAttribute('role') ?? null,\n ariaLabel: el.getAttribute('aria-label') ?? null,\n stableClasses: filterClasses(\n Array.from(el.classList),\n options?.classFilter,\n ),\n domPath: buildDomPath(el),\n siblingIndex: getSiblingIndex(el),\n parentAnchor: findParentAnchor(el, dataAttr),\n rawClasses: Array.from(el.classList),\n cssModules: decomposeCSSModules(Array.from(el.classList)),\n ...resolveSourceFields(el),\n }\n}\n\nfunction resolveSourceFields(el: HTMLElement): {\n sourceLocation: string | null\n componentName: string | null\n detectedSource: string | null\n detectedComponent: string | null\n} {\n const plugin = detectRemarqPlugin(el)\n if (plugin.source) {\n return {\n sourceLocation: plugin.source,\n componentName: plugin.component,\n detectedSource: null,\n detectedComponent: null,\n }\n }\n\n const detected = detectSource(el)\n return {\n sourceLocation: null,\n componentName: null,\n detectedSource: detected.source,\n detectedComponent: detected.component,\n }\n}\n\nfunction getStableId(el: HTMLElement): string | null {\n const id = el.id\n if (!id) return null\n if (isHashedClass(id)) return null\n return id\n}\n\nfunction getTextContent(el: HTMLElement): string | null {\n // Use only direct text nodes, not nested children's text\n let text = ''\n for (const node of Array.from(el.childNodes)) {\n if (node.nodeType === Node.TEXT_NODE) {\n text += node.textContent ?? ''\n }\n }\n text = text.trim()\n\n // If no direct text, try first meaningful child's text (for wrappers like <span><b>Text</b></span>)\n if (!text && el.children.length <= 3) {\n text = el.textContent?.trim() ?? ''\n }\n\n if (!text) return null\n return text.length > TEXT_MAX_LENGTH ? text.slice(0, TEXT_MAX_LENGTH) : text\n}\n\nfunction buildDomPath(el: HTMLElement): string {\n const parts: string[] = []\n let current: HTMLElement | null = el\n\n while (current && current !== document.body && parts.length < 5) {\n let segment = current.tagName.toLowerCase()\n const stable = filterClasses(Array.from(current.classList))\n if (stable.length > 0) {\n segment += '.' + stable.slice(0, 2).join('.')\n }\n parts.unshift(segment)\n current = current.parentElement\n }\n\n return parts.join(' > ')\n}\n\nfunction getSiblingIndex(el: HTMLElement): number {\n const parent = el.parentElement\n if (!parent) return 0\n const children = Array.from(parent.children)\n return children.indexOf(el)\n}\n\nfunction findParentAnchor(el: HTMLElement, dataAttr: string): string | null {\n let current = el.parentElement\n while (current && current !== document.body) {\n const value = current.getAttribute(dataAttr)\n if (value) return value\n current = current.parentElement\n }\n return null\n}\n","import type { ElementFingerprint, WebRemarqOptions } from './types'\nimport { filterClasses } from './hash-detect'\n\nconst MATCH_THRESHOLD = 50\n\nexport function levenshteinSimilarity(a: string, b: string): number {\n if (a === b) return 1\n if (!a.length || !b.length) return 0\n\n const matrix: number[][] = []\n for (let i = 0; i <= a.length; i++) {\n matrix[i] = [i]\n }\n for (let j = 0; j <= b.length; j++) {\n matrix[0][j] = j\n }\n\n for (let i = 1; i <= a.length; i++) {\n for (let j = 1; j <= b.length; j++) {\n const cost = a[i - 1] === b[j - 1] ? 0 : 1\n matrix[i][j] = Math.min(\n matrix[i - 1][j] + 1,\n matrix[i][j - 1] + 1,\n matrix[i - 1][j - 1] + cost,\n )\n }\n }\n\n const distance = matrix[a.length][b.length]\n return 1 - distance / Math.max(a.length, b.length)\n}\n\nfunction textSimilarity(a: string | null, b: string | null): number {\n if (!a || !b) return 0\n const na = a.trim().toLowerCase()\n const nb = b.trim().toLowerCase()\n if (na === nb) return 1\n if (na.includes(nb) || nb.includes(na)) return 1\n return levenshteinSimilarity(na, nb)\n}\n\nfunction jaccardSimilarity(a: string[], b: string[]): number {\n if (!a.length && !b.length) return 0\n const setA = new Set(a)\n const setB = new Set(b)\n let intersection = 0\n for (const item of setA) {\n if (setB.has(item)) intersection++\n }\n const union = new Set([...a, ...b]).size\n return union === 0 ? 0 : intersection / union\n}\n\nfunction scoreCandidate(el: HTMLElement, fp: ElementFingerprint, dataAttr: string): number {\n let score = 0\n\n // dataAnnotate match (+100)\n const elAnnotate = el.getAttribute(dataAttr)\n if (fp.dataAnnotate && elAnnotate === fp.dataAnnotate) {\n score += 100\n }\n\n // textContent match (+35 scaled)\n const elText = el.textContent?.trim().slice(0, 50) ?? null\n const textSim = textSimilarity(fp.textContent, elText)\n if (textSim > 0.7) {\n score += textSim * 35\n }\n\n // role + ariaLabel match (+30)\n if (fp.role && el.getAttribute('role') === fp.role &&\n fp.ariaLabel && el.getAttribute('aria-label') === fp.ariaLabel) {\n score += 30\n }\n\n // parentAnchor match (+15)\n if (fp.parentAnchor) {\n let parent = el.parentElement\n while (parent && parent !== document.body) {\n if (parent.getAttribute(dataAttr) === fp.parentAnchor) {\n score += 15\n break\n }\n parent = parent.parentElement\n }\n }\n\n // stableClasses overlap (+30 scaled)\n if (fp.stableClasses.length > 0) {\n const elClasses = filterClasses(Array.from(el.classList))\n const jaccard = jaccardSimilarity(fp.stableClasses, elClasses)\n score += jaccard * 30\n }\n\n // domPath match (+20 scaled)\n if (fp.domPath) {\n const elPath = buildDomPath(el)\n const pathSim = levenshteinSimilarity(fp.domPath, elPath)\n score += pathSim * 20\n }\n\n // siblingIndex match (+5)\n const parent = el.parentElement\n if (parent) {\n const idx = Array.from(parent.children).indexOf(el)\n if (idx === fp.siblingIndex) {\n score += 5\n }\n }\n\n return score\n}\n\nfunction buildDomPath(el: HTMLElement): string {\n const parts: string[] = []\n let current: HTMLElement | null = el\n while (current && current !== document.body && parts.length < 5) {\n let segment = current.tagName.toLowerCase()\n const stable = filterClasses(Array.from(current.classList))\n if (stable.length > 0) {\n segment += '.' + stable.slice(0, 2).join('.')\n }\n parts.unshift(segment)\n current = current.parentElement\n }\n return parts.join(' > ')\n}\n\nexport function matchElement(\n fp: ElementFingerprint,\n options?: Pick<WebRemarqOptions, 'dataAttribute'>,\n): HTMLElement | null {\n const dataAttr = options?.dataAttribute ?? 'data-annotate'\n\n // 1. Exact match by data-annotate\n if (fp.dataAnnotate) {\n const el = document.querySelector<HTMLElement>(`[${dataAttr}=\"${fp.dataAnnotate}\"]`)\n if (el) return el\n }\n\n // 2. Exact match by data-testid\n if (fp.dataTestId) {\n const el = document.querySelector<HTMLElement>(\n `[data-testid=\"${fp.dataTestId}\"], [data-test=\"${fp.dataTestId}\"], [data-cy=\"${fp.dataTestId}\"]`,\n )\n if (el) return el\n }\n\n // 3. Exact match by id\n if (fp.id) {\n const el = document.getElementById(fp.id) as HTMLElement | null\n if (el) return el\n }\n\n // 4. Fuzzy match by tagName + weighted scoring\n const candidates = document.querySelectorAll<HTMLElement>(fp.tagName)\n let bestEl: HTMLElement | null = null\n let bestScore = 0\n\n for (const candidate of candidates) {\n const score = scoreCandidate(candidate, fp, dataAttr)\n if (score > bestScore) {\n bestScore = score\n bestEl = candidate\n }\n }\n\n return bestScore >= MATCH_THRESHOLD ? bestEl : null\n}\n","type BucketChangeCallback = () => void\n\nlet currentBucket: number = 0\nlet onBucketChange: BucketChangeCallback | null = null\nlet resizeHandler: (() => void) | null = null\n\nexport function toBucket(width: number): number {\n return Math.floor(width / 100) * 100\n}\n\nfunction debounce(fn: () => void, ms: number): () => void {\n let timer: ReturnType<typeof setTimeout>\n return () => {\n clearTimeout(timer)\n timer = setTimeout(fn, ms)\n }\n}\n\nexport function initViewportListener(callback: BucketChangeCallback): void {\n currentBucket = toBucket(window.innerWidth)\n onBucketChange = callback\n\n resizeHandler = debounce(() => {\n const newBucket = toBucket(window.innerWidth)\n if (newBucket !== currentBucket) {\n currentBucket = newBucket\n onBucketChange?.()\n }\n }, 300)\n\n window.addEventListener('resize', resizeHandler)\n}\n\nexport function destroyViewportListener(): void {\n if (resizeHandler) {\n window.removeEventListener('resize', resizeHandler)\n resizeHandler = null\n }\n onBucketChange = null\n}\n","import type { Annotation, AnnotationStore } from './types'\nimport { toBucket } from './viewport'\n\nconst STORAGE_KEY = 'remarq:annotations'\n\nexport class AnnotationStorage {\n private annotations: Annotation[] = []\n private extraFields: Record<string, unknown> = {}\n isMemoryOnly = false\n\n constructor() {\n this.load()\n }\n\n getAll(): Annotation[] {\n return [...this.annotations]\n }\n\n getByRoute(route: string): Annotation[] {\n return this.annotations.filter((a) => a.route === route)\n }\n\n add(annotation: Annotation): void {\n this.annotations.push(annotation)\n this.save()\n }\n\n remove(id: string): void {\n this.annotations = this.annotations.filter((a) => a.id !== id)\n this.save()\n }\n\n update(id: string, changes: Partial<Annotation>): void {\n const idx = this.annotations.findIndex((a) => a.id === id)\n if (idx !== -1) {\n this.annotations[idx] = { ...this.annotations[idx], ...changes }\n this.save()\n }\n }\n\n clearAll(): void {\n this.annotations = []\n this.save()\n }\n\n exportJSON(): AnnotationStore {\n return {\n version: 1,\n annotations: [...this.annotations],\n }\n }\n\n importJSON(data: AnnotationStore): void {\n this.annotations = [...data.annotations]\n this.migrateViewportBuckets()\n this.save()\n }\n\n private migrateViewportBuckets(): void {\n for (const ann of this.annotations) {\n if (ann.viewportBucket == null && ann.viewport) {\n const width = parseInt(ann.viewport.split('x')[0], 10)\n ann.viewportBucket = toBucket(width)\n }\n }\n }\n\n private load(): void {\n try {\n const raw = localStorage.getItem(STORAGE_KEY)\n if (raw) {\n const parsed = JSON.parse(raw)\n const { version, annotations, ...rest } = parsed\n this.annotations = annotations ?? []\n this.extraFields = rest\n this.migrateViewportBuckets()\n }\n } catch {\n this.isMemoryOnly = true\n }\n }\n\n private save(): void {\n if (this.isMemoryOnly) return\n try {\n const data = {\n version: 1,\n ...this.extraFields,\n annotations: this.annotations,\n }\n localStorage.setItem(STORAGE_KEY, JSON.stringify(data))\n } catch {\n this.isMemoryOnly = true\n }\n }\n}\n","import type {\n Annotation,\n AgentExport,\n AgentAnnotation,\n AgentAnnotationSource,\n AgentSearchHints,\n GrepQuery,\n ElementFingerprint,\n} from './types'\n\nfunction parseSourceLocation(raw: string): { file: string; line: number; column: number } | null {\n const parts = raw.split(':')\n if (parts.length < 2) return null\n // file path may contain \":\" on Windows (C:\\...), so rejoin all but last 2\n const column = parseInt(parts.pop()!, 10)\n const line = parseInt(parts.pop()!, 10)\n const file = parts.join(':')\n if (!file || isNaN(line)) return null\n return { file, line, column: isNaN(column) ? 0 : column }\n}\n\nfunction resolveSource(fp: ElementFingerprint): AgentAnnotationSource | null {\n // Level 1: plugin source\n if (fp.sourceLocation) {\n const parsed = parseSourceLocation(fp.sourceLocation)\n if (parsed) return { ...parsed, component: fp.componentName ?? null }\n }\n\n // Level 2: detected source\n if (fp.detectedSource) {\n const parsed = parseSourceLocation(fp.detectedSource)\n if (parsed) return { ...parsed, component: fp.detectedComponent ?? null }\n }\n\n return null\n}\n\nconst TEMPLATE_GLOB = '*.{tsx,jsx,vue,svelte,html}'\nconst CSS_MODULE_GLOB = '*.module.{css,scss,less}'\nconst COMPONENT_GLOB = '*.{tsx,jsx,vue,ts,js}'\n\nfunction buildSearchHints(fp: ElementFingerprint): AgentSearchHints {\n const grepQueries: GrepQuery[] = []\n\n // High confidence — unique selectors\n if (fp.dataAnnotate) {\n grepQueries.push({ query: `data-annotate=\"${fp.dataAnnotate}\"`, glob: TEMPLATE_GLOB, confidence: 'high' })\n }\n if (fp.dataTestId) {\n grepQueries.push({ query: `data-testid=\"${fp.dataTestId}\"`, glob: TEMPLATE_GLOB, confidence: 'high' })\n }\n if (fp.id) {\n grepQueries.push({ query: `id=\"${fp.id}\"`, glob: TEMPLATE_GLOB, confidence: 'high' })\n }\n if (fp.ariaLabel) {\n grepQueries.push({ query: `aria-label=\"${fp.ariaLabel}\"`, glob: TEMPLATE_GLOB, confidence: 'high' })\n }\n\n // Medium confidence — text, CSS modules, role\n if (fp.textContent) {\n grepQueries.push({ query: `\"${fp.textContent}\"`, glob: TEMPLATE_GLOB, confidence: 'medium' })\n }\n if (fp.role) {\n grepQueries.push({ query: `role=\"${fp.role}\"`, glob: TEMPLATE_GLOB, confidence: 'medium' })\n }\n if (fp.cssModules?.length) {\n for (const mod of fp.cssModules) {\n grepQueries.push({ query: `.${mod.localName}`, glob: CSS_MODULE_GLOB, confidence: 'medium' })\n grepQueries.push({ query: `styles.${mod.localName}`, glob: COMPONENT_GLOB, confidence: 'medium' })\n }\n }\n\n // Low confidence — classes, domPath\n if (fp.stableClasses.length) {\n for (const cls of fp.stableClasses.slice(0, 3)) {\n grepQueries.push({ query: `\"${cls}\"`, glob: TEMPLATE_GLOB, confidence: 'low' })\n }\n }\n\n return {\n grepQueries,\n domContext: fp.domPath,\n tagName: fp.tagName,\n classes: fp.rawClasses ?? fp.stableClasses,\n }\n}\n\nexport function generateAgentExport(annotations: Annotation[], viewportBucket: number): AgentExport {\n const agentAnnotations: AgentAnnotation[] = annotations.map(ann => ({\n id: ann.id,\n route: ann.route,\n comment: ann.comment,\n status: ann.status,\n timestamp: ann.timestamp,\n source: resolveSource(ann.fingerprint),\n searchHints: buildSearchHints(ann.fingerprint),\n }))\n\n return {\n version: 1,\n format: 'agent',\n viewportBucket,\n annotations: agentAnnotations,\n }\n}\n"],"mappings":"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","names":["parent","buildDomPath"]}
package/dist/index.cjs CHANGED
@@ -217,12 +217,54 @@ function decomposeCSSModules(classes) {
217
217
  return result;
218
218
  }
219
219
 
220
+ // src/core/source-detect.ts
221
+ function detectRemarqPlugin(el) {
222
+ const source = el.getAttribute("data-remarq-source");
223
+ if (!source) return { source: null, component: null };
224
+ return {
225
+ source,
226
+ component: el.getAttribute("data-remarq-component")
227
+ };
228
+ }
229
+ function detectExternalSource(el) {
230
+ var _a;
231
+ const source = (_a = el.dataset.source) != null ? _a : el.getAttribute("data-locator");
232
+ if (!source) return { source: null, component: null };
233
+ return { source, component: null };
234
+ }
235
+ function detectReactFiber(el) {
236
+ var _a, _b, _c, _d;
237
+ const key = Object.keys(el).find((k) => k.startsWith("__reactFiber$"));
238
+ if (!key) return { source: null, component: null };
239
+ let current = el[key];
240
+ let depth = 0;
241
+ while (current && depth < 15) {
242
+ const debugSource = current._debugSource;
243
+ if (debugSource == null ? void 0 : debugSource.fileName) {
244
+ const source = `${debugSource.fileName}:${(_a = debugSource.lineNumber) != null ? _a : 0}:${(_b = debugSource.columnNumber) != null ? _b : 0}`;
245
+ const fiberType = current.type;
246
+ const component = typeof fiberType === "object" && fiberType ? (_d = (_c = fiberType.displayName) != null ? _c : fiberType.name) != null ? _d : null : null;
247
+ return { source, component };
248
+ }
249
+ current = current.return;
250
+ depth++;
251
+ }
252
+ return { source: null, component: null };
253
+ }
254
+ function detectSource(el) {
255
+ const external = detectExternalSource(el);
256
+ if (external.source) return external;
257
+ const fiber = detectReactFiber(el);
258
+ if (fiber.source) return fiber;
259
+ return { source: null, component: null };
260
+ }
261
+
220
262
  // src/core/fingerprint.ts
221
263
  var TEXT_MAX_LENGTH = 50;
222
264
  function createFingerprint(el, options2) {
223
265
  var _a, _b, _c, _d, _e, _f, _g;
224
266
  const dataAttr = (_a = options2 == null ? void 0 : options2.dataAttribute) != null ? _a : "data-annotate";
225
- return {
267
+ return __spreadValues({
226
268
  dataAnnotate: (_b = el.getAttribute(dataAttr)) != null ? _b : null,
227
269
  dataTestId: (_e = (_d = (_c = el.getAttribute("data-testid")) != null ? _c : el.getAttribute("data-test")) != null ? _d : el.getAttribute("data-cy")) != null ? _e : null,
228
270
  id: getStableId(el),
@@ -239,6 +281,24 @@ function createFingerprint(el, options2) {
239
281
  parentAnchor: findParentAnchor(el, dataAttr),
240
282
  rawClasses: Array.from(el.classList),
241
283
  cssModules: decomposeCSSModules(Array.from(el.classList))
284
+ }, resolveSourceFields(el));
285
+ }
286
+ function resolveSourceFields(el) {
287
+ const plugin = detectRemarqPlugin(el);
288
+ if (plugin.source) {
289
+ return {
290
+ sourceLocation: plugin.source,
291
+ componentName: plugin.component,
292
+ detectedSource: null,
293
+ detectedComponent: null
294
+ };
295
+ }
296
+ const detected = detectSource(el);
297
+ return {
298
+ sourceLocation: null,
299
+ componentName: null,
300
+ detectedSource: detected.source,
301
+ detectedComponent: detected.component
242
302
  };
243
303
  }
244
304
  function getStableId(el) {
@@ -424,6 +484,88 @@ function matchElement(fp, options2) {
424
484
  return bestScore >= MATCH_THRESHOLD ? bestEl : null;
425
485
  }
426
486
 
487
+ // src/core/agent-export.ts
488
+ function parseSourceLocation(raw) {
489
+ const parts = raw.split(":");
490
+ if (parts.length < 2) return null;
491
+ const column = parseInt(parts.pop(), 10);
492
+ const line = parseInt(parts.pop(), 10);
493
+ const file = parts.join(":");
494
+ if (!file || isNaN(line)) return null;
495
+ return { file, line, column: isNaN(column) ? 0 : column };
496
+ }
497
+ function resolveSource(fp) {
498
+ var _a, _b;
499
+ if (fp.sourceLocation) {
500
+ const parsed = parseSourceLocation(fp.sourceLocation);
501
+ if (parsed) return __spreadProps(__spreadValues({}, parsed), { component: (_a = fp.componentName) != null ? _a : null });
502
+ }
503
+ if (fp.detectedSource) {
504
+ const parsed = parseSourceLocation(fp.detectedSource);
505
+ if (parsed) return __spreadProps(__spreadValues({}, parsed), { component: (_b = fp.detectedComponent) != null ? _b : null });
506
+ }
507
+ return null;
508
+ }
509
+ var TEMPLATE_GLOB = "*.{tsx,jsx,vue,svelte,html}";
510
+ var CSS_MODULE_GLOB = "*.module.{css,scss,less}";
511
+ var COMPONENT_GLOB = "*.{tsx,jsx,vue,ts,js}";
512
+ function buildSearchHints(fp) {
513
+ var _a, _b;
514
+ const grepQueries = [];
515
+ if (fp.dataAnnotate) {
516
+ grepQueries.push({ query: `data-annotate="${fp.dataAnnotate}"`, glob: TEMPLATE_GLOB, confidence: "high" });
517
+ }
518
+ if (fp.dataTestId) {
519
+ grepQueries.push({ query: `data-testid="${fp.dataTestId}"`, glob: TEMPLATE_GLOB, confidence: "high" });
520
+ }
521
+ if (fp.id) {
522
+ grepQueries.push({ query: `id="${fp.id}"`, glob: TEMPLATE_GLOB, confidence: "high" });
523
+ }
524
+ if (fp.ariaLabel) {
525
+ grepQueries.push({ query: `aria-label="${fp.ariaLabel}"`, glob: TEMPLATE_GLOB, confidence: "high" });
526
+ }
527
+ if (fp.textContent) {
528
+ grepQueries.push({ query: `"${fp.textContent}"`, glob: TEMPLATE_GLOB, confidence: "medium" });
529
+ }
530
+ if (fp.role) {
531
+ grepQueries.push({ query: `role="${fp.role}"`, glob: TEMPLATE_GLOB, confidence: "medium" });
532
+ }
533
+ if ((_a = fp.cssModules) == null ? void 0 : _a.length) {
534
+ for (const mod of fp.cssModules) {
535
+ grepQueries.push({ query: `.${mod.localName}`, glob: CSS_MODULE_GLOB, confidence: "medium" });
536
+ grepQueries.push({ query: `styles.${mod.localName}`, glob: COMPONENT_GLOB, confidence: "medium" });
537
+ }
538
+ }
539
+ if (fp.stableClasses.length) {
540
+ for (const cls of fp.stableClasses.slice(0, 3)) {
541
+ grepQueries.push({ query: `"${cls}"`, glob: TEMPLATE_GLOB, confidence: "low" });
542
+ }
543
+ }
544
+ return {
545
+ grepQueries,
546
+ domContext: fp.domPath,
547
+ tagName: fp.tagName,
548
+ classes: (_b = fp.rawClasses) != null ? _b : fp.stableClasses
549
+ };
550
+ }
551
+ function generateAgentExport(annotations, viewportBucket) {
552
+ const agentAnnotations = annotations.map((ann) => ({
553
+ id: ann.id,
554
+ route: ann.route,
555
+ comment: ann.comment,
556
+ status: ann.status,
557
+ timestamp: ann.timestamp,
558
+ source: resolveSource(ann.fingerprint),
559
+ searchHints: buildSearchHints(ann.fingerprint)
560
+ }));
561
+ return {
562
+ version: 1,
563
+ format: "agent",
564
+ viewportBucket,
565
+ annotations: agentAnnotations
566
+ };
567
+ }
568
+
427
569
  // src/ui/styles.ts
428
570
  var STYLES_ID = "data-remarq-styles";
429
571
  var CSS = `
@@ -1978,6 +2120,22 @@ function copyToClipboard() {
1978
2120
  console.warn("[web-remarq] Clipboard write failed");
1979
2121
  });
1980
2122
  }
2123
+ function exportAgent() {
2124
+ const anns = storage.getAll();
2125
+ if (!anns.length) return;
2126
+ const data = generateAgentExport(anns, toBucket(window.innerWidth));
2127
+ const json = JSON.stringify(data, null, 2);
2128
+ downloadFile(json, `remarq-agent-${Date.now()}.json`, "application/json");
2129
+ }
2130
+ function copyAgentToClipboard() {
2131
+ const anns = storage.getAll();
2132
+ if (!anns.length) return;
2133
+ const data = generateAgentExport(anns, toBucket(window.innerWidth));
2134
+ const json = JSON.stringify(data, null, 2);
2135
+ navigator.clipboard.writeText(json).catch(() => {
2136
+ console.warn("[web-remarq] Clipboard write failed");
2137
+ });
2138
+ }
1981
2139
  function setupMutationObserver() {
1982
2140
  mutationObserver = new MutationObserver((mutations) => {
1983
2141
  for (const m of mutations) {
@@ -2083,10 +2241,12 @@ var WebRemarq = {
2083
2241
  },
2084
2242
  export(format) {
2085
2243
  if (format === "md") exportMarkdown();
2086
- else exportJSON();
2244
+ else if (format === "json") exportJSON();
2245
+ else exportAgent();
2087
2246
  },
2088
- copy() {
2089
- copyToClipboard();
2247
+ copy(format) {
2248
+ if (format === "agent") copyAgentToClipboard();
2249
+ else copyToClipboard();
2090
2250
  },
2091
2251
  async import(file) {
2092
2252
  const text = await file.text();