@lionrapid/core 0.6.5 → 0.6.6

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.
Files changed (65) hide show
  1. package/README.md +15 -2
  2. package/dist/browser/auto-init.d.ts.map +1 -1
  3. package/dist/browser/auto-init.js +2 -1
  4. package/dist/browser/auto-init.js.map +1 -1
  5. package/dist/browser/editor/editor-api.d.ts +12 -0
  6. package/dist/browser/editor/editor-api.d.ts.map +1 -1
  7. package/dist/browser/editor/editor-api.js.map +1 -1
  8. package/dist/browser/editor/editor-init.js +2 -1
  9. package/dist/browser/editor/editor-init.js.map +1 -1
  10. package/dist/browser/editor/sidebar.d.ts.map +1 -1
  11. package/dist/browser/editor/sidebar.js +11 -1
  12. package/dist/browser/editor/sidebar.js.map +1 -1
  13. package/dist/browser/lionrapid.auto.dev.js +241 -170
  14. package/dist/browser/lionrapid.auto.dev.js.map +3 -3
  15. package/dist/browser/lionrapid.auto.js +241 -170
  16. package/dist/browser/lionrapid.auto.js.map +3 -3
  17. package/dist/browser/lionrapid.auto.min.js +6 -4
  18. package/dist/browser/lionrapid.auto.min.js.map +4 -4
  19. package/dist/browser/lionrapid.editor.js +5 -2
  20. package/dist/browser/lionrapid.editor.js.map +3 -3
  21. package/dist/browser/lionrapid.editor.min.js +4 -4
  22. package/dist/browser/lionrapid.editor.min.js.map +4 -4
  23. package/dist/browser/lionrapid.esm.js +242 -170
  24. package/dist/browser/lionrapid.esm.js.map +3 -3
  25. package/dist/browser/lionrapid.esm.min.js +5 -3
  26. package/dist/browser/lionrapid.esm.min.js.map +4 -4
  27. package/dist/browser/lionrapid.iife.dev.js +242 -170
  28. package/dist/browser/lionrapid.iife.dev.js.map +3 -3
  29. package/dist/browser/lionrapid.iife.js +242 -170
  30. package/dist/browser/lionrapid.iife.js.map +3 -3
  31. package/dist/browser/lionrapid.iife.min.js +5 -3
  32. package/dist/browser/lionrapid.iife.min.js.map +4 -4
  33. package/dist/browser/lionrapid.umd.js +242 -170
  34. package/dist/browser/lionrapid.umd.js.map +3 -3
  35. package/dist/browser/lionrapid.umd.min.js +5 -3
  36. package/dist/browser/lionrapid.umd.min.js.map +4 -4
  37. package/dist/builder.d.ts.map +1 -1
  38. package/dist/builder.js +9 -2
  39. package/dist/builder.js.map +1 -1
  40. package/dist/config/api.config.d.ts +35 -0
  41. package/dist/config/api.config.d.ts.map +1 -0
  42. package/dist/config/api.config.js +35 -0
  43. package/dist/config/api.config.js.map +1 -0
  44. package/dist/handlers/network-handler.d.ts +6 -0
  45. package/dist/handlers/network-handler.d.ts.map +1 -1
  46. package/dist/handlers/network-handler.js +9 -0
  47. package/dist/handlers/network-handler.js.map +1 -1
  48. package/dist/index.d.ts +2 -1
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +4 -1
  51. package/dist/index.js.map +1 -1
  52. package/dist/lionRapid.d.ts.map +1 -1
  53. package/dist/lionRapid.js +11 -2
  54. package/dist/lionRapid.js.map +1 -1
  55. package/dist/repositories/network-repository.d.ts.map +1 -1
  56. package/dist/repositories/network-repository.js +4 -3
  57. package/dist/repositories/network-repository.js.map +1 -1
  58. package/dist/utils/log-safe.d.ts +92 -0
  59. package/dist/utils/log-safe.d.ts.map +1 -0
  60. package/dist/utils/log-safe.js +168 -0
  61. package/dist/utils/log-safe.js.map +1 -0
  62. package/dist/utils/logger.d.ts.map +1 -1
  63. package/dist/utils/logger.js +3 -2
  64. package/dist/utils/logger.js.map +1 -1
  65. package/package.json +6 -3
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../../src/browser/editor/editor-styles.ts", "../../src/content/canonical-tag.ts", "../../src/browser/markup-parser.ts", "../../src/browser/md5.ts", "../../src/browser/editor/key-resolver.ts", "../../src/browser/editor/inspector.ts", "../../src/browser/editor/toolbar.ts", "../../src/content/sanitizer/HTMLSanitizer.ts", "../../src/content/serializer/HTMLSerializer.ts", "../../src/browser/editor/live-preview.ts", "../../src/browser/editor/editor-api.ts", "../../src/browser/exclude-filter.ts", "../../src/browser/block-extractor.ts", "../../src/browser/editor/locale-url.ts", "../../src/browser/editor/sidebar.ts", "../../src/browser/editor/pii-redactor.ts", "../../src/browser/editor/snapshot-capture.ts", "../../src/browser/sanitizer.ts", "../../src/browser/editor/snapshot-viewer.ts", "../../src/browser/editor/editor-init.ts"],
4
- "sourcesContent": ["/**\n * Editor CSS Styles\n * All styles are injected into Shadow DOM \u2014 no leakage to host page.\n */\n\nexport const EDITOR_CSS = `\n/* Reset inside shadow DOM */\n:host {\n all: initial;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n font-size: 14px;\n color: #1a1a2e;\n line-height: 1.5;\n box-sizing: border-box;\n}\n\n*, *::before, *::after {\n box-sizing: border-box;\n}\n\n/* \u2500\u2500 FAB (replaces fixed toolbar) \u2500\u2500 */\n.lr-fab {\n position: fixed;\n bottom: 20px;\n right: 20px;\n z-index: 2147483647;\n display: flex;\n align-items: center;\n gap: 0;\n user-select: none;\n}\n\n.lr-fab-btn {\n width: 48px;\n height: 48px;\n border-radius: 50%;\n background: #2d7f7f;\n color: #fff;\n border: none;\n font-weight: 700;\n font-size: 16px;\n cursor: grab;\n box-shadow: 0 4px 12px rgba(0,0,0,0.25);\n transition: box-shadow 0.15s;\n flex-shrink: 0;\n z-index: 1;\n}\n\n.lr-fab-btn:hover {\n box-shadow: 0 6px 20px rgba(0,0,0,0.3);\n}\n\n.lr-fab-btn:active {\n cursor: grabbing;\n}\n\n.lr-fab-panel {\n display: none;\n align-items: center;\n gap: 8px;\n background: #1a1a2e;\n color: #fff;\n padding: 8px 12px;\n border-radius: 8px;\n margin-right: 8px; /* gap between panel (left) and the FAB button (right) */\n box-shadow: 0 4px 12px rgba(0,0,0,0.25);\n white-space: nowrap;\n}\n\n.lr-fab.expanded .lr-fab-panel {\n display: flex;\n}\n\n.lr-lang-dropdown {\n position: relative;\n}\n\n.lr-lang-btn {\n background: rgba(255,255,255,0.1);\n color: #fff;\n border: 1px solid rgba(255,255,255,0.2);\n border-radius: 4px;\n padding: 4px 10px;\n font-size: 13px;\n font-weight: 600;\n cursor: pointer;\n outline: none;\n}\n\n.lr-lang-btn:hover {\n background: rgba(255,255,255,0.2);\n}\n\n.lr-lang-menu {\n display: none;\n position: absolute;\n bottom: calc(100% + 6px);\n left: 0;\n background: #1a1a2e;\n border: 1px solid rgba(255,255,255,0.15);\n border-radius: 6px;\n padding: 4px;\n min-width: 60px;\n box-shadow: 0 4px 16px rgba(0,0,0,0.4);\n}\n\n.lr-lang-menu.open {\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.lr-lang-item {\n background: none;\n color: #ccc;\n border: none;\n padding: 6px 10px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n border-radius: 4px;\n text-align: left;\n}\n\n.lr-lang-item:hover {\n background: rgba(255,255,255,0.1);\n color: #fff;\n}\n\n.lr-lang-item.active {\n background: #2d7f7f;\n color: #fff;\n}\n\n.lr-fab-progress {\n font-size: 13px;\n color: rgba(255,255,255,0.7);\n}\n\n.lr-fab-exit {\n background: none;\n border: 1px solid rgba(255,255,255,0.3);\n color: #fff;\n padding: 4px 10px;\n border-radius: 4px;\n cursor: pointer;\n font-size: 13px;\n}\n\n.lr-fab-exit:hover {\n background: rgba(255,255,255,0.1);\n}\n\n.lr-fab-snapshot {\n background: #2d7f7f;\n border: 1px solid #2d7f7f;\n color: #fff;\n padding: 4px 10px;\n border-radius: 4px;\n cursor: pointer;\n font-size: 13px;\n font-weight: 600;\n}\n\n.lr-fab-snapshot:hover {\n background: #3a9999;\n border-color: #3a9999;\n}\n\n.lr-fab-snapshot:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.lr-fab-browse {\n background: none;\n border: 1px solid rgba(255,255,255,0.3);\n color: #fff;\n padding: 4px 10px;\n border-radius: 4px;\n cursor: pointer;\n font-size: 13px;\n}\n\n.lr-fab-browse:hover {\n background: rgba(255,255,255,0.1);\n}\n\n.lr-fab-browse.active {\n background: #f5a623;\n border-color: #f5a623;\n color: #1a1a2e;\n font-weight: 600;\n}\n\n.lr-link-nav {\n background: none;\n border: 1px solid #2d7f7f;\n color: #2d7f7f;\n padding: 6px 12px;\n border-radius: 4px;\n cursor: pointer;\n font-size: 13px;\n font-weight: 600;\n}\n\n.lr-link-nav:hover {\n background: #2d7f7f;\n color: #fff;\n}\n\n/* Workflow action buttons (Phase 3) */\n.lr-workflow-actions {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n margin: 12px 0 4px;\n padding: 8px 0 0;\n border-top: 1px solid #e5e7eb;\n}\n\n.lr-wf-btn {\n font-size: 12px;\n font-weight: 500;\n padding: 5px 10px;\n border-radius: 4px;\n border: 1px solid transparent;\n cursor: pointer;\n transition: background-color 0.1s;\n}\n\n.lr-wf-btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.lr-wf-primary {\n background: #2563eb;\n color: white;\n border-color: #2563eb;\n}\n.lr-wf-primary:hover:not(:disabled) {\n background: #1d4ed8;\n}\n\n.lr-wf-secondary {\n background: white;\n color: #374151;\n border-color: #d1d5db;\n}\n.lr-wf-secondary:hover:not(:disabled) {\n background: #f3f4f6;\n}\n\n.lr-wf-danger {\n background: white;\n color: #b91c1c;\n border-color: #fca5a5;\n}\n.lr-wf-danger:hover:not(:disabled) {\n background: #fef2f2;\n}\n\n.lr-wf-note {\n font-size: 11px;\n color: #6b7280;\n font-style: italic;\n padding: 4px 0;\n}\n\n/* Workflow context strip (per-row 'you are here' indicator at the top of\n the workflow actions block). Width-flexible: stages wrap on narrow sidebars. */\n.lr-wf-strip-host {\n width: 100%;\n}\n.lr-wf-strip {\n display: flex;\n flex-direction: column;\n gap: 4px;\n margin-bottom: 8px;\n padding: 6px 8px;\n background: #f9fafb;\n border: 1px solid #e5e7eb;\n border-radius: 4px;\n}\n.lr-wf-strip-stages {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 4px;\n font-size: 11px;\n font-weight: 500;\n}\n.lr-wf-strip-stage {\n padding: 2px 8px;\n border-radius: 3px;\n white-space: nowrap;\n}\n.lr-wf-strip-stage.is-active {\n background: #2563eb;\n color: white;\n}\n.lr-wf-strip-stage.is-muted {\n background: #e5e7eb;\n color: #9ca3af;\n}\n.lr-wf-strip-sep {\n color: #d1d5db;\n font-size: 12px;\n user-select: none;\n}\n.lr-wf-strip-caption {\n font-size: 11px;\n color: #4b5563;\n font-weight: 500;\n}\n\n/* Snapshot viewer sandbox wrapper */\n#__lr_snapshot_root {\n isolation: isolate;\n position: relative;\n width: 100%;\n min-height: 100vh;\n}\n\n/* \u2500\u2500 Overlay \u2500\u2500 */\n.lr-overlay {\n position: fixed;\n pointer-events: none;\n border: 2px solid #2d7f7f;\n border-radius: 3px;\n background: rgba(45, 127, 127, 0.06);\n transition: all 0.1s ease;\n z-index: 2147483646;\n display: none;\n}\n\n.lr-overlay.active {\n display: block;\n}\n\n/* \u2500\u2500 Sidebar \u2500\u2500 */\n.lr-sidebar {\n position: fixed;\n top: 0;\n right: 0;\n width: 360px;\n min-width: 280px;\n max-width: 600px;\n bottom: 0;\n background: #fff;\n border-left: 1px solid #e0e0e0;\n z-index: 2147483647;\n overflow-y: auto;\n transform: translateX(100%);\n transition: transform 0.2s ease;\n}\n\n.lr-sidebar.open {\n transform: translateX(0);\n}\n\n/* Resize handle on left edge */\n.lr-sidebar-resize {\n position: absolute;\n top: 0;\n left: -4px;\n width: 8px;\n bottom: 0;\n cursor: col-resize;\n z-index: 1;\n}\n\n.lr-sidebar-resize:hover,\n.lr-sidebar-resize.active {\n background: rgba(45, 127, 127, 0.3);\n}\n\n.lr-sidebar-header {\n padding: 16px;\n border-bottom: 1px solid #e0e0e0;\n display: flex;\n align-items: center;\n justify-content: space-between;\n}\n\n.lr-sidebar-title {\n font-weight: 600;\n font-size: 15px;\n}\n\n.lr-sidebar-close {\n background: none;\n border: none;\n font-size: 20px;\n cursor: pointer;\n color: #666;\n padding: 4px 8px;\n line-height: 1;\n}\n\n.lr-sidebar-close:hover {\n color: #1a1a2e;\n}\n\n.lr-sidebar-body {\n padding: 16px;\n}\n\n.lr-field {\n margin-bottom: 16px;\n}\n\n.lr-field-label {\n display: block;\n font-size: 12px;\n font-weight: 600;\n color: #666;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n margin-bottom: 6px;\n}\n\n.lr-field-key {\n font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;\n font-size: 12px;\n color: #2d7f7f;\n background: #f0f7f7;\n padding: 4px 8px;\n border-radius: 4px;\n word-break: break-all;\n}\n\n.lr-field-original {\n font-size: 13px;\n color: #333;\n background: #f8f8f8;\n padding: 10px 12px;\n border-radius: 6px;\n border: 1px solid #eee;\n white-space: pre-wrap;\n word-break: break-word;\n}\n\n.lr-field-tag {\n display: inline;\n background: #d9eded;\n color: #2d7f7f;\n padding: 0 3px;\n border-radius: 2px;\n font-family: monospace;\n font-size: 12px;\n}\n\n.lr-textarea {\n width: 100%;\n min-height: 100px;\n padding: 10px 12px;\n border: 1px solid #ddd;\n border-radius: 6px;\n font-family: inherit;\n font-size: 14px;\n line-height: 1.5;\n resize: vertical;\n outline: none;\n transition: border-color 0.15s;\n}\n\n.lr-textarea:focus {\n border-color: #2d7f7f;\n box-shadow: 0 0 0 3px rgba(45, 127, 127, 0.1);\n}\n\n.lr-preview {\n padding: 10px 12px;\n background: #f8f8f8;\n border: 1px solid #eee;\n border-radius: 6px;\n font-size: 13px;\n min-height: 40px;\n}\n\n.lr-status {\n padding: 10px 16px;\n font-size: 12px;\n color: #666;\n text-align: center;\n border-top: 1px solid #eee;\n background: #fafafa;\n}\n\n.lr-status.saved {\n color: #22c55e;\n}\n\n.lr-status.error {\n color: #ef4444;\n}\n\n.lr-status.unsaved {\n color: #f59e0b;\n}\n\n/* \u2500\u2500 Page blocks list \u2500\u2500 */\n.lr-blocks-section {\n margin-top: 16px;\n padding-top: 16px;\n border-top: 1px solid #eee;\n}\n\n.lr-blocks-title {\n font-size: 12px;\n font-weight: 600;\n color: #666;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n margin-bottom: 8px;\n}\n\n.lr-block-item {\n padding: 8px 10px;\n border: 1px solid #eee;\n border-radius: 4px;\n margin-bottom: 4px;\n cursor: pointer;\n font-size: 13px;\n color: #333;\n overflow: hidden;\n transition: background 0.1s;\n display: flex;\n align-items: flex-start;\n gap: 8px;\n}\n\n.lr-block-item:hover {\n background: #f0f7f7;\n border-color: #2d7f7f;\n}\n\n.lr-block-item.active {\n background: #e5f2f2;\n border-color: #2d7f7f;\n}\n\n/* \u2500\u2500 Status dots \u2500\u2500 */\n.lr-status-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n flex-shrink: 0;\n margin-top: 5px;\n background: #d1d5db;\n}\n\n.lr-status-dot.missing { background: #d1d5db; }\n.lr-status-dot.mt { background: #60a5fa; }\n.lr-status-dot.edited { background: #f97316; }\n.lr-status-dot.confirmed { background: #22c55e; }\n.lr-status-dot.ready { background: #22c55e; }\n.lr-status-dot.locked { background: #a855f7; }\n.lr-status-dot.unsaved { background: #f59e0b; }\n.lr-status-dot.readonly { background: #9ca3af; opacity: 0.6; } /* grey + faded \u2014 past my stage */\n\n/* \u2500\u2500 Locked textarea \u2500\u2500 */\n.lr-textarea-locked {\n background: #f3f4f6;\n color: #6b7280;\n cursor: not-allowed;\n border-color: #e5e7eb;\n}\n\n/* \u2500\u2500 Block text layout \u2500\u2500 */\n.lr-block-text {\n overflow: hidden;\n min-width: 0;\n}\n\n.lr-block-source {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.lr-block-preview {\n font-size: 11px;\n color: #888;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n margin-top: 2px;\n}\n`;\n", "/**\n * Canonical opening-tag format for ContentUnit `raw:` styles.\n *\n * All four parsers (edge, core HTML walker, core DOM walker, PHP) must\n * produce byte-identical output for the same source element. Rules:\n * - tag name lowercased\n * - attribute names lowercased\n * - attribute values always wrapped in double-quotes\n * - attribute insertion order preserved from source\n * - boolean attrs emitted as `attr=\"\"`\n * - void elements use the same form (no self-closing slash, no close tag emitted)\n *\n * Attribute values are stored byte-for-byte as captured from the source\n * (the source's own entity encoding is preserved \u2014 already valid HTML).\n * The serializer only re-escapes `\"` \u2192 `&quot;` so the chosen double-quote\n * delimiter stays well-formed when the source used single quotes.\n */\n\nexport const VOID_TAGS = new Set([\n 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',\n 'link', 'meta', 'param', 'source', 'track', 'wbr',\n]);\n\nfunction escapeAttrValue(value: string): string {\n return value.replace(/\"/g, '&quot;');\n}\n\n/**\n * Build the canonical opening tag string. Used by parsers to produce\n * the `raw:<...>` style content.\n */\nexport function serializeOpeningTag(\n tagName: string,\n attrs: Record<string, string>,\n): string {\n const tag = tagName.toLowerCase();\n let result = `<${tag}`;\n for (const [name, value] of Object.entries(attrs)) {\n result += ` ${name.toLowerCase()}=\"${escapeAttrValue(value)}\"`;\n }\n result += '>';\n return result;\n}\n\n/**\n * Extract the tag name from a canonical opening tag string.\n * `<span class=\"x\">` \u2192 `span`. Returns lowercase.\n */\nexport function extractTagName(rawOpenTag: string): string {\n const end = rawOpenTag.search(/[\\s>]/);\n return rawOpenTag.slice(1, end === -1 ? rawOpenTag.length : end).toLowerCase();\n}\n\n/**\n * Return the raw opening tag (the portion after `raw:`) from a style array,\n * or null if no `raw:` style is present.\n */\nexport function readRawStyle(styles: string[]): string | null {\n for (const s of styles) {\n if (s.startsWith('raw:')) return s.slice(4);\n }\n return null;\n}\n", "/**\n * DOM-based Markup Parser (raw-only)\n *\n * Converts a block element's childNodes into ContentUnit by walking the\n * DOM. Every inline element becomes an indexed tag whose style is the\n * verbatim opening tag in canonical form (see content/canonical-tag.ts).\n *\n * Mirrors the regex-based parser in `content/parser/MarkupParser.ts` and\n * the edge proxy parser, producing byte-identical text + styles for the\n * same source so translation keys match across boundaries.\n */\n\nimport type { ContentUnit } from '../content/types.js';\nimport { serializeOpeningTag, VOID_TAGS } from '../content/canonical-tag.js';\n\n/**\n * Parse a block element into ContentUnit using DOM walking.\n */\nexport function parseBlockElement(element: Element): ContentUnit {\n const styles: Record<string, string[]> = {};\n let tagIndex = 0;\n\n function walk(node: Node): string {\n if (node.nodeType === Node.TEXT_NODE) {\n return node.nodeValue ?? '';\n }\n\n if (node.nodeType !== Node.ELEMENT_NODE) {\n return '';\n }\n\n const el = node as Element;\n const tag = el.tagName.toLowerCase();\n\n // Collect attributes preserving source insertion order.\n const attrs: Record<string, string> = {};\n for (let i = 0; i < el.attributes.length; i++) {\n const attr = el.attributes[i];\n attrs[attr.name.toLowerCase()] = attr.value;\n }\n\n tagIndex++;\n const idx = tagIndex;\n styles[String(idx)] = [`raw:${serializeOpeningTag(tag, attrs)}`];\n\n if (VOID_TAGS.has(tag)) {\n return `<${idx}/>`;\n }\n\n let inner = '';\n for (let i = 0; i < node.childNodes.length; i++) {\n inner += walk(node.childNodes[i]);\n }\n return `<${idx}>${inner}</${idx}>`;\n }\n\n let text = '';\n for (let i = 0; i < element.childNodes.length; i++) {\n text += walk(element.childNodes[i]);\n }\n\n return { text, styles };\n}\n", "/**\n * MD5 Hash Implementation\n * Minimal implementation to match PHP's md5() function\n * Used for generating auto_* keys that match server-side\n */\n\n/* eslint-disable no-bitwise */\n\nfunction safeAdd(x: number, y: number): number {\n const lsw = (x & 0xffff) + (y & 0xffff);\n const msw = (x >> 16) + (y >> 16) + (lsw >> 16);\n return (msw << 16) | (lsw & 0xffff);\n}\n\nfunction bitRotateLeft(num: number, cnt: number): number {\n return (num << cnt) | (num >>> (32 - cnt));\n}\n\nfunction md5cmn(\n q: number,\n a: number,\n b: number,\n x: number,\n s: number,\n t: number\n): number {\n return safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b);\n}\n\nfunction md5ff(\n a: number,\n b: number,\n c: number,\n d: number,\n x: number,\n s: number,\n t: number\n): number {\n return md5cmn((b & c) | (~b & d), a, b, x, s, t);\n}\n\nfunction md5gg(\n a: number,\n b: number,\n c: number,\n d: number,\n x: number,\n s: number,\n t: number\n): number {\n return md5cmn((b & d) | (c & ~d), a, b, x, s, t);\n}\n\nfunction md5hh(\n a: number,\n b: number,\n c: number,\n d: number,\n x: number,\n s: number,\n t: number\n): number {\n return md5cmn(b ^ c ^ d, a, b, x, s, t);\n}\n\nfunction md5ii(\n a: number,\n b: number,\n c: number,\n d: number,\n x: number,\n s: number,\n t: number\n): number {\n return md5cmn(c ^ (b | ~d), a, b, x, s, t);\n}\n\nfunction binlMD5(x: number[], len: number): number[] {\n x[len >> 5] |= 0x80 << len % 32;\n x[(((len + 64) >>> 9) << 4) + 14] = len;\n\n let a = 1732584193;\n let b = -271733879;\n let c = -1732584194;\n let d = 271733878;\n\n for (let i = 0; i < x.length; i += 16) {\n const olda = a;\n const oldb = b;\n const oldc = c;\n const oldd = d;\n\n a = md5ff(a, b, c, d, x[i], 7, -680876936);\n d = md5ff(d, a, b, c, x[i + 1], 12, -389564586);\n c = md5ff(c, d, a, b, x[i + 2], 17, 606105819);\n b = md5ff(b, c, d, a, x[i + 3], 22, -1044525330);\n a = md5ff(a, b, c, d, x[i + 4], 7, -176418897);\n d = md5ff(d, a, b, c, x[i + 5], 12, 1200080426);\n c = md5ff(c, d, a, b, x[i + 6], 17, -1473231341);\n b = md5ff(b, c, d, a, x[i + 7], 22, -45705983);\n a = md5ff(a, b, c, d, x[i + 8], 7, 1770035416);\n d = md5ff(d, a, b, c, x[i + 9], 12, -1958414417);\n c = md5ff(c, d, a, b, x[i + 10], 17, -42063);\n b = md5ff(b, c, d, a, x[i + 11], 22, -1990404162);\n a = md5ff(a, b, c, d, x[i + 12], 7, 1804603682);\n d = md5ff(d, a, b, c, x[i + 13], 12, -40341101);\n c = md5ff(c, d, a, b, x[i + 14], 17, -1502002290);\n b = md5ff(b, c, d, a, x[i + 15], 22, 1236535329);\n\n a = md5gg(a, b, c, d, x[i + 1], 5, -165796510);\n d = md5gg(d, a, b, c, x[i + 6], 9, -1069501632);\n c = md5gg(c, d, a, b, x[i + 11], 14, 643717713);\n b = md5gg(b, c, d, a, x[i], 20, -373897302);\n a = md5gg(a, b, c, d, x[i + 5], 5, -701558691);\n d = md5gg(d, a, b, c, x[i + 10], 9, 38016083);\n c = md5gg(c, d, a, b, x[i + 15], 14, -660478335);\n b = md5gg(b, c, d, a, x[i + 4], 20, -405537848);\n a = md5gg(a, b, c, d, x[i + 9], 5, 568446438);\n d = md5gg(d, a, b, c, x[i + 14], 9, -1019803690);\n c = md5gg(c, d, a, b, x[i + 3], 14, -187363961);\n b = md5gg(b, c, d, a, x[i + 8], 20, 1163531501);\n a = md5gg(a, b, c, d, x[i + 13], 5, -1444681467);\n d = md5gg(d, a, b, c, x[i + 2], 9, -51403784);\n c = md5gg(c, d, a, b, x[i + 7], 14, 1735328473);\n b = md5gg(b, c, d, a, x[i + 12], 20, -1926607734);\n\n a = md5hh(a, b, c, d, x[i + 5], 4, -378558);\n d = md5hh(d, a, b, c, x[i + 8], 11, -2022574463);\n c = md5hh(c, d, a, b, x[i + 11], 16, 1839030562);\n b = md5hh(b, c, d, a, x[i + 14], 23, -35309556);\n a = md5hh(a, b, c, d, x[i + 1], 4, -1530992060);\n d = md5hh(d, a, b, c, x[i + 4], 11, 1272893353);\n c = md5hh(c, d, a, b, x[i + 7], 16, -155497632);\n b = md5hh(b, c, d, a, x[i + 10], 23, -1094730640);\n a = md5hh(a, b, c, d, x[i + 13], 4, 681279174);\n d = md5hh(d, a, b, c, x[i + 0], 11, -358537222);\n c = md5hh(c, d, a, b, x[i + 3], 16, -722521979);\n b = md5hh(b, c, d, a, x[i + 6], 23, 76029189);\n a = md5hh(a, b, c, d, x[i + 9], 4, -640364487);\n d = md5hh(d, a, b, c, x[i + 12], 11, -421815835);\n c = md5hh(c, d, a, b, x[i + 15], 16, 530742520);\n b = md5hh(b, c, d, a, x[i + 2], 23, -995338651);\n\n a = md5ii(a, b, c, d, x[i], 6, -198630844);\n d = md5ii(d, a, b, c, x[i + 7], 10, 1126891415);\n c = md5ii(c, d, a, b, x[i + 14], 15, -1416354905);\n b = md5ii(b, c, d, a, x[i + 5], 21, -57434055);\n a = md5ii(a, b, c, d, x[i + 12], 6, 1700485571);\n d = md5ii(d, a, b, c, x[i + 3], 10, -1894986606);\n c = md5ii(c, d, a, b, x[i + 10], 15, -1051523);\n b = md5ii(b, c, d, a, x[i + 1], 21, -2054922799);\n a = md5ii(a, b, c, d, x[i + 8], 6, 1873313359);\n d = md5ii(d, a, b, c, x[i + 15], 10, -30611744);\n c = md5ii(c, d, a, b, x[i + 6], 15, -1560198380);\n b = md5ii(b, c, d, a, x[i + 13], 21, 1309151649);\n a = md5ii(a, b, c, d, x[i + 4], 6, -145523070);\n d = md5ii(d, a, b, c, x[i + 11], 10, -1120210379);\n c = md5ii(c, d, a, b, x[i + 2], 15, 718787259);\n b = md5ii(b, c, d, a, x[i + 9], 21, -343485551);\n\n a = safeAdd(a, olda);\n b = safeAdd(b, oldb);\n c = safeAdd(c, oldc);\n d = safeAdd(d, oldd);\n }\n\n return [a, b, c, d];\n}\n\nfunction binl2hex(binarray: number[]): string {\n const hexTab = '0123456789abcdef';\n let str = '';\n for (let i = 0; i < binarray.length * 4; i++) {\n str +=\n hexTab.charAt((binarray[i >> 2] >> ((i % 4) * 8 + 4)) & 0xf) +\n hexTab.charAt((binarray[i >> 2] >> ((i % 4) * 8)) & 0xf);\n }\n return str;\n}\n\n/**\n * Encode a string to its UTF-8 bytes. PHP's md5() operates on bytes, so to\n * match its output we must hash the UTF-8 representation \u2014 not raw UTF-16\n * code units. The previous code did `charCodeAt & 0xff`, which truncated\n * any character above U+00FF to a single low byte: U+2019 (curly `'`) was\n * hashed as 0x19 instead of its real UTF-8 form `E2 80 99`. That silently\n * broke key parity with PHP for every non-ASCII character on the page.\n */\nfunction utf8Bytes(str: string): Uint8Array {\n if (typeof TextEncoder !== 'undefined') {\n return new TextEncoder().encode(str);\n }\n // Fallback (older Node/test envs without TextEncoder).\n const out: number[] = [];\n for (let i = 0; i < str.length; i++) {\n const c = str.charCodeAt(i);\n if (c < 0x80) {\n out.push(c);\n } else if (c < 0x800) {\n out.push(0xc0 | (c >> 6), 0x80 | (c & 0x3f));\n } else if (c >= 0xd800 && c < 0xdc00 && i + 1 < str.length) {\n const c2 = str.charCodeAt(++i);\n const cp = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00);\n out.push(\n 0xf0 | (cp >> 18),\n 0x80 | ((cp >> 12) & 0x3f),\n 0x80 | ((cp >> 6) & 0x3f),\n 0x80 | (cp & 0x3f),\n );\n } else {\n out.push(\n 0xe0 | (c >> 12),\n 0x80 | ((c >> 6) & 0x3f),\n 0x80 | (c & 0x3f),\n );\n }\n }\n return new Uint8Array(out);\n}\n\nfunction bytes2binl(bytes: Uint8Array): number[] {\n const bin: number[] = [];\n for (let i = 0; i < bytes.length; i++) {\n bin[i >> 2] |= bytes[i] << ((i % 4) * 8);\n }\n return bin;\n}\n\n/**\n * Generate MD5 hash of a string.\n * Hashes the UTF-8 bytes of `str` so output matches PHP's `md5()` for any\n * input \u2014 ASCII or not.\n */\nexport function md5(str: string): string {\n const bytes = utf8Bytes(str);\n return binl2hex(binlMD5(bytes2binl(bytes), bytes.length * 8));\n}\n\n/**\n * Generate auto key matching PHP's TranslationStateManager::generateKey()\n * Format: auto_ + first 12 chars of md5 hash\n */\nexport function generateAutoKey(text: string): string {\n const trimmed = text.trim();\n return 'auto_' + md5(trimmed).substring(0, 12);\n}\n", "/**\n * Key Resolver\n * Resolves a clicked element to its translation key and ContentUnit.\n */\n\nimport type { ContentUnit } from '../../content/types.js';\nimport { parseBlockElement } from '../markup-parser.js';\nimport { generateAutoKey } from '../md5.js';\n\nconst BLOCK_SELECTOR =\n 'p,h1,h2,h3,h4,h5,h6,li,td,th,label,button,figcaption,dt,dd,summary,caption';\n\nexport interface ResolvedKey {\n key: string;\n /** Source ContentUnit (original text with indexed tags) */\n unit: ContentUnit;\n /** Current translation as ContentUnit (if element is already translated) */\n translatedUnit: ContentUnit | null;\n element: Element;\n}\n\n/**\n * Resolve an element to its translation key.\n * Priority: data-lr-block attr \u2192 parseBlockElement \u2192 fallback textContent\n */\nexport function resolveKey(target: Element): ResolvedKey | null {\n // Priority 1: Already translated element with data-lr-block\n const blockEl = target.closest('[data-lr-block]');\n if (blockEl) {\n const key = blockEl.getAttribute('data-lr-block')!;\n const original = blockEl.getAttribute('data-lr-original');\n let unit: ContentUnit;\n let translatedUnit: ContentUnit | null = null;\n\n if (original) {\n // Parse original HTML \u2192 source ContentUnit\n const temp = document.createElement(blockEl.tagName);\n temp.innerHTML = original;\n unit = parseBlockElement(temp);\n // Parse current DOM \u2192 translated ContentUnit\n translatedUnit = parseBlockElement(blockEl);\n } else {\n unit = parseBlockElement(blockEl);\n }\n return { key, unit, translatedUnit, element: blockEl };\n }\n\n // Priority 2: Find nearest block element and parse it\n const nearest = target.closest(BLOCK_SELECTOR);\n if (nearest) {\n const unit = parseBlockElement(nearest);\n const key = generateAutoKey(unit.text);\n return { key, unit, translatedUnit: null, element: nearest };\n }\n\n // Priority 3: Fallback to element text\n const text = target.textContent?.trim();\n if (text && text.length >= 2) {\n const key = generateAutoKey(text);\n const unit: ContentUnit = { text, styles: {} };\n return { key, unit, translatedUnit: null, element: target };\n }\n\n return null;\n}\n", "/**\n * Inspector \u2014 Hover/click handling\n * Shows overlay on hover, resolves key on click, opens sidebar.\n */\n\nimport { resolveKey, ResolvedKey } from './key-resolver.js';\nimport { parseBlockElement } from '../markup-parser.js';\n\nconst SKIP_TAGS = new Set(['SCRIPT', 'STYLE', 'CODE', 'PRE', 'TEXTAREA', 'NOSCRIPT', 'SVG']);\n\nconst BLOCK_SELECTOR =\n 'p,h1,h2,h3,h4,h5,h6,li,td,th,label,button,figcaption,dt,dd,summary,caption';\n\nexport type OnSelectCallback = (resolved: ResolvedKey) => void;\n\nexport class Inspector {\n private overlay: HTMLDivElement;\n private currentTarget: Element | null = null;\n private onSelect: OnSelectCallback;\n private rafId: number | null = null;\n private active = true;\n private rootElement: Element;\n /** When set, clicks resolve keys from this map (snapshot viewer mode)\n * instead of re-computing from the (redacted) DOM text. */\n private snapshotKeyMap: Map<number, string> | null;\n\n constructor(\n shadowRoot: ShadowRoot,\n onSelect: OnSelectCallback,\n rootElement?: Element,\n snapshotKeyMap?: Map<number, string>,\n ) {\n this.onSelect = onSelect;\n // Default scope is the live document body; snapshot viewer passes a\n // sandbox wrapper so blocks outside it are ignored.\n this.rootElement = rootElement || document.body;\n this.snapshotKeyMap = snapshotKeyMap || null;\n\n // Create overlay inside shadow DOM\n this.overlay = document.createElement('div');\n this.overlay.className = 'lr-overlay';\n shadowRoot.appendChild(this.overlay);\n\n // Bind handlers\n this.handleMouseOver = this.handleMouseOver.bind(this);\n this.handleClick = this.handleClick.bind(this);\n this.handleScroll = this.handleScroll.bind(this);\n\n // Listen on document (capture phase to get events before page handlers)\n document.addEventListener('mouseover', this.handleMouseOver, true);\n document.addEventListener('click', this.handleClick, true);\n window.addEventListener('scroll', this.handleScroll, true);\n window.addEventListener('resize', this.handleScroll);\n }\n\n destroy(): void {\n this.active = false;\n document.removeEventListener('mouseover', this.handleMouseOver, true);\n document.removeEventListener('click', this.handleClick, true);\n window.removeEventListener('scroll', this.handleScroll, true);\n window.removeEventListener('resize', this.handleScroll);\n if (this.rafId !== null) cancelAnimationFrame(this.rafId);\n this.overlay.remove();\n }\n\n clearHighlight(): void {\n this.currentTarget = null;\n this.overlay.classList.remove('active');\n }\n\n /**\n * Suspend/resume the inspector. When inactive, hover/click handlers\n * early-return so page links navigate natively \u2014 this is what the toolbar's\n * \"Browse\" mode flips so a PM can click through the site's nav to reach\n * another page, instead of every click opening the edit drawer.\n */\n setActive(active: boolean): void {\n this.active = active;\n if (!active) this.clearHighlight();\n }\n\n isActive(): boolean {\n return this.active;\n }\n\n private handleMouseOver(e: Event): void {\n if (!this.active) return;\n const target = e.target as Element;\n if (!target || !target.closest) return;\n\n // Skip editor UI elements (inside shadow DOM won't trigger this, but be safe)\n if (target.closest('#__lionrapid-editor')) return;\n\n // Skip anything outside the scope root (snapshot viewer mode)\n if (!this.rootElement.contains(target)) return;\n\n // Skip non-translatable elements\n if (this.shouldSkip(target)) {\n this.clearHighlight();\n return;\n }\n\n // Find nearest block element\n const block = target.closest(BLOCK_SELECTOR) || target.closest('[data-lr-block]');\n if (!block) {\n this.clearHighlight();\n return;\n }\n\n if (block === this.currentTarget) return;\n\n this.currentTarget = block;\n this.positionOverlay(block);\n }\n\n private handleClick(e: Event): void {\n if (!this.active) return;\n const target = e.target as Element;\n if (!target || !target.closest) return;\n\n // Skip editor UI\n if (target.closest('#__lionrapid-editor')) return;\n\n // Skip anything outside the scope root (snapshot viewer mode)\n if (!this.rootElement.contains(target)) return;\n\n // Find the block to resolve\n const block = target.closest(BLOCK_SELECTOR) || target.closest('[data-lr-block]');\n if (!block) return;\n\n if (this.shouldSkip(block)) return;\n\n e.preventDefault();\n e.stopPropagation();\n\n // Snapshot viewer mode: look up pre-computed key by data-lr-snap-idx\n // instead of re-running resolveKey on the redacted text.\n if (this.snapshotKeyMap) {\n const idxAttr = block.getAttribute('data-lr-snap-idx');\n if (idxAttr !== null) {\n const idx = parseInt(idxAttr, 10);\n const key = this.snapshotKeyMap.get(idx);\n if (key) {\n const unit = parseBlockElement(block);\n this.onSelect({ key, unit, translatedUnit: null, element: block });\n return;\n }\n }\n // Block has no snap-idx \u2014 not a pre-extracted block, skip\n return;\n }\n\n const resolved = resolveKey(block);\n if (resolved) {\n this.onSelect(resolved);\n }\n }\n\n private handleScroll(): void {\n if (!this.currentTarget) return;\n if (this.rafId !== null) cancelAnimationFrame(this.rafId);\n this.rafId = requestAnimationFrame(() => {\n if (this.currentTarget) {\n this.positionOverlay(this.currentTarget);\n }\n });\n }\n\n private positionOverlay(element: Element): void {\n const rect = element.getBoundingClientRect();\n this.overlay.style.top = `${rect.top}px`;\n this.overlay.style.left = `${rect.left}px`;\n this.overlay.style.width = `${rect.width}px`;\n this.overlay.style.height = `${rect.height}px`;\n this.overlay.classList.add('active');\n }\n\n private shouldSkip(element: Element): boolean {\n // Skip SKIP_TAGS\n let current: Element | null = element;\n while (current) {\n if (SKIP_TAGS.has(current.tagName)) return true;\n if (current.hasAttribute('data-no-translate')) return true;\n if (current.getAttribute('translate') === 'no') return true;\n current = current.parentElement;\n }\n return false;\n }\n}\n", "/**\n * Toolbar \u2014 Draggable FAB that expands to show controls\n * Collapsed: small circle with \"LR\" logo\n * Expanded: horizontal bar with language selector, progress, exit\n */\n\nexport type OnLanguageChange = (locale: string) => void;\nexport type OnExit = () => void;\nexport type OnCaptureSnapshot = () => void;\n/** Fired when the user toggles Browse mode. `browsing=true` means the\n * inspector should be suspended so page links navigate natively. */\nexport type OnBrowseToggle = (browsing: boolean) => void;\n\nexport interface ToolbarCaptureOptions {\n /** 'capture' = new snapshot, 'recapture' = overwrite an existing snapshot's version */\n mode?: 'capture' | 'recapture';\n}\n\nexport class Toolbar {\n private el: HTMLDivElement;\n private langBtnEl: HTMLButtonElement;\n private langMenuEl: HTMLDivElement;\n private progressEl: HTMLSpanElement;\n private expanded = false;\n private currentLocale: string;\n private onLanguageChange: OnLanguageChange;\n private onExit: OnExit;\n private onCaptureSnapshot: OnCaptureSnapshot | null;\n private onBrowseToggle: OnBrowseToggle | null;\n private browsing = false;\n private browseBtnEl: HTMLButtonElement | null = null;\n\n // Drag state\n private dragging = false;\n private dragOffsetX = 0;\n private dragOffsetY = 0;\n\n constructor(\n shadowRoot: ShadowRoot,\n languages: string[],\n currentLocale: string,\n onLanguageChange: OnLanguageChange,\n onExit: OnExit,\n onCaptureSnapshot: OnCaptureSnapshot | null = null,\n captureOptions: ToolbarCaptureOptions = {},\n onBrowseToggle: OnBrowseToggle | null = null,\n ) {\n this.onLanguageChange = onLanguageChange;\n this.onExit = onExit;\n this.onCaptureSnapshot = onCaptureSnapshot;\n this.onBrowseToggle = onBrowseToggle;\n this.currentLocale = currentLocale;\n const captureMode = captureOptions.mode ?? 'capture';\n\n this.el = document.createElement('div');\n this.el.className = 'lr-fab';\n\n // FAB button (always visible)\n const fabBtn = document.createElement('button');\n fabBtn.className = 'lr-fab-btn';\n fabBtn.textContent = 'LR';\n fabBtn.addEventListener('click', (e) => {\n // Don't toggle if we just finished dragging\n if (this.dragging) return;\n e.stopPropagation();\n this.toggle();\n });\n // Note: fabBtn is appended to this.el at the END (after panel), so flex\n // lays out panel-then-button \u2014 i.e. the panel expands to the LEFT of the\n // button. The button's default position is near the right edge of the\n // viewport, so leftward expansion keeps the panel on-screen.\n\n // Expanded panel\n const panel = document.createElement('div');\n panel.className = 'lr-fab-panel';\n\n // Custom language dropdown\n const langWrap = document.createElement('div');\n langWrap.className = 'lr-lang-dropdown';\n\n this.langBtnEl = document.createElement('button');\n this.langBtnEl.className = 'lr-lang-btn';\n this.langBtnEl.textContent = currentLocale.toUpperCase();\n this.langBtnEl.addEventListener('click', (e) => {\n e.stopPropagation();\n this.langMenuEl.classList.toggle('open');\n });\n langWrap.appendChild(this.langBtnEl);\n\n this.langMenuEl = document.createElement('div');\n this.langMenuEl.className = 'lr-lang-menu';\n for (const lang of languages) {\n const item = document.createElement('button');\n item.className = 'lr-lang-item';\n if (lang === currentLocale) item.classList.add('active');\n item.textContent = lang.toUpperCase();\n item.addEventListener('click', (e) => {\n e.stopPropagation();\n this.currentLocale = lang;\n this.langBtnEl.textContent = lang.toUpperCase();\n this.langMenuEl.classList.remove('open');\n // Update active state\n this.langMenuEl.querySelectorAll('.lr-lang-item').forEach((el) => el.classList.remove('active'));\n item.classList.add('active');\n this.onLanguageChange(lang);\n });\n this.langMenuEl.appendChild(item);\n }\n langWrap.appendChild(this.langMenuEl);\n panel.appendChild(langWrap);\n\n // Capture Snapshot button (only in capture mode, not viewer mode).\n // Label/title change when recapture mode is active so the PM knows this\n // click will overwrite an existing snapshot's version instead of making\n // a new one.\n if (this.onCaptureSnapshot) {\n const captureBtn = document.createElement('button');\n captureBtn.className = 'lr-fab-snapshot';\n if (captureMode === 'recapture') {\n captureBtn.textContent = 'Save re-capture';\n captureBtn.title = 'Save this page as a new version of the existing snapshot';\n } else {\n captureBtn.textContent = 'Capture';\n captureBtn.title = 'Capture a redacted snapshot of this page for translators';\n }\n captureBtn.addEventListener('click', (e) => {\n e.stopPropagation();\n if (this.onCaptureSnapshot) this.onCaptureSnapshot();\n });\n panel.appendChild(captureBtn);\n }\n\n // Browse-mode toggle \u2014 suspends the inspector so the PM can click the\n // site's own nav links to reach another page without every click opening\n // the edit drawer. Only shown when a handler is wired (live-page editing,\n // not snapshot viewer mode).\n if (this.onBrowseToggle) {\n const browseBtn = document.createElement('button');\n browseBtn.className = 'lr-fab-browse';\n browseBtn.textContent = 'Browse';\n browseBtn.title = 'Pause editing so you can click links and navigate to another page';\n browseBtn.addEventListener('click', (e) => {\n e.stopPropagation();\n this.setBrowsing(!this.browsing);\n });\n this.browseBtnEl = browseBtn;\n panel.appendChild(browseBtn);\n }\n\n // Progress\n this.progressEl = document.createElement('span');\n this.progressEl.className = 'lr-fab-progress';\n panel.appendChild(this.progressEl);\n\n // Exit button\n const exitBtn = document.createElement('button');\n exitBtn.className = 'lr-fab-exit';\n exitBtn.textContent = 'Exit';\n exitBtn.addEventListener('click', () => this.onExit());\n panel.appendChild(exitBtn);\n\n this.el.appendChild(panel);\n this.el.appendChild(fabBtn);\n shadowRoot.appendChild(this.el);\n\n // Dragging\n this.handleMouseDown = this.handleMouseDown.bind(this);\n this.handleMouseMove = this.handleMouseMove.bind(this);\n this.handleMouseUp = this.handleMouseUp.bind(this);\n fabBtn.addEventListener('mousedown', this.handleMouseDown);\n }\n\n toggle(): void {\n this.expanded = !this.expanded;\n this.el.classList.toggle('expanded', this.expanded);\n }\n\n private setBrowsing(browsing: boolean): void {\n this.browsing = browsing;\n if (this.browseBtnEl) {\n this.browseBtnEl.textContent = browsing ? 'Editing' : 'Browse';\n this.browseBtnEl.classList.toggle('active', browsing);\n this.browseBtnEl.title = browsing\n ? 'Resume editing \u2014 clicks open the edit drawer again'\n : 'Pause editing so you can click links and navigate to another page';\n }\n this.el.classList.toggle('lr-fab--browsing', browsing);\n if (this.onBrowseToggle) this.onBrowseToggle(browsing);\n }\n\n updateProgress(translated: number, total: number): void {\n this.progressEl.textContent = `${translated}/${total}`;\n }\n\n getCurrentLocale(): string {\n return this.currentLocale;\n }\n\n destroy(): void {\n document.removeEventListener('mousemove', this.handleMouseMove);\n document.removeEventListener('mouseup', this.handleMouseUp);\n this.el.remove();\n }\n\n private handleMouseDown(e: MouseEvent): void {\n this.dragging = false;\n this.dragOffsetX = e.clientX - this.el.getBoundingClientRect().left;\n this.dragOffsetY = e.clientY - this.el.getBoundingClientRect().top;\n document.addEventListener('mousemove', this.handleMouseMove);\n document.addEventListener('mouseup', this.handleMouseUp);\n }\n\n private handleMouseMove(e: MouseEvent): void {\n this.dragging = true;\n const x = Math.max(0, Math.min(e.clientX - this.dragOffsetX, window.innerWidth - this.el.offsetWidth));\n const y = Math.max(0, Math.min(e.clientY - this.dragOffsetY, window.innerHeight - this.el.offsetHeight));\n\n // Anchor by `right` (not `left`) so the FAB grows the panel LEFTWARD\n // when expanded, keeping the button visually fixed where the user\n // dropped it. With `left`-anchoring the container's left edge stays\n // pinned and the panel pushes the button rightward on expand \u2014 that's\n // the \"FAB jumps off-screen after drag\" bug. Switching to `right`\n // here means the button's right edge stays put across the drag /\n // expand / language-switch cycle.\n const rightOffset = window.innerWidth - x - this.el.offsetWidth;\n this.el.style.right = `${Math.max(0, rightOffset)}px`;\n this.el.style.top = `${y}px`;\n this.el.style.left = 'auto';\n this.el.style.bottom = 'auto';\n }\n\n private handleMouseUp(): void {\n document.removeEventListener('mousemove', this.handleMouseMove);\n document.removeEventListener('mouseup', this.handleMouseUp);\n // Reset dragging flag after a tick so the click handler can check it\n if (this.dragging) {\n setTimeout(() => { this.dragging = false; }, 0);\n }\n }\n}\n", "/**\n * Shared allowlist HTML sanitizer \u2014 pure string functions, Node-safe (no DOM).\n *\n * One implementation for every sink that turns translation data into HTML:\n * core's HTMLSerializer/auto-translator, the editor text sinks, the snapshot\n * viewer, react `<Trans>`, and WordPress TS. The WordPress PHP port must\n * mirror `SAFE_TAGS`/`SAFE_ATTRS` exactly (see improvement-plan \u00A74 item 2).\n *\n * Threat model: translation values (and their `raw:` styles) come from the\n * server/KV and may be attacker-controlled \u2014 they are NOT guaranteed to be in\n * canonical form. Everything here is allowlist-based: unknown tags are\n * rejected, unknown attributes dropped, URL schemes checked after entity\n * decoding.\n *\n * @module content/sanitizer/HTMLSanitizer\n */\n\nimport { extractTagName, readRawStyle, serializeOpeningTag } from '../canonical-tag.js';\nimport type { ContentUnit } from '../types.js';\n\n/**\n * Inline formatting elements allowed inside translated content. Block\n * structure lives outside ContentUnit (block-level pipeline), so anything\n * that isn't inline text formatting is rejected outright.\n */\nexport const SAFE_TAGS = new Set([\n 'a', 'abbr', 'b', 'bdi', 'bdo', 'br', 'cite', 'code', 'del', 'dfn',\n 'em', 'i', 'img', 'ins', 'kbd', 'mark', 'q', 's', 'samp', 'small',\n 'span', 'strong', 'sub', 'sup', 'time', 'u', 'var', 'wbr',\n]);\n\n/**\n * Flat attribute allowlist (plus `data-*`/`aria-*` prefixes, handled in\n * `sanitizeOpeningTag`). Kept flat \u2014 not per-tag \u2014 so the PHP mirror stays\n * trivial; an allowed attribute on the wrong tag is inert.\n */\nexport const SAFE_ATTRS = new Set([\n 'alt', 'cite', 'class', 'datetime', 'dir', 'height', 'href', 'id',\n 'lang', 'loading', 'rel', 'role', 'src', 'target', 'title', 'width',\n]);\n\n/** Attributes whose values are URLs and must pass `sanitizeUrl`. */\nconst URL_ATTRS = new Set(['href', 'src', 'cite']);\n\n/**\n * CSS properties allowed through an inline `style` attribute.\n *\n * RISK NOTE \u2014 why `style` never passes through verbatim: with zero\n * JavaScript, an attacker who controls a style value can render a\n * full-viewport overlay (`position:fixed;inset:0` \u2192 pixel-perfect fake\n * login UI on the customer's real domain), fire tracking/exfiltration\n * beacons via `url(...)`, or hide and deface content. Translation data is\n * attacker-writable in our threat model, so `sanitizeStyle` re-parses the\n * declarations and keeps only the properties below: cosmetic text/font\n * properties whose value grammar cannot fetch a URL and cannot reposition\n * or restack the element (no `position`/`inset`/`display`/`z-index`/\n * `opacity`/`transform`, no `background` shorthand). A smuggled `url(...)`\n * inside an allowed property is doubly inert: the value is rejected here,\n * and these properties ignore URL values anyway.\n *\n * CONTINUOUS DEVELOPMENT: this allowlist is intentionally minimal and is\n * expected to grow deliberately as real sites surface legitimate inline\n * styling the strict set drops (tracked as a WATCH item in\n * IMPLEMENTATION.md). When extending it: (1) never add a property that\n * accepts `url()`/`image()`/`element()` values or that affects layout,\n * positioning, or stacking; (2) add both hostile and benign cases to\n * html-sanitizer.test.ts; (3) mirror the change in the WordPress PHP port.\n */\nexport const SAFE_STYLE_PROPS = new Set([\n 'color', 'background-color', 'font-family', 'font-size', 'font-style',\n 'font-weight', 'letter-spacing', 'line-height', 'text-decoration',\n 'text-decoration-color', 'text-decoration-line', 'text-decoration-style',\n 'text-transform', 'vertical-align', 'white-space', 'word-spacing',\n]);\n\n/**\n * Value-level rejections inside an otherwise-allowed declaration: URL/code\n * carriers, and backslashes because CSS escape sequences (`\\75\\72\\6c`)\n * can spell `url(` past a textual check.\n */\nconst DANGEROUS_CSS_VALUE =\n /url\\s*\\(|expression\\s*\\(|image-set\\s*\\(|element\\s*\\(|paint\\s*\\(|javascript:|\\\\|[<>{}@]/i;\n\nconst SAFE_SCHEMES = new Set(['http', 'https', 'mailto', 'tel']);\n\n/**\n * The named entities that matter for scheme smuggling (`&colon;`, `&Tab;`)\n * plus the basic set. Names are case-sensitive, as in HTML.\n */\nconst NAMED_ENTITIES: Record<string, string> = {\n amp: '&', lt: '<', gt: '>', quot: '\"', apos: \"'\", nbsp: '\u00A0',\n colon: ':', semi: ';', sol: '/', bsol: '\\\\',\n Tab: '\\t', NewLine: '\\n',\n};\n\n/**\n * Decode entities exactly once \u2014 mirroring the single decode a browser does\n * when parsing an attribute value out of HTML.\n */\nfunction decodeEntities(value: string): string {\n return value.replace(\n /&(?:#[xX]([0-9a-fA-F]+);?|#(\\d+);?|([a-zA-Z]+);)/g,\n (full, hex: string, dec: string, name: string) => {\n if (hex) return String.fromCodePoint(parseInt(hex, 16));\n if (dec) return String.fromCodePoint(parseInt(dec, 10));\n return NAMED_ENTITIES[name] ?? full;\n },\n );\n}\n\n/** HTML-escape text for safe use in element content or attribute values. */\nexport function escapeHtml(text: string): string {\n return text\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;');\n}\n\n/**\n * Validate a URL attribute value. Returns the value unchanged when safe,\n * or `null` when it must be dropped.\n *\n * Entity-decodes first, then strips the control/whitespace characters\n * browsers ignore inside a scheme (`jav\\tascript:`), then requires any\n * explicit scheme to be on the allowlist. Scheme-less (relative) URLs pass.\n */\nexport function sanitizeUrl(value: string): string | null {\n const decoded = decodeEntities(value);\n // eslint-disable-next-line no-control-regex -- stripping C0 controls/space/DEL is the point\n const probe = decoded.replace(/[\\u0000-\\u0020\\u007f]/g, '').toLowerCase();\n const scheme = /^([a-z][a-z0-9+.-]*):/.exec(probe);\n if (!scheme) return value;\n return SAFE_SCHEMES.has(scheme[1]) ? value : null;\n}\n\n/**\n * Sanitize an inline `style` attribute value by re-parsing its declarations\n * and keeping only `SAFE_STYLE_PROPS` with clean values (see the risk note\n * on `SAFE_STYLE_PROPS`). Returns the rebuilt declaration list in canonical\n * `prop: value; prop: value` form, or `null` when nothing survives \u2014 the\n * caller then drops the attribute entirely.\n */\nexport function sanitizeStyle(value: string): string | null {\n return filterCssDeclarations(value, true);\n}\n\n/**\n * Relaxed CSS profile for the snapshot viewer: keeps ANY standard property\n * (a snapshot is a full captured page \u2014 `position:fixed` navbars and layout\n * styles are legitimate and must render) but still rejects value-level\n * dangers: `url(...)` beacons, `expression()`, CSS escape smuggling. The\n * residual risk (page-authored overlay styles) is accepted for snapshots:\n * the PM is deliberately viewing arbitrary captured pages, and the editor\n * UI itself lives in a Shadow DOM host stacked above the snapshot root.\n */\nexport function sanitizeCssDeclarations(value: string): string | null {\n return filterCssDeclarations(value, false);\n}\n\nfunction filterCssDeclarations(\n value: string,\n requirePropAllowlist: boolean,\n): string | null {\n const decoded = decodeEntities(value);\n const kept: string[] = [];\n for (const declaration of decoded.split(';')) {\n const colon = declaration.indexOf(':');\n if (colon === -1) continue;\n const prop = declaration.slice(0, colon).trim().toLowerCase();\n const val = declaration.slice(colon + 1).trim();\n if (!/^[a-z-]+$/.test(prop)) continue; // also blocks --custom-prop indirection\n if (requirePropAllowlist && !SAFE_STYLE_PROPS.has(prop)) continue;\n if (val === '' || DANGEROUS_CSS_VALUE.test(val)) continue;\n kept.push(`${prop}: ${val}`);\n }\n return kept.length > 0 ? kept.join('; ') : null;\n}\n\n/**\n * Attribute scanner for a (possibly hostile, non-canonical) opening tag.\n * Handles double-quoted, single-quoted, unquoted, and bare boolean attrs.\n */\nconst ATTR_PATTERN =\n /([^\\s=/>\"']+)(?:\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\s>]*)))?/g;\n\n/**\n * Sanitize a raw opening tag (`raw:` style payload). Returns the canonical\n * rebuilt tag with only allowlisted attributes, or `null` when the element\n * itself is not allowed \u2014 callers must then drop the tag entirely (emit the\n * indexed marker verbatim / skip the element).\n */\nexport function sanitizeOpeningTag(rawOpen: string): string | null {\n if (!rawOpen.startsWith('<')) return null;\n const tagName = extractTagName(rawOpen);\n if (!/^[a-z][a-z0-9-]*$/.test(tagName) || !SAFE_TAGS.has(tagName)) {\n return null;\n }\n\n // The slice between `<tagname` and the closing `>` (or end of string \u2014\n // hostile input may be truncated).\n const end = rawOpen.endsWith('/>')\n ? rawOpen.length - 2\n : rawOpen.endsWith('>')\n ? rawOpen.length - 1\n : rawOpen.length;\n const attrSource = rawOpen.slice(1 + tagName.length, end);\n\n const attrs: Record<string, string> = {};\n for (const match of attrSource.matchAll(ATTR_PATTERN)) {\n const name = match[1].toLowerCase();\n const value = match[2] ?? match[3] ?? match[4] ?? '';\n if (name in attrs) continue; // first occurrence wins, as in HTML\n if (name === 'style') {\n // Never verbatim \u2014 re-parsed against the CSS property allowlist.\n const cleanStyle = sanitizeStyle(value);\n if (cleanStyle !== null) attrs[name] = cleanStyle;\n continue;\n }\n const allowed =\n SAFE_ATTRS.has(name) ||\n /^data-[a-z0-9-]*$/.test(name) ||\n /^aria-[a-z0-9-]*$/.test(name);\n if (!allowed) continue;\n if (URL_ATTRS.has(name)) {\n if (sanitizeUrl(value) === null) continue;\n }\n attrs[name] = value;\n }\n\n return serializeOpeningTag(tagName, attrs);\n}\n\n/**\n * Sanitize a ContentUnit at an ingest boundary: run every `raw:` style\n * through {@link sanitizeOpeningTag} so no dangerous element/attribute/scheme\n * enters the translation store. Render-time sinks (HTMLSerializer etc.)\n * sanitize again \u2014 this is the store-side half, protecting every other\n * consumer of stored units (server, MT/TM, exports, the WP PHP port).\n *\n * `text` is never modified, so `generateAutoKey(unit.text)` is unaffected \u2014\n * key parity across surfaces holds even for hostile input. A rejected\n * element's `raw:` style is dropped entirely; the indexed tag left in the\n * text is emitted by serializers as an inert marker. Returns the SAME object\n * when nothing changed, so benign units round-trip identically.\n */\nexport function sanitizeContentUnit(unit: ContentUnit): ContentUnit {\n let changed = false;\n const styles: Record<string, string[]> = {};\n\n for (const [index, styleList] of Object.entries(unit.styles)) {\n const raw = readRawStyle(styleList);\n if (raw === null) {\n styles[index] = styleList; // no raw tag \u2192 nothing to sanitize\n continue;\n }\n const safe = sanitizeOpeningTag(raw);\n if (safe === null) {\n // Element rejected \u2014 drop the raw style (keep any non-raw entries).\n changed = true;\n const rest = styleList.filter((s) => !s.startsWith('raw:'));\n if (rest.length > 0) styles[index] = rest;\n } else if (safe === raw) {\n styles[index] = styleList;\n } else {\n changed = true;\n styles[index] = styleList.map((s) => (s.startsWith('raw:') ? `raw:${safe}` : s));\n }\n }\n\n return changed ? { text: unit.text, styles } : unit;\n}\n", "/**\n * HTML serializer \u2014 converts ContentUnit back to HTML strings (raw-only).\n *\n * Every indexed tag carries a `raw:<opening tag>` style. The serializer\n * routes the stored opening tag through the shared allowlist sanitizer and\n * derives the close tag from the embedded tag name. Void elements get no\n * close tag. Text segments are HTML-escaped (ContentUnit.text holds decoded\n * Unicode \u2014 see MarkupParser.decodeEntities), so serializer output is inert\n * even when the translation value is attacker-controlled. `ContentUnit.text`\n * itself is never modified \u2014 auto_key hashing is unaffected.\n *\n * @module content/serializer/HTMLSerializer\n */\n\nimport type { ISerializer } from './ISerializer.js';\nimport type { ContentUnit } from '../types.js';\nimport { extractTagName, readRawStyle, VOID_TAGS } from '../canonical-tag.js';\nimport { escapeHtml, sanitizeOpeningTag } from '../sanitizer/HTMLSanitizer.js';\n\n/**\n * Serializer that converts ContentUnit format back to HTML strings.\n *\n * @example\n * ```typescript\n * const serializer = new HTMLSerializer();\n * serializer.serialize({\n * text: 'Read our <1>Terms</1>.',\n * styles: { '1': ['raw:<b>'] }\n * });\n * // \u2192 'Read our <b>Terms</b>.'\n * ```\n */\nexport class HTMLSerializer implements ISerializer<string> {\n serialize(unit: ContentUnit): string {\n const { text, styles } = unit;\n const tagPattern = /<(\\/?)?(\\d+)(\\/)?>/g;\n\n let result = '';\n let lastIndex = 0;\n let match;\n\n while ((match = tagPattern.exec(text)) !== null) {\n result += escapeHtml(text.slice(lastIndex, match.index));\n\n const [fullMatch, closingSlash, indexStr] = match;\n const styleList = styles[indexStr] ?? [];\n const rawOpen = readRawStyle(styleList);\n const safeOpen = rawOpen === null ? null : sanitizeOpeningTag(rawOpen);\n\n if (rawOpen && safeOpen !== null) {\n const tagName = extractTagName(rawOpen);\n const isVoid = VOID_TAGS.has(tagName);\n\n if (closingSlash) {\n if (!isVoid) result += `</${tagName}>`;\n } else {\n // Both <N> and <N/> emit the opening tag. For void tags that's the\n // complete element.\n result += safeOpen;\n }\n } else {\n // No raw style, or the sanitizer rejected the element \u2014 preserve the\n // indexed marker (parses as inert text) so the problem is visible\n // rather than silently dropping content, and never emit a close tag\n // for a rejected element.\n result += fullMatch;\n }\n\n lastIndex = match.index + fullMatch.length;\n }\n\n result += escapeHtml(text.slice(lastIndex));\n return result;\n }\n}\n", "/**\n * Live Preview\n * Optimistically updates DOM elements as the user types translations.\n */\n\nimport type { ContentUnit } from '../../content/types.js';\nimport { HTMLSerializer } from '../../content/serializer/HTMLSerializer.js';\n\nconst htmlSerializer = new HTMLSerializer();\n\n/**\n * Update a single element's content with a new translation.\n * Writes to core cache and updates the DOM without full page retranslate.\n */\nexport function applyLivePreview(\n element: Element,\n text: string,\n styles: Record<string, string[]>,\n key: string,\n locale: string,\n namespace: string,\n sourceText?: string,\n rowState?: string,\n): void {\n // Save original HTML before first edit\n if (!element.hasAttribute('data-lr-original')) {\n element.setAttribute('data-lr-original', element.innerHTML);\n }\n\n // Update core cache if available\n const lr = (window as { LionRapid?: { autoTranslator?: { core?: { cache?: { set?: (k: string, v: string, s?: Record<string, string[]>) => void } } } } }).LionRapid;\n const cache = (lr as Record<string, unknown>)?.autoTranslator\n ? getCache()\n : null;\n if (cache) {\n const fullKey = `${locale}:${namespace}:${key}`;\n cache.set(fullKey, text, styles);\n }\n\n // Update DOM. Three branches:\n //\n // 1. Translation has its own indexed markers (`<1>...</1>`) \u2192 serialize\n // normally; the serializer re-wraps each marker with its raw style.\n // Translators position markers freely (Finnish \"Valitse\" wrapping the\n // whole word; Spanish \"<1>aqu\u00ED</1>\" wrapping just one word) and this\n // branch renders whatever they chose.\n //\n // 2. Translation is plain AND the row is still machine-translated AND the\n // source was entirely wrapped in one indexed tag \u2192 MT silently dropped\n // the marker that was in the source. Auto-wrap to rescue the wrapper\n // so links survive the MT step. Gated on `machine_translated` so that\n // a human who deliberately typed plain text (rowState='edited') has\n // their choice respected \u2014 we don't put a link back they removed.\n //\n // 3. Anything else \u2192 write plain textContent. The wrapper, if any, is\n // gone \u2014 that's either by design (human chose plain) or unavoidable\n // (mixed inline content the regex above can't reconstruct).\n const hasIndexedMarkers = /<\\/?[0-9]+\\/?>/.test(text);\n const isMachineTranslated = rowState === 'machine_translated';\n const fullyWrapped = sourceText\n ? sourceText.match(/^<(\\d+)>([\\s\\S]*)<\\/\\1>$/)\n : null;\n\n if (hasIndexedMarkers) {\n const unit: ContentUnit = { text, styles };\n element.innerHTML = htmlSerializer.serialize(unit);\n } else if (isMachineTranslated && fullyWrapped && styles && Object.keys(styles).length > 0) {\n const idx = fullyWrapped[1];\n const wrappedText = `<${idx}>${text}</${idx}>`;\n const unit: ContentUnit = { text: wrappedText, styles };\n element.innerHTML = htmlSerializer.serialize(unit);\n } else {\n element.textContent = text;\n }\n\n // Mark as translated\n element.setAttribute('data-lr-block', key);\n element.setAttribute('data-lionrapid-client', '1');\n}\n\nfunction getCache(): { set: (key: string, value: string, styles?: Record<string, string[]>) => void } | null {\n try {\n const lr = (window as unknown as { LionRapid?: Record<string, unknown> }).LionRapid;\n if (!lr) return null;\n const at = lr.autoTranslator as Record<string, unknown> | undefined;\n if (!at) return null;\n const core = at.core as Record<string, unknown> | undefined;\n if (!core) return null;\n const cache = core.cache as { set?: (key: string, value: string, styles?: Record<string, string[]>) => void } | undefined;\n if (!cache || typeof cache.set !== 'function') return null;\n return cache as { set: (key: string, value: string, styles?: Record<string, string[]>) => void };\n } catch {\n return null;\n }\n}\n", "/**\n * Editor API \u2014 Batch save queue + session auth\n * Queues translation edits and flushes them in a single batch API call.\n * Uses editor session JWTs (es_ prefixed) for authentication with auto-renewal.\n */\n\nimport type { CapturedSnapshot, SnapshotTextEntry } from './snapshot-capture.js';\n\nexport interface QueuedTranslation {\n key: string;\n locale: string;\n namespace: string;\n text: string;\n styles: Record<string, string[]>;\n}\n\n// ============================================\n// Snapshot API types\n// ============================================\n\nexport interface SnapshotListItem {\n snapshotUuid: string;\n pageUrl: string;\n title: string;\n capturedBy: string;\n createdAt: string;\n expiresAt: string;\n textEntryCount: number;\n status: string;\n}\n\nexport interface SnapshotDetail {\n snapshotUuid: string;\n pageUrl: string;\n title: string;\n html: string;\n textEntries: SnapshotTextEntry[];\n viewport: { width: number; height: number; devicePixelRatio: number };\n createdAt: string;\n expiresAt: string;\n}\n\nexport interface TargetStatus {\n text: string;\n rowState: string; // \"empty\" | \"machine_translated\" | \"edited\"\n isConfirmed: boolean;\n isReady: boolean; // approved for delivery\n isLocked: boolean; // workflow locked OR delivery locked\n /** Whether the calling editor session can confirm/reject this row.\n * Server-computed via can_user_edit_row() \u2014 reflects PM/admin override,\n * postreview, and per-stage assignment. False for rows past the user's\n * stage (e.g. translator looking at a row they've already pushed forward).\n * Optional for backward compatibility with older backend versions. */\n canEdit?: boolean;\n /** Current workflow stage index. Optional for backward compatibility. */\n stageIndex?: number;\n}\n\n/** One stage in the project's workflow. */\nexport interface WorkflowStageInfo {\n index: number;\n name: string;\n role: string | null;\n /** True for the Postreview stage \u2014 rows at/past it cannot Advance. */\n isPostreview: boolean;\n}\n\n/** Workflow context for the calling editor session. Cached per locale with a\n * TTL (see fetchWorkflowInfo) \u2014 the workflow can change mid-session and can\n * differ per locale via a per-target override. */\nexport interface EditorWorkflowInfo {\n stages: WorkflowStageInfo[];\n /** Stage indices the user is assigned to via UserTargetLink. Empty for PM/admin. */\n userAssignedStages: number[];\n isPmAdmin: boolean;\n workflowMode: 'per_row' | 'per_file';\n /** Number of non-Postreview (work) stages \u2014 the server's advance boundary:\n * a row with stageIndex >= this is in postreview and cannot Advance.\n * 0 also covers a project with no workflow configured at all. */\n workStageCount: number;\n}\n\n/** Whether a row sits at/past the workflow's postreview boundary \u2014 the same\n * check the server's advance endpoint enforces (\"Row is already past the\n * final stage\"). False when workflow info or the row's stage is unknown:\n * the sidebar then keeps its old behavior and lets the server decide. */\nexport function isRowAtPostreview(\n info: EditorWorkflowInfo | null,\n stageIndex: number | undefined,\n): boolean {\n return info !== null && typeof stageIndex === 'number' && stageIndex >= info.workStageCount;\n}\n\n/** Per-key outcome for a batch item the server refused to write (per-row\n * authorization). The batch analogue of the dashboard save's 423 \"locked\" /\n * 403 \"forbidden\" rejection with a row snapshot. */\nexport interface DeniedRow {\n key: string;\n /** \"locked\" (row locked, PM-only), \"forbidden\" (not assigned to this row/stage),\n * or \"busy\" (target's MT fill in flight \u2014 transient, the edit is retried). */\n reason: 'locked' | 'forbidden' | 'busy' | string;\n /** Server's current translation text (null if the row doesn't exist yet). */\n current_text: string | null;\n}\n\nexport type StatusCallback = (status: 'unsaved' | 'saving' | 'saved' | 'error', message?: string) => void;\n\nexport class EditorAPI {\n private queue: QueuedTranslation[] = [];\n private apiBase: string;\n private sessionToken: string;\n private projectId: string;\n private flushInterval: ReturnType<typeof setInterval> | null = null;\n private renewalInterval: ReturnType<typeof setInterval> | null = null;\n private onStatus: StatusCallback = () => {};\n private onFlushed: (keys: string[]) => void = () => {};\n private onSessionExpired: (() => void) | null = null;\n // Workflow context cache \u2014 keyed by locale (a per-target override can give\n // one locale a different workflow) and TTL-bound (the PM can change the\n // project's workflow from the dashboard mid-session; a boundary pinned at\n // first fetch would render buttons the server refuses). Promises are\n // cached so concurrent callers share one in-flight fetch.\n private workflowInfoCache = new Map<\n string,\n { promise: Promise<EditorWorkflowInfo | null>; fetchedAt: number }\n >();\n private static readonly WORKFLOW_INFO_TTL_MS = 5 * 60 * 1000;\n /** Whether this session may lazily create rows for keys with no target row\n * yet. Mirrors the server's create gate (creation is stage-0 work \u2014 PM/admin\n * or stage-0 assignees); refreshed from targets/status `can_create`.\n * Default true: older backends don't send it and don't gate creates. */\n canCreateMissing = true;\n\n /** True while this locale's target has an MT fill queued/running (refreshed\n * from targets/status `target_busy`). canEdit stays the permission truth\n * during the window; stage transitions would 423 until it clears, and\n * saves are denied as \"busy\" and auto-retried by the flush loop. */\n targetBusy = false;\n\n constructor(apiBase: string, sessionToken: string, projectId: string) {\n this.apiBase = apiBase.replace(/\\/$/, '');\n this.sessionToken = sessionToken;\n this.projectId = projectId;\n }\n\n setStatusCallback(cb: StatusCallback): void {\n this.onStatus = cb;\n }\n\n setFlushedCallback(cb: (keys: string[]) => void): void {\n this.onFlushed = cb;\n }\n\n setSessionExpiredCallback(cb: () => void): void {\n this.onSessionExpired = cb;\n }\n\n /**\n * Start auto-flush (30s interval + visibilitychange) and session renewal (60s check)\n */\n startAutoFlush(): void {\n this.flushInterval = setInterval(() => this.flush(), 30_000);\n document.addEventListener('visibilitychange', this.handleVisibility);\n\n // Check session renewal every 60s\n this.renewalInterval = setInterval(() => this.maybeRenewSession(), 60_000);\n }\n\n /**\n * Stop auto-flush and flush remaining items\n */\n async stopAndFlush(): Promise<void> {\n if (this.flushInterval) {\n clearInterval(this.flushInterval);\n this.flushInterval = null;\n }\n if (this.renewalInterval) {\n clearInterval(this.renewalInterval);\n this.renewalInterval = null;\n }\n document.removeEventListener('visibilitychange', this.handleVisibility);\n await this.flush();\n }\n\n /**\n * Queue a translation for saving\n */\n queueSave(item: QueuedTranslation): void {\n // Replace existing entry for same key+locale\n const idx = this.queue.findIndex(\n (q) => q.key === item.key && q.locale === item.locale && q.namespace === item.namespace\n );\n if (idx >= 0) {\n this.queue[idx] = item;\n } else {\n this.queue.push(item);\n }\n this.onStatus('unsaved', `${this.queue.length} unsaved`);\n\n // Auto-flush if queue is large\n if (this.queue.length > 50) {\n this.flush();\n }\n }\n\n /**\n * Flush all queued translations to the server\n */\n async flush(): Promise<void> {\n if (this.queue.length === 0) return;\n\n const items = [...this.queue];\n this.onStatus('saving');\n\n try {\n const response = await this.authFetch(\n `${this.apiBase}/api/integrations/targets/batch`,\n {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n translations: items.map((t) => ({\n key: `${t.locale}:${t.namespace}:${t.key}`,\n value: { text: t.text, styles: t.styles },\n })),\n }),\n }\n );\n\n if (response.status === 401) {\n this.handleSessionExpired();\n return;\n }\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`);\n }\n\n // Per-key authorization outcomes (dashboard parity): the server writes\n // what it can and reports refused rows in denied_rows instead of failing\n // the batch. A 200 with denied rows must NOT read as \"all saved\".\n const body = (await response.json().catch(() => null)) as {\n denied?: number;\n denied_rows?: DeniedRow[];\n } | null;\n const deniedRows = body?.denied_rows ?? [];\n // \"busy\" (TARGET_BUSY: the target's MT fill is in flight) is transient \u2014\n // keep those edits queued and retry after the fill's sweep window,\n // instead of reverting the draft like a real authorization denial.\n const busyKeys = new Set(\n deniedRows.filter((d) => d.reason === 'busy').map((d) => d.key)\n );\n const hardDenied = deniedRows.filter((d) => d.reason !== 'busy');\n\n // Remove flushed items from queue (new items may have been added during\n // fetch) \u2014 except busy ones, which stay queued for the retry flush.\n const retained: QueuedTranslation[] = [];\n for (const item of items) {\n const idx = this.queue.indexOf(item);\n if (busyKeys.has(`${item.locale}:${item.namespace}:${item.key}`)) {\n retained.push(item);\n continue;\n }\n if (idx >= 0) this.queue.splice(idx, 1);\n }\n\n // Notify sidebar to re-fetch status for flushed keys \u2014 for denied rows\n // the refetch restores the server's current text in place of the\n // refused edit (the batch analogue of the dashboard's 423/403 snapshot).\n // Busy rows are excluded: their draft is still pending, not refused.\n this.onFlushed(\n items\n .filter((i) => !busyKeys.has(`${i.locale}:${i.namespace}:${i.key}`))\n .map((i) => i.key)\n );\n\n if (retained.length > 0) {\n this.scheduleBusyRetry();\n }\n\n if (hardDenied.length > 0) {\n const lockedCount = hardDenied.filter((d) => d.reason === 'locked').length;\n const message =\n hardDenied.length === 1\n ? lockedCount === 1\n ? '1 change not saved \u2014 row is locked (only a PM can edit it)'\n : '1 change not saved \u2014 you are not assigned to that row'\n : `${hardDenied.length} changes not saved \u2014 locked or not assigned to you`;\n this.onStatus('error', message);\n } else if (retained.length > 0) {\n this.onStatus(\n 'unsaved',\n `${retained.length} waiting \u2014 machine translation is filling this page, retrying shortly`\n );\n } else if (this.queue.length === 0) {\n this.onStatus('saved', 'All changes saved');\n } else {\n this.onStatus('unsaved', `${this.queue.length} unsaved`);\n }\n } catch (err) {\n this.onStatus('error', `Save failed: ${(err as Error).message}`);\n }\n }\n\n private busyRetryTimer: ReturnType<typeof setTimeout> | null = null;\n\n /**\n * Re-flush edits refused with reason \"busy\" (target MT fill in flight).\n * One pipeline sweep (~30 s worst case) clears the fill; a single pending\n * timer is enough \u2014 each retry re-schedules itself while busy rows remain.\n */\n private scheduleBusyRetry(): void {\n if (this.busyRetryTimer) return;\n this.busyRetryTimer = setTimeout(() => {\n this.busyRetryTimer = null;\n this.flush();\n }, 8000);\n }\n\n private statusAbort: AbortController | null = null;\n\n /**\n * Fetch translation status for a batch of keys (includes unconfirmed rows).\n * Cancels any in-flight status request. Caps at 200 keys per request.\n */\n async fetchStatus(\n keys: string[],\n locale: string,\n namespace: string\n ): Promise<Record<string, TargetStatus | null>> {\n if (keys.length === 0) return {};\n\n // Cancel previous in-flight request\n if (this.statusAbort) this.statusAbort.abort();\n this.statusAbort = new AbortController();\n\n // Cap keys to avoid exceeding URL length limits\n const batch = keys.slice(0, 200);\n\n const params = new URLSearchParams({ locale, namespace, keys: batch.join(',') });\n try {\n const response = await this.authFetch(\n `${this.apiBase}/api/integrations/targets/status?${params}`,\n { signal: this.statusAbort.signal }\n );\n if (response.status === 401) {\n this.handleSessionExpired();\n return {};\n }\n if (!response.ok) return {};\n const data = await response.json();\n // Server sends can_create when the caller is an editor session \u2014 false\n // means keys WITHOUT a row must render read-only (the batch save would\n // refuse the lazy create). Absent (older backend) = allowed.\n this.canCreateMissing = data.can_create !== false;\n // Absent (older backend) = not busy \u2014 old servers bake the fill gate\n // into canEdit instead, so nothing extra to surface.\n this.targetBusy = data.target_busy === true;\n return data.translations;\n } catch (err) {\n if ((err as Error).name === 'AbortError') return {};\n return {};\n }\n }\n\n get pendingCount(): number {\n return this.queue.length;\n }\n\n /**\n * Fetch workflow context (stages, user's stage assignments, PM/admin flag,\n * advance boundary) for the EFFECTIVE workflow of `locale` \u2014 the server\n * resolves the per-target override (`target ?? project`) when a locale is\n * given. Cached per locale with a 5-min TTL so a mid-session workflow\n * change from the dashboard settings is picked up without a page reload.\n * Returns null if the fetch fails; callers should hide the strip in that case.\n */\n async fetchWorkflowInfo(locale?: string): Promise<EditorWorkflowInfo | null> {\n const cacheKey = locale ?? '';\n const cached = this.workflowInfoCache.get(cacheKey);\n if (cached && Date.now() - cached.fetchedAt < EditorAPI.WORKFLOW_INFO_TTL_MS) {\n return cached.promise;\n }\n const promise = (async (): Promise<EditorWorkflowInfo | null> => {\n try {\n const url =\n `${this.apiBase}/api/integrations/editor/workflow` +\n (locale ? `?locale=${encodeURIComponent(locale)}` : '');\n const response = await this.authFetch(url);\n if (response.status === 401) {\n this.handleSessionExpired();\n return null;\n }\n if (!response.ok) return null;\n const data = await response.json();\n const stages: WorkflowStageInfo[] = (Array.isArray(data.stages) ? data.stages : []).map(\n (s: { index: number; name: string; role: string | null; is_postreview?: boolean }) => ({\n index: s.index,\n name: s.name,\n role: s.role ?? null,\n isPostreview: Boolean(s.is_postreview),\n })\n );\n return {\n stages,\n userAssignedStages: Array.isArray(data.user_assigned_stages) ? data.user_assigned_stages : [],\n isPmAdmin: Boolean(data.is_pm_admin),\n workflowMode: data.workflow_mode === 'per_file' ? 'per_file' : 'per_row',\n // Older backends don't send work_stage_count; before Postreview\n // stages existed the boundary was len(stages), so fall back to that.\n workStageCount:\n typeof data.work_stage_count === 'number' ? data.work_stage_count : stages.length,\n };\n } catch {\n return null;\n }\n })();\n this.workflowInfoCache.set(cacheKey, { promise, fetchedAt: Date.now() });\n return promise;\n }\n\n // ============================================\n // Session renewal\n // ============================================\n\n /**\n * Check if session is within 5min of expiry, and renew if so.\n *\n * The threshold is intentionally generous because background tabs throttle\n * setInterval to once-per-minute (Chrome) or even less aggressively, and\n * a sleeping laptop pauses timers entirely. Renewing early gives us\n * headroom for those cases \u2014 the renewal interval still runs before the\n * 15min server-side TTL elapses even if it fires a minute or two late.\n */\n private async maybeRenewSession(): Promise<void> {\n const exp = this.getSessionExp();\n if (!exp) return;\n\n const timeLeft = exp * 1000 - Date.now();\n if (timeLeft > 300_000) return; // More than 5 min left, skip\n\n await this.renewSession();\n }\n\n /**\n * Attempt one renewal and signal whether the token actually changed.\n * Used by the 401-retry path in API calls.\n */\n private async tryRenew(): Promise<boolean> {\n const before = this.sessionToken;\n await this.renewSession();\n return this.sessionToken !== before;\n }\n\n private async renewSession(): Promise<void> {\n try {\n const response = await fetch(`${this.apiBase}/api/editor/renew`, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${this.sessionToken}`,\n },\n });\n\n if (response.status === 401) {\n this.handleSessionExpired();\n return;\n }\n\n if (!response.ok) return;\n\n const data = await response.json();\n this.sessionToken = data.session_token;\n\n // Update sessionStorage + preview cookie. The cookie tells the edge\n // proxy this is an active editor session so it should fetch drafts.\n try {\n const raw = this.sessionToken.startsWith('es_') ? this.sessionToken.slice(3) : this.sessionToken;\n const parts = raw.split('.');\n if (parts.length === 3) {\n const payload = JSON.parse(atob(parts[1]));\n sessionStorage.setItem('__lr_editor_session', JSON.stringify({\n token: this.sessionToken,\n exp: payload.exp || 0,\n }));\n document.cookie = '__lr_editor_active=1; path=/; SameSite=Lax';\n }\n } catch { /* non-critical */ }\n } catch {\n // Network error \u2014 will retry on next interval\n }\n }\n\n private getSessionExp(): number | null {\n try {\n const raw = this.sessionToken.startsWith('es_') ? this.sessionToken.slice(3) : this.sessionToken;\n const parts = raw.split('.');\n if (parts.length !== 3) return null;\n const payload = JSON.parse(atob(parts[1]));\n return payload.exp || null;\n } catch {\n return null;\n }\n }\n\n /**\n * Authenticated fetch with one-shot 401 retry. If the request comes back\n * 401, we attempt a session renewal and replay the request once with the\n * new token. If renewal fails (token past the server's 2min grace), the\n * caller still gets a 401 and falls through to handleSessionExpired.\n *\n * Always passes `init.signal` straight through so abortable callers\n * (e.g. fetchStatus) still cancel correctly.\n */\n private async authFetch(url: string, init: RequestInit = {}): Promise<Response> {\n const buildHeaders = (): HeadersInit => {\n const h = new Headers(init.headers);\n h.set('Authorization', `Bearer ${this.sessionToken}`);\n return h;\n };\n\n let response = await fetch(url, { ...init, headers: buildHeaders() });\n if (response.status === 401) {\n const renewed = await this.tryRenew();\n if (renewed) {\n response = await fetch(url, { ...init, headers: buildHeaders() });\n }\n }\n return response;\n }\n\n private handleSessionExpired(): void {\n // Clear the preview cookie too so the edge stops serving drafts as\n // soon as the session is gone, even if the page stays open.\n document.cookie = '__lr_editor_active=; path=/; SameSite=Lax; Max-Age=0';\n this.onStatus('error', 'Session expired. Please reopen the editor from the dashboard.');\n if (this.onSessionExpired) {\n this.onSessionExpired();\n }\n }\n\n private handleVisibility = (): void => {\n if (document.visibilityState === 'hidden') {\n this.flush();\n } else if (document.visibilityState === 'visible') {\n // Tab returned to foreground. Background timers throttle/pause, so\n // by the time the user comes back the session may be close to (or\n // past) expiry. Try a renewal immediately \u2014 if still within the\n // 2min server grace, this saves the next API call from 401.\n this.maybeRenewSession();\n }\n };\n\n // ============================================\n // Snapshot API\n // ============================================\n\n /**\n * Upload a captured snapshot. Returns the created snapshot's UUID so the\n * caller can build a share URL (`?__lr_snapshot={uuid}`).\n */\n async createSnapshot(\n snapshot: CapturedSnapshot,\n ): Promise<{ snapshotUuid: string } | null> {\n try {\n const response = await fetch(`${this.apiBase}/api/editor/snapshots`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.sessionToken}`,\n },\n body: JSON.stringify({\n pageUrl: snapshot.pageUrl,\n title: snapshot.title,\n viewport: snapshot.viewport,\n textEntries: snapshot.textEntries,\n html: snapshot.html,\n }),\n });\n\n if (response.status === 401) {\n this.handleSessionExpired();\n return null;\n }\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`);\n }\n\n const data = await response.json();\n return { snapshotUuid: data.snapshot_uuid || data.snapshotUuid };\n } catch (err) {\n this.onStatus('error', `Snapshot failed: ${(err as Error).message}`);\n return null;\n }\n }\n\n /**\n * Re-capture an existing snapshot. Server appends a new version and keeps\n * the same `snapshotUuid`. Returns the new version number on success.\n */\n async recaptureSnapshot(\n snapshotUuid: string,\n snapshot: CapturedSnapshot,\n ): Promise<{ snapshotUuid: string; version: number } | null> {\n try {\n const response = await fetch(\n `${this.apiBase}/api/editor/snapshots/${encodeURIComponent(snapshotUuid)}`,\n {\n method: 'PUT',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.sessionToken}`,\n },\n body: JSON.stringify({\n pageUrl: snapshot.pageUrl,\n title: snapshot.title,\n viewport: snapshot.viewport,\n textEntries: snapshot.textEntries,\n html: snapshot.html,\n }),\n },\n );\n\n if (response.status === 401) {\n this.handleSessionExpired();\n return null;\n }\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`);\n }\n\n const data = await response.json();\n return {\n snapshotUuid: data.snapshot_uuid || data.snapshotUuid,\n version: data.version || 1,\n };\n } catch (err) {\n this.onStatus('error', `Re-capture failed: ${(err as Error).message}`);\n return null;\n }\n }\n\n /**\n * Fetch a single snapshot by UUID. Returns null on auth failure or 404.\n */\n async getSnapshot(snapshotUuid: string): Promise<SnapshotDetail | null> {\n try {\n const response = await fetch(\n `${this.apiBase}/api/editor/snapshots/${encodeURIComponent(snapshotUuid)}`,\n {\n headers: { 'Authorization': `Bearer ${this.sessionToken}` },\n },\n );\n if (response.status === 401) {\n this.handleSessionExpired();\n return null;\n }\n if (!response.ok) return null;\n return (await response.json()) as SnapshotDetail;\n } catch {\n return null;\n }\n }\n\n /**\n * List all snapshots the current editor session can see.\n */\n async listSnapshots(): Promise<SnapshotListItem[]> {\n try {\n const response = await fetch(`${this.apiBase}/api/editor/snapshots`, {\n headers: { 'Authorization': `Bearer ${this.sessionToken}` },\n });\n if (response.status === 401) {\n this.handleSessionExpired();\n return [];\n }\n if (!response.ok) return [];\n const data = await response.json();\n return data.snapshots || [];\n } catch {\n return [];\n }\n }\n\n /**\n * Delete (soft) a snapshot by UUID.\n */\n async deleteSnapshot(snapshotUuid: string): Promise<boolean> {\n try {\n const response = await fetch(\n `${this.apiBase}/api/editor/snapshots/${encodeURIComponent(snapshotUuid)}`,\n {\n method: 'DELETE',\n headers: { 'Authorization': `Bearer ${this.sessionToken}` },\n },\n );\n if (response.status === 401) {\n this.handleSessionExpired();\n return false;\n }\n return response.ok;\n } catch {\n return false;\n }\n }\n\n /**\n * Workflow transitions (Phase 3). Per-key endpoints on the integrations API\n * that mirror the dashboard's row-level transitions but are addressable by\n * composite editor key (locale:namespace:auto_xxx).\n *\n * Returns the parsed JSON on success, or null on failure (caller usually\n * shows a toast / re-fetches status).\n */\n async transitionTarget(\n composite: string,\n action: 'confirm' | 'unconfirm' | 'reject' | 'advance' | 'mark-ready' | 'unmark-ready',\n ): Promise<Record<string, unknown> | null> {\n try {\n const response = await this.authFetch(\n `${this.apiBase}/api/integrations/targets/${encodeURIComponent(composite)}/${action}`,\n { method: 'POST' },\n );\n if (response.status === 401) {\n this.handleSessionExpired();\n return null;\n }\n if (!response.ok) {\n // 423 TARGET_BUSY: the target's MT fill is in flight \u2014 transient\n // (clears within one pipeline sweep), not a permissions failure.\n const errBody = (await response.json().catch(() => null)) as {\n error?: { code?: string };\n } | null;\n if (response.status === 423 && errBody?.error?.code === 'TARGET_BUSY') {\n this.onStatus(\n 'error',\n 'Machine translation is filling this page \u2014 try again in a few seconds.'\n );\n } else {\n this.onStatus('error', `${action} failed: HTTP ${response.status}`);\n }\n return null;\n }\n return (await response.json()) as Record<string, unknown>;\n } catch (err) {\n this.onStatus('error', `${action} failed: ${(err as Error).message}`);\n return null;\n }\n }\n}\n", "/**\n * Exclude Filter\n * Filters out text that should not be translated\n * Mirrors PHP's HtmlTranslator logic\n */\n\nexport interface ExcludePatterns {\n emails: boolean;\n urls: boolean;\n numbers: boolean;\n phones: boolean;\n}\n\nconst DEFAULT_PATTERNS: ExcludePatterns = {\n emails: true,\n urls: true,\n numbers: true,\n phones: true,\n};\n\n// Regex patterns matching PHP's HtmlTranslator\nconst PATTERNS = {\n email: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$/,\n url: /^(https?:\\/\\/|www\\.)[^\\s]+$/i,\n number: /^[\\d\\s.,+-]+$/,\n phone: /^[+\\d][\\d\\s().-]{6,}$/,\n};\n\nexport class ExcludeFilter {\n private patterns: ExcludePatterns;\n private minTextLength: number;\n\n constructor(patterns: Partial<ExcludePatterns> = {}, minTextLength = 2) {\n this.patterns = { ...DEFAULT_PATTERNS, ...patterns };\n this.minTextLength = minTextLength;\n }\n\n /**\n * Check if text should be excluded from translation\n */\n shouldExclude(text: string): boolean {\n const trimmed = text.trim();\n\n // Too short\n if (trimmed.length < this.minTextLength) {\n return true;\n }\n\n // Check auto-exclude patterns\n if (this.patterns.emails && PATTERNS.email.test(trimmed)) {\n return true;\n }\n\n if (this.patterns.urls && PATTERNS.url.test(trimmed)) {\n return true;\n }\n\n if (this.patterns.numbers && PATTERNS.number.test(trimmed)) {\n return true;\n }\n\n if (this.patterns.phones && PATTERNS.phone.test(trimmed)) {\n return true;\n }\n\n return false;\n }\n}\n", "/**\n * Block Extractor\n * Finds translatable block elements in DOM and parses them into ContentUnit.\n * Mirrors WordPress TS BlockExtractor and edge proxy block-level approach.\n */\n\nimport type { ContentUnit } from '../content/types.js';\nimport { ExcludeFilter, ExcludePatterns } from './exclude-filter.js';\nimport { parseBlockElement } from './markup-parser.js';\nimport { generateAutoKey } from './md5.js';\n\nconst BLOCK_SELECTOR =\n 'p,h1,h2,h3,h4,h5,h6,li,td,th,label,button,figcaption,dt,dd,summary,caption';\n\n// Inline elements eligible for \"orphan\" extraction when they have no block\n// ancestor. Without this, markup like `<div><a>Transfer</a></div>` produces\n// zero translatable units because <div>/<a> are absent from BLOCK_SELECTOR\n// and the link has no <p>/<li>/etc. parent to absorb it as an indexed style.\n// Conservative on purpose: only <a>. <span>/<button> can join later if real\n// usage demands it (button is already a block tag).\nconst INLINE_BLOCK_SELECTOR = 'a';\n\n// Tags to skip (same as PHP/edge)\nconst SKIP_TAGS = new Set([\n 'SCRIPT',\n 'STYLE',\n 'CODE',\n 'PRE',\n 'TEXTAREA',\n 'NOSCRIPT',\n 'SVG',\n]);\n\n// Attributes that mark elements to skip\nconst SKIP_ATTRIBUTES = [\n 'data-lionrapid-translated', // Server already translated\n 'data-lionrapid-client', // Client already translated\n 'data-no-translate', // Explicitly marked to skip\n // Edge proxy already rendered this block with the correct translation\n // and styles. Skip in normal extraction so the client-side auto-translator\n // can't overwrite the styled DOM with a stale (or plain-text) cache entry.\n // The editor passes includeTranslated=true to bypass this and re-extract\n // edge-rendered blocks for inspection.\n 'data-lr-block',\n];\n\nexport interface ExtractedBlock {\n element: Element;\n unit: ContentUnit;\n key: string;\n}\n\nexport class BlockExtractor {\n private excludeFilter: ExcludeFilter;\n private excludeSelectors: string[];\n\n constructor(\n excludePatterns: Partial<ExcludePatterns> = {},\n excludeSelectors: string[] = [],\n minTextLength = 2\n ) {\n this.excludeFilter = new ExcludeFilter(excludePatterns, minTextLength);\n this.excludeSelectors = excludeSelectors;\n }\n\n /**\n * Extract all translatable block elements from a root.\n * @param includeTranslated - If true, include elements already marked as translated (for editor mode)\n */\n extract(root: Element | Document, includeTranslated = false): ExtractedBlock[] {\n const results: ExtractedBlock[] = [];\n const descendants = root.querySelectorAll(BLOCK_SELECTOR);\n\n // Include the root itself if it matches the block selector\n const elements: Element[] = [];\n if (root instanceof Element && root.matches(BLOCK_SELECTOR)) {\n elements.push(root);\n }\n for (let i = 0; i < descendants.length; i++) {\n elements.push(descendants[i]);\n }\n\n for (let i = 0; i < elements.length; i++) {\n const el = elements[i];\n\n // Skip if inside a SKIP_TAG ancestor\n if (this.isInsideSkipTag(el)) {\n continue;\n }\n\n // Skip if has skip attributes (unless editor mode includes translated)\n if (!includeTranslated && this.hasSkipAttribute(el)) {\n continue;\n }\n\n // Skip if matches exclude selectors\n if (this.matchesExcludeSelector(el)) {\n continue;\n }\n\n // Skip if has translate=\"no\"\n if (el.getAttribute('translate') === 'no') {\n continue;\n }\n\n // Skip nested block elements (let the inner blocks be extracted)\n if (el.querySelector(BLOCK_SELECTOR)) {\n continue;\n }\n\n // For already-translated elements, use original HTML for key + unit\n const lrBlock = el.getAttribute('data-lr-block');\n const lrOriginal = el.getAttribute('data-lr-original');\n\n let unit: ContentUnit;\n if (lrOriginal) {\n // Parse original (source) HTML \u2192 correct ContentUnit for key generation\n const temp = document.createElement(el.tagName);\n temp.innerHTML = lrOriginal;\n unit = parseBlockElement(temp);\n } else {\n unit = parseBlockElement(el);\n }\n\n // Strip indexed tags for exclude filter\n const strippedText = unit.text.replace(/<\\/?[0-9]+\\/?>/g, '');\n\n // Apply exclude filter\n if (this.excludeFilter.shouldExclude(strippedText)) {\n continue;\n }\n\n // Key priority: data-lr-block (translated) \u2192 data-i18n (named) \u2192 auto-generate\n let key: string;\n if (lrBlock) {\n key = lrBlock;\n } else {\n const dataI18n = el.getAttribute('data-i18n');\n if (dataI18n) {\n key = dataI18n.includes(':') ? dataI18n : `common:${dataI18n}`;\n } else {\n key = generateAutoKey(unit.text);\n }\n }\n\n results.push({ element: el, unit, key });\n }\n\n this.extractOrphanInline(root, results, includeTranslated);\n\n return results;\n }\n\n /**\n * Second pass: extract inline elements (currently <a>) that have no block\n * ancestor. These would otherwise slip through both the block pass (not in\n * BLOCK_SELECTOR) and the inline-style capture (no parent block to host\n * them as `<1>...</1>`). Common shape: a `<div>` action group containing\n * only links.\n */\n private extractOrphanInline(\n root: Element | Document,\n results: ExtractedBlock[],\n includeTranslated: boolean\n ): void {\n const candidates = root.querySelectorAll(INLINE_BLOCK_SELECTOR);\n const elements: Element[] = [];\n if (root instanceof Element && root.matches(INLINE_BLOCK_SELECTOR)) {\n elements.push(root);\n }\n for (let i = 0; i < candidates.length; i++) {\n elements.push(candidates[i]);\n }\n\n for (let i = 0; i < elements.length; i++) {\n const el = elements[i];\n\n if (this.isInsideSkipTag(el)) continue;\n if (!includeTranslated && this.hasSkipAttribute(el)) continue;\n if (this.matchesExcludeSelector(el)) continue;\n if (el.getAttribute('translate') === 'no') continue;\n\n // Has a block ancestor \u2014 already captured as `<N>...</N>` by the\n // block pass (or its parent block was skipped, in which case we leave\n // it alone to avoid double-counting from a partially-skipped block).\n if (el.parentElement?.closest(BLOCK_SELECTOR)) continue;\n\n // Outer orphan wins: `<a><span>X</span></a>` \u2192 only the outer <a>.\n // (Currently INLINE_BLOCK_SELECTOR is just <a>, so this guards\n // against nested links in malformed HTML.)\n if (el.parentElement?.closest(INLINE_BLOCK_SELECTOR)) continue;\n\n // Contains a nested block \u2014 let the inner block be extracted instead.\n if (el.querySelector(BLOCK_SELECTOR)) continue;\n\n const lrOriginal = el.getAttribute('data-lr-original');\n let unit: ContentUnit;\n if (lrOriginal) {\n const temp = document.createElement(el.tagName);\n temp.innerHTML = lrOriginal;\n unit = parseBlockElement(temp);\n } else {\n unit = parseBlockElement(el);\n }\n\n const strippedText = unit.text.replace(/<\\/?[0-9]+\\/?>/g, '');\n if (this.excludeFilter.shouldExclude(strippedText)) continue;\n\n const lrBlock = el.getAttribute('data-lr-block');\n let key: string;\n if (lrBlock) {\n key = lrBlock;\n } else {\n const dataI18n = el.getAttribute('data-i18n');\n if (dataI18n) {\n key = dataI18n.includes(':') ? dataI18n : `common:${dataI18n}`;\n } else {\n key = generateAutoKey(unit.text);\n }\n }\n\n results.push({ element: el, unit, key });\n }\n }\n\n private isInsideSkipTag(element: Element): boolean {\n let current: Element | null = element.parentElement;\n while (current) {\n // Foreign elements (SVG/MathML) report a case-preserved tagName (\"svg\",\n // not \"SVG\"), so match case-insensitively or the SVG skip never fires.\n if (SKIP_TAGS.has(current.tagName.toUpperCase())) {\n return true;\n }\n current = current.parentElement;\n }\n return false;\n }\n\n private hasSkipAttribute(element: Element): boolean {\n let current: Element | null = element;\n while (current) {\n for (const attr of SKIP_ATTRIBUTES) {\n if (current.hasAttribute(attr)) {\n return true;\n }\n }\n current = current.parentElement;\n }\n return false;\n }\n\n private matchesExcludeSelector(element: Element): boolean {\n for (const selector of this.excludeSelectors) {\n try {\n if (element.matches(selector) || element.closest(selector)) {\n return true;\n }\n } catch {\n // Invalid selector, skip\n }\n }\n return false;\n }\n}\n", "/**\n * Locale-preserving URL helper.\n *\n * When the operator is browsing on a localized URL (e.g. `/es/about/`) and\n * follows an internal link whose href doesn't include the locale prefix\n * (e.g. `/contact/`), naive navigation drops them back to the default\n * language. This helper rewrites same-origin URLs to keep the current\n * locale prefix in front, matching the `/{lang}/{path}` shape the server\n * already routes.\n *\n * No-op when:\n * - href is cross-origin (don't touch external links)\n * - href is malformed (can't parse \u2014 leave as-is)\n * - the current locale IS the default locale (no prefix needed)\n * - the URL already has a locale prefix (don't double-prefix)\n */\nexport function localizeUrl(href: string, defaultLocale: string): string {\n let url: URL;\n try {\n url = new URL(href, window.location.href);\n } catch {\n return href;\n }\n if (url.origin !== window.location.origin) return href;\n\n const currentMatch = window.location.pathname.match(/^\\/([a-z]{2})(\\/|$)/);\n const currentLocale = currentMatch ? currentMatch[1] : defaultLocale;\n if (currentLocale === defaultLocale) return url.href;\n\n // Already locale-prefixed \u2014 leave alone.\n if (/^\\/[a-z]{2}(\\/|$)/.test(url.pathname)) return url.href;\n\n url.pathname = '/' + currentLocale + url.pathname;\n return url.href;\n}\n", "/**\n * Sidebar \u2014 Translation editing panel\n * Shows original text, translation textarea, live preview, and page blocks list.\n */\n\nimport type { ContentUnit } from '../../content/types.js';\nimport type { ResolvedKey } from './key-resolver.js';\nimport { HTMLSerializer } from '../../content/serializer/HTMLSerializer.js';\nimport { escapeHtml } from '../../content/sanitizer/HTMLSanitizer.js';\nimport { applyLivePreview } from './live-preview.js';\nimport type { EditorAPI, TargetStatus, EditorWorkflowInfo } from './editor-api.js';\nimport { isRowAtPostreview } from './editor-api.js';\nimport { BlockExtractor } from '../block-extractor.js';\nimport { generateAutoKey } from '../md5.js';\nimport { localizeUrl } from './locale-url.js';\n\nconst htmlSerializer = new HTMLSerializer();\n\nexport class Sidebar {\n private el: HTMLDivElement;\n private bodyEl: HTMLDivElement;\n private statusEl: HTMLDivElement;\n private api: EditorAPI;\n private locale: string;\n private namespace: string;\n private defaultLocale: string;\n private current: ResolvedKey | null = null;\n private blockExtractor: BlockExtractor;\n private onBlockClick: (resolved: ResolvedKey) => void;\n private statusMap: Map<string, TargetStatus | null> = new Map();\n private unsavedKeys: Set<string> = new Set();\n private onProgressUpdate: ((translated: number, total: number) => void) | null = null;\n private lastFetchTime = 0;\n private lastFlushTime = 0;\n\n private userRole: string;\n // Latest known workflow context for this.locale. Revalidated on every strip\n // render through the API's TTL-bound, per-locale cache \u2014 the workflow can\n // change mid-session (dashboard settings) and can differ per locale\n // (per-target override), so it must not be pinned at first fetch.\n private workflowInfo: EditorWorkflowInfo | null = null;\n\n constructor(\n shadowRoot: ShadowRoot,\n api: EditorAPI,\n locale: string,\n namespace: string,\n defaultLocale: string,\n onBlockClick: (resolved: ResolvedKey) => void,\n userRole: string = '',\n ) {\n this.api = api;\n this.locale = locale;\n this.namespace = namespace;\n this.defaultLocale = defaultLocale;\n this.onBlockClick = onBlockClick;\n this.userRole = userRole;\n this.blockExtractor = new BlockExtractor();\n\n this.el = document.createElement('div');\n this.el.className = 'lr-sidebar';\n\n // Resize handle\n const resizeHandle = document.createElement('div');\n resizeHandle.className = 'lr-sidebar-resize';\n this.initResize(resizeHandle);\n this.el.appendChild(resizeHandle);\n\n // Header\n const header = document.createElement('div');\n header.className = 'lr-sidebar-header';\n const title = document.createElement('span');\n title.className = 'lr-sidebar-title';\n title.textContent = 'Edit Translation';\n header.appendChild(title);\n const closeBtn = document.createElement('button');\n closeBtn.className = 'lr-sidebar-close';\n closeBtn.innerHTML = '&times;';\n closeBtn.addEventListener('click', () => this.close());\n header.appendChild(closeBtn);\n this.el.appendChild(header);\n\n // Body\n this.bodyEl = document.createElement('div');\n this.bodyEl.className = 'lr-sidebar-body';\n this.el.appendChild(this.bodyEl);\n\n // Status bar (rendered inline in renderContent, above preview)\n this.statusEl = document.createElement('div');\n this.statusEl.className = 'lr-status';\n\n // Wire up API status\n this.api.setStatusCallback((status, message) => {\n this.statusEl.textContent = message || status;\n this.statusEl.className = `lr-status ${status}`;\n });\n\n // After flush, re-fetch status for saved keys and refresh blocks list\n this.api.setFlushedCallback(async (keys) => {\n this.lastFlushTime = Date.now();\n const freshStatuses = await this.api.fetchStatus(keys, this.locale, this.namespace);\n for (const [key, status] of Object.entries(freshStatuses)) {\n this.statusMap.set(key, status);\n this.unsavedKeys.delete(key);\n }\n this.refreshBlocksList();\n this.emitProgress();\n });\n\n // Console diagnostic: dump every block's key + full ContentUnit text +\n // styles so the operator can compare against what's stored on the\n // server. Useful when the same source sentence appears with multiple\n // auto_keys (PHP\u2194JS parity divergence) \u2014 paste the output here and\n // we'll spot the difference.\n //\n // Usage from DevTools: __lr_dump_keys()\n if (typeof window !== 'undefined') {\n (window as unknown as Record<string, unknown>).__lr_dump_keys = () => {\n const blocks = this.blockExtractor.extract(document.body, true);\n const rows = blocks.map((b, i) => {\n const s = this.statusMap.get(b.key);\n return {\n i,\n key: b.key,\n text: b.unit.text,\n stylesJson: JSON.stringify(b.unit.styles),\n statusText: s?.text ?? '',\n isReady: !!s?.isReady,\n hasTranslation: !!s?.text,\n };\n });\n console.table(rows);\n return rows;\n };\n\n // Diagnostic/e2e hooks that drive the SAME save path the sidebar\n // textarea uses (queueSave \u2192 batched flush \u2192 POST /targets/batch \u2192\n // flushed-callback status re-fetch). The editor UI lives in a closed\n // shadow root \u2014 intentionally unreachable from page JS and from\n // Playwright \u2014 so automated tests exercise the save round-trip\n // through these instead of synthetic textarea events.\n //\n // __lr_editor_edit('auto_xxx', 'nuevo texto') \u2014 queue an edit\n // await __lr_editor_flush() \u2014 force the batch POST now\n (window as unknown as Record<string, unknown>).__lr_editor_edit = (key: string, text: string) => {\n const blocks = this.blockExtractor.extract(document.body, true);\n const block = blocks.find((b) => b.key === key);\n if (!block) throw new Error(`[__lr_editor_edit] no block on page with key ${key}`);\n this.unsavedKeys.add(key);\n this.api.queueSave({\n key,\n locale: this.locale,\n namespace: this.namespace,\n text,\n styles: block.unit.styles,\n });\n };\n (window as unknown as Record<string, unknown>).__lr_editor_flush = () => this.api.flush();\n }\n\n shadowRoot.appendChild(this.el);\n }\n\n open(resolved: ResolvedKey): void {\n this.current = resolved;\n this.el.classList.add('open');\n this.renderContent(resolved);\n }\n\n close(): void {\n this.current = null;\n this.el.classList.remove('open');\n }\n\n isOpen(): boolean {\n return this.el.classList.contains('open');\n }\n\n updateLocale(locale: string): void {\n this.locale = locale;\n // Clear status on locale change \u2014 will be re-fetched\n this.statusMap.clear();\n this.unsavedKeys.clear();\n }\n\n setProgressCallback(cb: (translated: number, total: number) => void): void {\n this.onProgressUpdate = cb;\n }\n\n /**\n * Hydrate the status map with server data for all page blocks.\n * Called once on editor init.\n */\n async hydrateStatus(): Promise<void> {\n const blocks = this.blockExtractor.extract(document.body, true);\n const keys = blocks.map((b) => b.key);\n if (keys.length === 0) return;\n\n const statuses = await this.api.fetchStatus(keys, this.locale, this.namespace);\n for (const [key, status] of Object.entries(statuses)) {\n this.statusMap.set(key, status);\n }\n\n // Apply draft translations to page blocks that the server didn't render\n // (server only ships is_ready_for_delivery; the editor shows drafts too).\n //\n // Blocks without a server translation are the NORMAL state of any page\n // mid-translation (theme chrome, nav, fresh content) \u2014 not warning-\n // worthy by themselves. But this is also the one spot where a real\n // JS\u2194server key-parity bug would surface, and for untranslated\n // STAMPED blocks we can check it definitively: the element's current\n // markup IS the source (nothing was replaced), so recomputing the\n // auto_ key client-side must reproduce the server's stamp exactly.\n // A mismatch means the two sides hash the same source differently \u2014\n // entity decoding, marker syntax, trim rules \u2014 and every lookup for\n // that block will silently miss. Shout about those; only debug-log\n // the plain untranslated count.\n const divergent: Array<{ stamped: string; recomputed: string; text: string }> = [];\n let untranslatedCount = 0;\n for (const block of blocks) {\n const status = this.statusMap.get(block.key);\n if (status && status.text && !status.isReady) {\n applyLivePreview(block.element, status.text, block.unit.styles, block.key, this.locale, this.namespace, block.unit.text, status.rowState);\n } else if (!status || !status.text) {\n untranslatedCount++;\n const stamp = block.element.getAttribute('data-lr-block');\n if (stamp && stamp.startsWith('auto_')) {\n const recomputed = generateAutoKey(block.unit.text);\n if (recomputed !== stamp) {\n divergent.push({ stamped: stamp, recomputed, text: block.unit.text.slice(0, 80) });\n }\n }\n }\n }\n if (divergent.length > 0) {\n console.warn(\n `[LionRapid Editor] ${divergent.length} block(s) where the client-side auto_ key does NOT ` +\n 'match the server\\'s data-lr-block stamp \u2014 JS and server key generation are diverging ' +\n 'for these inputs. Sample (first 5):',\n divergent.slice(0, 5),\n );\n } else if (untranslatedCount > 0) {\n console.debug(\n `[LionRapid Editor] ${untranslatedCount} block(s) untranslated (no server text yet) \u2014 normal for in-progress pages.`,\n );\n }\n\n this.emitProgress();\n }\n\n getTranslatedCount(): number {\n let count = 0;\n for (const status of this.statusMap.values()) {\n if (status && status.rowState !== 'empty') count++;\n }\n return count;\n }\n\n getTotalCount(): number {\n return this.blockExtractor.extract(document.body, true).length;\n }\n\n private emitProgress(): void {\n if (this.onProgressUpdate) {\n this.onProgressUpdate(this.getTranslatedCount(), this.getTotalCount());\n }\n }\n\n destroy(): void {\n this.el.remove();\n }\n\n private async renderContent(resolved: ResolvedKey): Promise<void> {\n const { key, unit, element } = resolved;\n this.bodyEl.innerHTML = '';\n\n // Smart fetch: if no flush since last full fetch, only fetch the clicked key.\n // Otherwise re-fetch all blocks to pick up server-side changes from our saves.\n const isStale = this.lastFlushTime > this.lastFetchTime || this.lastFetchTime === 0;\n\n if (isStale) {\n const blocks = this.blockExtractor.extract(document.body, true);\n const allKeys = blocks.map((b) => b.key);\n if (!allKeys.includes(key)) allKeys.push(key);\n const freshStatuses = await this.api.fetchStatus(allKeys, this.locale, this.namespace);\n for (const [k, s] of Object.entries(freshStatuses)) {\n this.statusMap.set(k, s);\n }\n this.lastFetchTime = Date.now();\n } else {\n // Fresh \u2014 only fetch the clicked key\n const freshStatus = await this.api.fetchStatus([key], this.locale, this.namespace);\n if (freshStatus[key] !== undefined) {\n this.statusMap.set(key, freshStatus[key]);\n }\n }\n const status = this.statusMap.get(key) ?? null;\n\n // Key field\n this.addField('Key', () => {\n const keyEl = document.createElement('div');\n keyEl.className = 'lr-field-key';\n keyEl.textContent = key;\n return keyEl;\n });\n\n // Original text\n this.addField('Original', () => {\n const origEl = document.createElement('div');\n origEl.className = 'lr-field-original';\n origEl.innerHTML = this.renderTextWithTags(unit.text);\n return origEl;\n });\n\n // If this block is (or contains) a same-origin link \u2014 e.g. a nav menu\n // item \u2014 offer an explicit way to follow it. Clicking page links is\n // intercepted by the inspector (so the drawer can open), so without this\n // the PM has no way to navigate to that page to translate it. Navigation\n // is deliberate (no accidental loss of edits) and the session rides along\n // via the sessionStorage JWT + __lr_editor_active cookie.\n this.renderLinkNav(element);\n\n const isSourceLocale = this.locale === this.defaultLocale;\n const isLocked = isSourceLocale || status?.isLocked || status?.isReady || false;\n // Dashboard parity: rows the caller can't act on render read-only, same as\n // the dashboard row list. Existing row \u2192 server-computed canEdit\n // (can_user_edit_row: stage assignment, postreview, PM override). Missing\n // row \u2192 can_create from targets/status (creation is stage-0 work; the\n // batch save refuses it for e.g. a stage-1 proofreader, so don't offer\n // an edit that can't be saved).\n const cannotEdit = status ? status.canEdit === false : !this.api.canCreateMissing;\n const readOnly = isLocked || cannotEdit;\n\n // Translation textarea\n const textarea = document.createElement('textarea');\n this.addField('Translation', () => {\n textarea.className = 'lr-textarea';\n // Pre-fill: server status > in-DOM translation > empty\n textarea.value = status?.text || resolved.translatedUnit?.text || '';\n textarea.placeholder = unit.text; // Show source as placeholder\n if (readOnly) {\n textarea.readOnly = true;\n textarea.classList.add('lr-textarea-locked');\n textarea.title = isSourceLocale\n ? 'Source language \u2014 switch to a target language to edit'\n : status?.isReady ? 'Approved for delivery \u2014 read only'\n : isLocked ? 'Locked \u2014 read only'\n : status ? 'Assigned to another stage \u2014 read only'\n : 'Not yet translated \u2014 rows are created at the translation stage';\n }\n return textarea;\n });\n\n // Status bar (above preview)\n this.bodyEl.appendChild(this.statusEl);\n\n // Preview\n const previewEl = document.createElement('div');\n this.addField('Preview', () => {\n previewEl.className = 'lr-preview';\n if (textarea.value) {\n previewEl.innerHTML = this.renderPreview(textarea.value, unit.styles);\n }\n return previewEl;\n });\n\n // Workflow actions (Phase 3) \u2014 only on target locales (not source).\n // Visibility per role + state is enforced server-side; we hide buttons\n // the server would refuse so users don't see dead ends.\n if (!isSourceLocale) {\n this.renderWorkflowActions(key, status);\n }\n\n // Live preview on input (only if editable)\n if (!readOnly) {\n textarea.addEventListener('input', () => {\n const text = textarea.value;\n previewEl.innerHTML = this.renderPreview(text, unit.styles);\n if (text) {\n // When the operator types in the textarea the row becomes 'edited'\n // \u2014 pass that so applyLivePreview respects the typed text exactly\n // (no auto-wrap rescue: if they typed plain, they meant plain).\n applyLivePreview(element, text, unit.styles, key, this.locale, this.namespace, unit.text, 'edited');\n }\n this.unsavedKeys.add(key);\n this.api.queueSave({\n key,\n locale: this.locale,\n namespace: this.namespace,\n text,\n styles: unit.styles,\n });\n });\n }\n\n // Page blocks list\n this.renderBlocksList();\n\n // Focus textarea (only if editable)\n if (!readOnly) {\n setTimeout(() => textarea.focus(), 100);\n }\n }\n\n /**\n * Render Confirm/Reject/Mark-Ready/Unmark-Ready buttons under the preview.\n * Visibility:\n * - Confirm: shown when row is not locked and not already ready\n * - Reject: shown when row is not locked and not already ready (server\n * returns 400 if stage_index is already 0)\n * - Unconfirm: shown when isConfirmed\n * - Mark Ready: PM/admin only, hidden when already ready\n * - Unmark Ready: PM/admin only, only when ready\n * After any successful click we re-fetch status for this key so the UI\n * reflects the new state and the unaffected buttons hide/show themselves.\n */\n private renderWorkflowActions(key: string, status: TargetStatus | null | undefined): void {\n const isPM = this.userRole === 'project_manager' || this.userRole === 'admin';\n const isReady = !!status?.isReady;\n const isLocked = !!status?.isLocked;\n const isConfirmed = !!status?.isConfirmed;\n // Server-computed canEdit reflects can_user_edit_row() \u2014 false for\n // rows past the user's stage assignment, postreview rows, etc.\n // Default to true if absent (older backend or unknown \u2014 let the user\n // try; server enforces 403 anyway). Missing rows fall back to the\n // session's create permission (targets/status can_create).\n const canEdit = status ? status.canEdit !== false : this.api.canCreateMissing;\n\n const composite = `${this.locale}:${this.namespace}:${key}`;\n\n const wrap = document.createElement('div');\n wrap.className = 'lr-workflow-actions';\n\n // Workflow context strip \u2014 renders synchronously from cache when present,\n // otherwise inserts a placeholder and back-fills after lazy-fetch resolves.\n // The strip lives at the top of the actions wrap so the user sees 'where\n // am I' before the action buttons / out-of-scope note.\n const stripHost = document.createElement('div');\n stripHost.className = 'lr-wf-strip-host';\n wrap.appendChild(stripHost);\n this.renderWorkflowStrip(stripHost, status);\n\n const addBtn = (\n label: string,\n action: 'confirm' | 'unconfirm' | 'reject' | 'advance' | 'mark-ready' | 'unmark-ready',\n variant: 'primary' | 'secondary' | 'danger' = 'secondary',\n ): void => {\n const btn = document.createElement('button');\n btn.className = `lr-wf-btn lr-wf-${variant}`;\n btn.textContent = label;\n btn.addEventListener('click', async () => {\n btn.disabled = true;\n const result = await this.api.transitionTarget(composite, action);\n btn.disabled = false;\n if (result) {\n // Refresh status for this key so the buttons recompute\n await this.refreshStatusForKey(key);\n }\n });\n wrap.appendChild(btn);\n };\n\n // In-flight MT fill: stage transitions would 423 (TARGET_BUSY) until the\n // pipeline sweep finishes, so swap the buttons for an honest note. Editing\n // itself stays available \u2014 canEdit is the permission truth (the server\n // ignores the transient gate for status) and busy saves queue + retry.\n const fillBusy = this.api.targetBusy;\n if (fillBusy && canEdit && !isLocked && !isReady) {\n const note = document.createElement('span');\n note.className = 'lr-wf-note';\n note.textContent =\n 'Machine translation is filling this page \u2014 edits save automatically; stage actions unlock in a moment.';\n wrap.appendChild(note);\n }\n\n // Translator-side actions: only show when the caller can actually act\n // on the row. Without canEdit gating, a translator sees Confirm on a\n // row they've already pushed past their stage and gets a 403.\n //\n // Two-step flow (Confirm \u2192 Advance) mirrors the dashboard:\n // - Confirm: marks is_confirmed=True at current stage (does NOT move stage)\n // - Advance: bumps stage_index, clears is_confirmed for the next reviewer\n // This keeps Confirm and Advance legible as separate intents on both surfaces.\n if (!fillBusy && canEdit && !isLocked && !isReady) {\n if (!isConfirmed) {\n addBtn('Confirm', 'confirm', 'primary');\n // A stage-0 row has nowhere to be sent back to \u2014 the server refuses\n // the reject (\"already at the first stage\"), so don't offer it.\n // Unknown stageIndex (older backend) stays permissive: server decides.\n if (status?.stageIndex !== 0) {\n addBtn('Reject', 'reject', 'danger');\n }\n } else {\n // Confirmed at current stage \u2014 show Advance (per_row only) and Unconfirm\n // as the next-step options. Hide Advance for per_file workflows (rows\n // advance together as a target from the dashboard) and for rows at/past\n // the workflow's postreview boundary \u2014 the server rejects that advance\n // (\"already past the final stage\"), so don't offer a button that can\n // only 400.\n const isPerFile = this.workflowInfo?.workflowMode === 'per_file';\n const atPostreview = isRowAtPostreview(this.workflowInfo, status?.stageIndex);\n if (!isPerFile && !atPostreview) {\n addBtn('Advance', 'advance', 'primary');\n }\n addBtn('Unconfirm', 'unconfirm');\n if (!isPerFile && atPostreview) {\n const note = document.createElement('span');\n note.className = 'lr-wf-note';\n // A project with no workflow at all (continuous projects may defer\n // choosing one) reads differently from a row parked at final review.\n note.textContent =\n this.workflowInfo && this.workflowInfo.stages.length === 0\n ? 'No translation workflow configured \u2014 set one in project settings to use stages.'\n : 'In final review \u2014 no further stages to advance.';\n wrap.appendChild(note);\n }\n }\n }\n\n // PM-only actions \u2014 PM/admin can always Mark/Unmark, no canEdit gate\n // (PM/admin is_pm_admin override in can_user_edit_row keeps canEdit\n // True for them anyway).\n if (isPM) {\n if (!isReady) {\n addBtn('Mark Ready', 'mark-ready', 'primary');\n } else {\n addBtn('Unmark Ready', 'unmark-ready', 'danger');\n }\n }\n\n // Helpful breadcrumb for translators when they see a row they can't\n // edit \u2014 tells them why no action buttons appeared.\n if (!canEdit && !isPM && !isReady && !isLocked) {\n const note = document.createElement('span');\n note.className = 'lr-wf-note';\n note.textContent =\n typeof status?.stageIndex === 'number' && status.stageIndex > 0\n ? `Row already advanced (stage ${status.stageIndex + 1}) \u2014 out of your scope.`\n : 'You are not assigned to this row at the current stage.';\n wrap.appendChild(note);\n }\n\n if (wrap.children.length > 0) {\n this.bodyEl.appendChild(wrap);\n }\n }\n\n /**\n * Render the workflow context strip ('Workflow: A \u2192 B \u2192 C \u2014 you are at stage X').\n * Highlights the user's POV stage and greys the rest.\n *\n * Highlight rule:\n * - PM/admin: highlight = row's current stage_index (deep-dive view)\n * - Non-PM: highlight = first userAssignedStages entry (fixed POV)\n *\n * Renders immediately from the latest known shape, then revalidates through\n * the API's TTL-bound per-locale cache (cache hits resolve instantly) so a\n * mid-session workflow change or a per-target override is picked up. While\n * the first fetch is in flight, the host stays empty (no flash).\n */\n private renderWorkflowStrip(host: HTMLElement, status: TargetStatus | null | undefined): void {\n if (this.workflowInfo) {\n const node = this.buildWorkflowStripNode(this.workflowInfo, status);\n host.replaceChildren(...(node ? [node] : []));\n }\n void this.api.fetchWorkflowInfo(this.locale).then((info) => {\n // Same object \u21D2 cache hit, nothing changed. null keeps the last known\n // shape rather than blanking the strip on a transient fetch failure.\n if (!info || info === this.workflowInfo) return;\n this.workflowInfo = info;\n // The user may have clicked another row by now. Only render if still\n // on the same key \u2014 re-resolve status from the cache.\n if (!this.current) return;\n const fresh = this.statusMap.get(this.current.key) ?? status ?? null;\n const node = this.buildWorkflowStripNode(info, fresh);\n if (host.isConnected) host.replaceChildren(...(node ? [node] : []));\n });\n }\n\n private buildWorkflowStripNode(\n info: EditorWorkflowInfo,\n status: TargetStatus | null | undefined,\n ): HTMLElement | null {\n if (!info.stages || info.stages.length === 0) return null;\n\n let highlightIndex: number | null = null;\n if (info.isPmAdmin) {\n highlightIndex = typeof status?.stageIndex === 'number' ? status.stageIndex : null;\n } else if (info.userAssignedStages.length > 0) {\n highlightIndex = info.userAssignedStages[0];\n }\n\n const strip = document.createElement('div');\n strip.className = 'lr-wf-strip';\n\n const stagesRow = document.createElement('div');\n stagesRow.className = 'lr-wf-strip-stages';\n info.stages.forEach((stage, i) => {\n const item = document.createElement('span');\n item.className = 'lr-wf-strip-stage';\n if (highlightIndex !== null && stage.index === highlightIndex) {\n item.classList.add('is-active');\n } else {\n item.classList.add('is-muted');\n }\n item.textContent = stage.name;\n stagesRow.appendChild(item);\n if (i < info.stages.length - 1) {\n const sep = document.createElement('span');\n sep.className = 'lr-wf-strip-sep';\n sep.textContent = '\u2192';\n stagesRow.appendChild(sep);\n }\n });\n strip.appendChild(stagesRow);\n\n // Caption: 'You are here \u2014 stage N (Name)' or row position for PM\n const caption = document.createElement('div');\n caption.className = 'lr-wf-strip-caption';\n if (highlightIndex !== null) {\n const stage = info.stages.find((s) => s.index === highlightIndex);\n const name = stage?.name ?? `Stage ${highlightIndex + 1}`;\n const label = info.isPmAdmin ? 'Row currently at' : 'You are here';\n caption.textContent = `${label}: stage ${highlightIndex + 1} of ${info.stages.length} \u2014 ${name}`;\n } else if (!info.isPmAdmin && info.userAssignedStages.length === 0) {\n caption.textContent = 'You have no stage assignment on this project.';\n } else {\n caption.textContent = `${info.stages.length} stages`;\n }\n strip.appendChild(caption);\n\n return strip;\n }\n\n /** Re-fetch status for one key and re-render the current view. */\n private async refreshStatusForKey(key: string): Promise<void> {\n const fresh = await this.api.fetchStatus([key], this.locale, this.namespace);\n for (const [k, s] of Object.entries(fresh)) {\n this.statusMap.set(k, s);\n }\n if (this.current) {\n // Re-render the current key to pick up new status\n this.renderContent(this.current);\n }\n this.emitProgress();\n }\n\n /**\n * Render an \"Open linked page \u2192\" button when the selected block is or\n * contains a same-origin link. Cross-origin links are skipped \u2014 the editor\n * session only applies to this site. No-op when there's no usable link.\n */\n private renderLinkNav(element: Element): void {\n const anchor = (element.matches?.('a[href]')\n ? element\n : element.querySelector?.('a[href]')) as HTMLAnchorElement | null;\n if (!anchor) return;\n\n let dest: URL;\n try {\n dest = new URL(anchor.href, window.location.href);\n } catch {\n return;\n }\n if (dest.origin !== window.location.origin) return;\n // Skip a link that just points at the current page (e.g. \"#\" anchors).\n if (dest.pathname === window.location.pathname && !dest.hash) return;\n\n this.addField('Linked page', () => {\n const btn = document.createElement('button');\n btn.className = 'lr-link-nav';\n btn.textContent = 'Open linked page \u2192';\n btn.title = dest.pathname + dest.search;\n btn.addEventListener('click', () => {\n // Localize so we stay in the operator's current locale (e.g. /es).\n window.location.href = localizeUrl(dest.href, this.defaultLocale);\n });\n return btn;\n });\n }\n\n private addField(label: string, contentFn: () => HTMLElement): void {\n const field = document.createElement('div');\n field.className = 'lr-field';\n const labelEl = document.createElement('span');\n labelEl.className = 'lr-field-label';\n labelEl.textContent = label;\n field.appendChild(labelEl);\n field.appendChild(contentFn());\n this.bodyEl.appendChild(field);\n }\n\n /**\n * Get CSS class for the status dot based on actual row state, with one\n * viewer-relative override: rows that have advanced past the viewer's\n * stage assignment render as 'readonly' (greyed) \u2014 same data for everyone,\n * different signal per viewer. Avoids the new-color-per-stage explosion the\n * user was originally considering; reuses existing semantics.\n */\n private getDotClass(key: string): string {\n if (this.unsavedKeys.has(key)) return 'unsaved';\n const s = this.statusMap.get(key);\n // Missing row: grey it out when this session can't create rows\n // (creation is stage-0 work \u2014 mirrors the batch save's create gate).\n if (!s) return this.api.canCreateMissing ? 'missing' : 'readonly';\n if (s.isLocked) return 'locked';\n // Viewer-relative read-only: row is past my stage and I can't act on it.\n // Sits above ready/confirmed/edited so the dot signals 'hands off' even\n // if the row's underlying state would otherwise be green/orange.\n if (s.canEdit === false && typeof s.stageIndex === 'number' && s.stageIndex > 0) return 'readonly';\n if (s.isReady) return 'ready';\n if (s.isConfirmed) return 'confirmed';\n if (s.rowState === 'edited') return 'edited';\n if (s.rowState === 'machine_translated') return 'mt';\n return 'missing';\n }\n\n private renderTextWithTags(text: string): string {\n // Highlight indexed tags like <1>, </1>. The surrounding prose is\n // page-controlled (any visitor-visible text, e.g. a hostile comment),\n // and this string goes to innerHTML in the editor realm \u2014 which holds\n // the PM's session JWT \u2014 so everything is escaped; only the marker\n // wrapper spans are real markup.\n const tagPattern = /<(\\/?)?(\\d+)(\\/)?>/g;\n let result = '';\n let lastIndex = 0;\n let match;\n while ((match = tagPattern.exec(text)) !== null) {\n result += escapeHtml(text.slice(lastIndex, match.index));\n result += `<span class=\"lr-field-tag\">${escapeHtml(match[0])}</span>`;\n lastIndex = match.index + match[0].length;\n }\n return result + escapeHtml(text.slice(lastIndex));\n }\n\n private renderPreview(text: string, styles: Record<string, string[]>): string {\n if (!text) return '';\n if (Object.keys(styles).length > 0) {\n const unit: ContentUnit = { text, styles };\n // Serializer output is sanitized (core Phase 2).\n return htmlSerializer.serialize(unit);\n }\n // Plain-text branch: textarea content is user/page-controlled prose.\n return escapeHtml(text);\n }\n\n private initResize(handle: HTMLDivElement): void {\n let startX = 0;\n let startWidth = 0;\n\n const onMouseMove = (e: MouseEvent) => {\n const newWidth = startWidth + (startX - e.clientX);\n const clamped = Math.max(280, Math.min(600, newWidth));\n this.el.style.width = `${clamped}px`;\n };\n\n const onMouseUp = () => {\n handle.classList.remove('active');\n document.removeEventListener('mousemove', onMouseMove);\n document.removeEventListener('mouseup', onMouseUp);\n };\n\n handle.addEventListener('mousedown', (e) => {\n e.preventDefault();\n startX = e.clientX;\n startWidth = this.el.offsetWidth;\n handle.classList.add('active');\n document.addEventListener('mousemove', onMouseMove);\n document.addEventListener('mouseup', onMouseUp);\n });\n }\n\n private refreshBlocksList(): void {\n const existing = this.bodyEl.querySelector('.lr-blocks-section');\n if (existing) existing.remove();\n this.renderBlocksList();\n }\n\n private renderBlocksList(): void {\n const section = document.createElement('div');\n section.className = 'lr-blocks-section';\n\n const blocks = this.blockExtractor.extract(document.body, true);\n const translatedCount = this.getTranslatedCount();\n\n const title = document.createElement('div');\n title.className = 'lr-blocks-title';\n title.textContent = `Blocks on this page (${translatedCount}/${blocks.length} translated)`;\n section.appendChild(title);\n\n for (const block of blocks) {\n const item = document.createElement('div');\n item.className = 'lr-block-item';\n\n const isActive = this.current && block.element === this.current.element;\n if (isActive) item.classList.add('active');\n\n // Status dot\n const dot = document.createElement('span');\n dot.className = 'lr-status-dot';\n dot.classList.add(this.getDotClass(block.key));\n item.appendChild(dot);\n\n // Source text\n const textWrap = document.createElement('div');\n textWrap.className = 'lr-block-text';\n const displayText = block.unit.text.replace(/<\\/?[0-9]+\\/?>/g, '');\n const sourceSpan = document.createElement('div');\n sourceSpan.className = 'lr-block-source';\n sourceSpan.textContent = displayText.length > 60 ? displayText.slice(0, 60) + '...' : displayText;\n sourceSpan.title = displayText;\n textWrap.appendChild(sourceSpan);\n\n // Translation preview (if exists)\n const blockStatus = this.statusMap.get(block.key);\n if (blockStatus && blockStatus.text) {\n const previewSpan = document.createElement('div');\n previewSpan.className = 'lr-block-preview';\n const previewText = blockStatus.text.replace(/<\\/?[0-9]+\\/?>/g, '');\n previewSpan.textContent = previewText.length > 50 ? previewText.slice(0, 50) + '...' : previewText;\n textWrap.appendChild(previewSpan);\n }\n\n item.appendChild(textWrap);\n\n item.addEventListener('click', () => {\n block.element.scrollIntoView({ behavior: 'smooth', block: 'center' });\n this.onBlockClick({ key: block.key, unit: block.unit, translatedUnit: null, element: block.element });\n });\n\n section.appendChild(item);\n }\n\n this.bodyEl.appendChild(section);\n }\n}\n", "/**\n * PII Redactor \u2014 fine-grained inline replacement of PII patterns in text.\n *\n * Phase 4 of the snapshot feature. Phase 1 redacts whole text nodes only;\n * this module replaces matches *inline*, preserving surrounding prose:\n * \"Contact us at foo@bar.com for help\" \u2192 \"Contact us at [REDACTED] for help\"\n *\n * Two entry points:\n * - `redactString(text)` \u2014 pure string-in/string-out, used in tests and\n * callable independently if a non-DOM caller ever needs it.\n * - `redactTextNodes(root)` \u2014 walks every text node under `root` and\n * replaces PII inline, skipping subtrees marked with `data-no-redact`,\n * and skipping `<code>` / `<pre>` (legitimate examples).\n *\n * Design notes:\n * - Patterns are tuned to avoid common false positives. SSN requires\n * dashes/spaces (so 9-digit order numbers survive); credit cards must\n * pass a Luhn check (so 16-digit tracking IDs survive); IPs reject any\n * octet > 255 (so version strings like 1.2.3.999 survive).\n * - We bias toward under-redaction in ambiguous cases \u2014 better to leak\n * a tracking number than corrupt a customer's real prose.\n */\n\nconst REDACTED = '[REDACTED]';\n\n// ----- Regex catalog ---------------------------------------------------------\n//\n// Every pattern uses the `g` flag so .replace() can hit multiple matches in\n// one string. Anchors (`\\b` or surrounding-char checks) are tuned per pattern.\n\n// Email: standard local@domain.tld, fairly permissive on the local part.\nconst EMAIL_RE =\n /[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,}/gi;\n\n// US/intl phone: optional `+1 ` prefix, optional parens around area code,\n// 3-3-4 digit groups separated by space/dash/dot. Requires at least one\n// separator so bare 10-digit numbers don't match (those are usually IDs).\nconst PHONE_RE =\n /(?:\\+\\d{1,3}[\\s.-]?)?(?:\\(\\d{3}\\)|\\d{3})[\\s.-]\\d{3}[\\s.-]\\d{4}/g;\n\n// SSN: XXX-XX-XXXX, dashes or spaces (NOT bare 9 digits \u2014 too many false\n// positives on order numbers, IDs, etc.).\nconst SSN_RE = /\\b\\d{3}[\\s-]\\d{2}[\\s-]\\d{4}\\b/g;\n\n// IPv4: four 1-3 digit octets, dot-separated. We post-filter for \"all\n// octets <= 255\" so version strings like 1.2.3.999 don't match.\nconst IPV4_RE = /\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b/g;\n\n// URL: http(s):// or www. then non-whitespace. Greedy through path/query.\nconst URL_RE = /\\b(?:https?:\\/\\/|www\\.)[^\\s<>\"']+/gi;\n\n// Credit card candidate: 13-19 digits in 4-4-4-4 (or contiguous) format,\n// allowing dashes/spaces between groups. Luhn-checked before redacting.\n// Must end on a digit (not a separator) \u2014 prevents greedy matching of\n// trailing whitespace.\nconst CC_CANDIDATE_RE = /\\b\\d(?:[ -]?\\d){12,18}\\b/g;\n\n// ----- Luhn check ------------------------------------------------------------\n\nfunction luhnValid(digits: string): boolean {\n // digits already stripped of separators by caller\n if (digits.length < 13 || digits.length > 19) return false;\n let sum = 0;\n let alt = false;\n for (let i = digits.length - 1; i >= 0; i--) {\n let n = digits.charCodeAt(i) - 48; // '0' = 48\n if (n < 0 || n > 9) return false;\n if (alt) {\n n *= 2;\n if (n > 9) n -= 9;\n }\n sum += n;\n alt = !alt;\n }\n return sum % 10 === 0;\n}\n\n// ----- IPv4 octet bounds check ----------------------------------------------\n\nfunction ipv4Valid(match: string): boolean {\n const parts = match.split('.');\n if (parts.length !== 4) return false;\n for (const p of parts) {\n const n = Number(p);\n if (!Number.isInteger(n) || n < 0 || n > 255) return false;\n }\n return true;\n}\n\n// ----- Public: string redactor ----------------------------------------------\n\n/**\n * Replace every PII match in `text` with `[REDACTED]`. Surrounding prose\n * is preserved.\n *\n * Order matters: URLs first (so `http://...` doesn't get mangled by IPv4\n * matching), then emails, then more specific numeric patterns.\n */\nexport function redactString(text: string): string {\n if (!text) return text;\n let out = text;\n\n out = out.replace(URL_RE, REDACTED);\n out = out.replace(EMAIL_RE, REDACTED);\n out = out.replace(PHONE_RE, REDACTED);\n out = out.replace(SSN_RE, REDACTED);\n out = out.replace(IPV4_RE, (m) => (ipv4Valid(m) ? REDACTED : m));\n out = out.replace(CC_CANDIDATE_RE, (m) => {\n const digits = m.replace(/[\\s-]/g, '');\n return luhnValid(digits) ? REDACTED : m;\n });\n\n return out;\n}\n\n// ----- Public: DOM walker ---------------------------------------------------\n\n/**\n * Tag names whose text we never touch \u2014 code samples, preformatted blocks,\n * and `<style>` content. `<script>` is already removed earlier in the\n * snapshot pipeline, so it's not in this list.\n */\nconst SKIP_TAGS = new Set(['CODE', 'PRE', 'STYLE', 'TEXTAREA']);\n\n/**\n * Walks every text node under `root` and redacts PII inline. Skips:\n * - text nodes inside any ancestor with `[data-no-redact]`\n * - text nodes inside `<code>`, `<pre>`, `<style>`, `<textarea>`\n *\n * Mutates the DOM in place.\n */\nexport function redactTextNodes(root: Element): void {\n const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);\n // Collect first, mutate after \u2014 walker behaviour with mutation is\n // implementation-defined.\n const toUpdate: { node: Text; value: string }[] = [];\n\n let node = walker.nextNode() as Text | null;\n while (node) {\n if (!isInSkippedSubtree(node)) {\n const original = node.nodeValue || '';\n const replaced = redactString(original);\n if (replaced !== original) {\n toUpdate.push({ node, value: replaced });\n }\n }\n node = walker.nextNode() as Text | null;\n }\n\n for (const { node: textNode, value } of toUpdate) {\n textNode.nodeValue = value;\n }\n}\n\nfunction isInSkippedSubtree(node: Node): boolean {\n let cur: Node | null = node.parentNode;\n while (cur && cur.nodeType === Node.ELEMENT_NODE) {\n const el = cur as Element;\n if (SKIP_TAGS.has(el.tagName)) return true;\n if (el.hasAttribute('data-no-redact')) return true;\n cur = el.parentNode;\n }\n return false;\n}\n", "/**\n * Snapshot Capture \u2014 Serializes the live DOM with PII redaction.\n *\n * Used by the visual editor to let an authenticated admin capture an auth page\n * so a translator (without credentials) can later translate it against a\n * frozen copy. The serialized HTML is uploaded to the server and retrieved in\n * viewer mode (?__lr_snapshot={uuid}) where Inspector + Sidebar boot against\n * the snapshot root instead of document.body.\n *\n * Redaction strategy:\n * - Remove: <script>, <noscript>, <iframe>, <embed>, <object>, editor host\n * - Clear all form <input>, <textarea>, <select> values\n * - Replace [data-pii] / [data-sensitive] text with [REDACTED]\n * - Strip all inline event handlers (onclick, onmouseover, etc.)\n * - Inline PII redaction in text nodes (email / phone / URL / SSN /\n * credit card / IPv4) \u2014 see pii-redactor.ts. `[data-no-redact]`\n * subtrees, `<code>`, and `<pre>` are skipped.\n * - Strip CSRF/CSP meta tags and auth tokens from link/script URLs\n */\n\nimport { BlockExtractor } from '../block-extractor.js';\nimport { redactTextNodes } from './pii-redactor.js';\n\nconst REDACTED = '[REDACTED]';\n\nexport interface SnapshotTextEntry {\n /** auto_xxxxxxxxxxxx \u2014 12-char md5 hash, generated BEFORE redaction so it matches the live page */\n key: string;\n /** The source tag name (P, H1, LI, etc.) \u2014 useful for future analytics */\n tagName: string;\n /** Index of this block among all block elements in the snapshot DOM. Used by\n * the viewer to map a clicked element back to its pre-computed key without\n * re-running BlockExtractor on the redacted text. */\n blockIndex: number;\n}\n\nexport interface SnapshotViewport {\n width: number;\n height: number;\n devicePixelRatio: number;\n}\n\nexport interface CapturedSnapshot {\n html: string;\n textEntries: SnapshotTextEntry[];\n pageUrl: string;\n title: string;\n viewport: SnapshotViewport;\n}\n\n/**\n * Capture the current page as a redacted snapshot.\n *\n * Pure function \u2014 does NOT mutate the live DOM. Clones first, then:\n * 1. Extracts block keys BEFORE redaction (so auto_keys match the live page)\n * 2. Redacts the clone (strips PII, scripts, forms, etc.)\n * 3. Stamps each block element with a data-lr-snap-idx attribute so the\n * viewer can map a click back to the pre-computed key\n * 4. Serializes to HTML\n *\n * The textEntries carry pre-computed keys (no source text) and the HTML is\n * safe to store. The viewer uses textEntries to look up the correct key by\n * index when the translator clicks a block.\n */\nexport function captureSnapshot(): CapturedSnapshot {\n // 1. Clone the entire document element.\n const clone = document.documentElement.cloneNode(true) as HTMLElement;\n\n // 2. Extract blocks BEFORE redaction so keys match the live page.\n // The real text is used for md5 hashing, producing the same auto_keys\n // the edge proxy / auto-init generates on the live page.\n const textEntries = extractTextEntries(clone);\n\n // 3. Stamp each extracted block with a data attribute linking it to its\n // entry index. The viewer uses this to resolve clicks to pre-computed\n // keys without re-running BlockExtractor on the redacted text.\n // (extractTextEntries stores the element reference so we can stamp it)\n\n // 4. Redact the clone. Order: remove dangerous elements first so we don't\n // waste time walking their subtrees.\n removeDangerousElements(clone);\n clearFormValues(clone);\n redactSensitiveElements(clone);\n stripInlineEventHandlers(clone);\n redactTextNodePII(clone);\n stripSensitiveMetaAndHeaders(clone);\n\n // 5. Serialize.\n const html = '<!DOCTYPE html>\\n' + clone.outerHTML;\n\n return {\n html,\n textEntries,\n pageUrl: window.location.href,\n title: document.title,\n viewport: {\n width: window.innerWidth,\n height: window.innerHeight,\n devicePixelRatio: window.devicePixelRatio || 1,\n },\n };\n}\n\n/**\n * Remove elements that should never appear in a snapshot:\n * - <script>, <noscript>: could re-execute or leak source\n * - <iframe>, <embed>, <object>: third-party content, likely auth-bound\n * - #__lionrapid-editor: the editor's own shadow host\n * - <link rel=\"prefetch\"> / <link rel=\"preload\"> that may leak auth URLs\n */\nfunction removeDangerousElements(root: HTMLElement): void {\n const selectors = [\n 'script',\n 'noscript',\n 'iframe',\n 'embed',\n 'object',\n '#__lionrapid-editor',\n 'link[rel=\"prefetch\"]',\n 'link[rel=\"preload\"]',\n 'link[rel=\"modulepreload\"]',\n ];\n for (const selector of selectors) {\n const matches = root.querySelectorAll(selector);\n for (let i = 0; i < matches.length; i++) {\n matches[i].remove();\n }\n }\n}\n\n/**\n * Clear all form field values so no user input leaks.\n * This covers <input> (all types), <textarea>, and <select>.\n */\nfunction clearFormValues(root: HTMLElement): void {\n const inputs = root.querySelectorAll('input, textarea, select');\n for (let i = 0; i < inputs.length; i++) {\n const el = inputs[i] as HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;\n\n // Clear live value\n if ('value' in el) el.value = '';\n\n // Clear the serialized attribute \u2014 cloneNode copies the attribute,\n // not the .value property, so we need to touch both.\n if (el.hasAttribute('value')) el.setAttribute('value', '');\n\n // <textarea> content lives in text children, not an attribute\n if (el.tagName === 'TEXTAREA') el.textContent = '';\n\n // Clear option selection on <select>\n if (el.tagName === 'SELECT') {\n const options = el.querySelectorAll('option');\n for (let j = 0; j < options.length; j++) {\n (options[j] as HTMLOptionElement).selected = false;\n options[j].removeAttribute('selected');\n }\n }\n\n // Remove data-* attributes that often hold state\n const attrs = Array.from(el.attributes);\n for (const attr of attrs) {\n if (attr.name.startsWith('data-value') || attr.name === 'data-initial') {\n el.removeAttribute(attr.name);\n }\n }\n }\n}\n\n/**\n * Replace the text content of elements flagged as sensitive with [REDACTED].\n * Preserves the element itself so layout stays intact.\n */\nfunction redactSensitiveElements(root: HTMLElement): void {\n const sensitive = root.querySelectorAll('[data-pii], [data-sensitive]');\n for (let i = 0; i < sensitive.length; i++) {\n const el = sensitive[i] as HTMLElement;\n el.textContent = REDACTED;\n // Strip all attributes except structural ones so classes/ids still position it.\n const attrs = Array.from(el.attributes);\n for (const attr of attrs) {\n if (attr.name !== 'class' && attr.name !== 'id' && attr.name !== 'style') {\n el.removeAttribute(attr.name);\n }\n }\n }\n}\n\n/**\n * Walk every element and strip any attribute that starts with 'on' (onclick,\n * onmouseover, onload, etc.). These could re-execute in the viewer.\n */\nfunction stripInlineEventHandlers(root: HTMLElement): void {\n const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);\n let node = walker.nextNode() as HTMLElement | null;\n while (node) {\n const attrs = Array.from(node.attributes);\n for (const attr of attrs) {\n if (attr.name.toLowerCase().startsWith('on')) {\n node.removeAttribute(attr.name);\n }\n }\n node = walker.nextNode() as HTMLElement | null;\n }\n}\n\n/**\n * Walk text nodes and replace PII matches inline. Phase 4: matches are\n * replaced where they appear (\"Contact foo@bar.com today\" \u2192\n * \"Contact [REDACTED] today\") instead of redacting the whole text node.\n * See pii-redactor.ts for pattern details and the data-no-redact escape\n * hatch.\n */\nfunction redactTextNodePII(root: HTMLElement): void {\n redactTextNodes(root);\n}\n\n/**\n * Strip meta tags and link/script URLs that commonly carry auth material.\n */\nfunction stripSensitiveMetaAndHeaders(root: HTMLElement): void {\n // CSRF / CSP nonces\n const sensitiveMetas = root.querySelectorAll(\n 'meta[name=\"csrf-token\"], meta[name=\"csp-nonce\"], meta[name=\"csrf-param\"], meta[http-equiv=\"set-cookie\"]',\n );\n for (let i = 0; i < sensitiveMetas.length; i++) {\n sensitiveMetas[i].remove();\n }\n\n // Strip token-bearing query params from <link> and <a href> URLs\n const TOKEN_PARAMS = ['token', 'auth', 'access_token', 'session', 'sid', 'key', 'api_key', 'apikey'];\n const urlElements = root.querySelectorAll('link[href], a[href], img[src]');\n for (let i = 0; i < urlElements.length; i++) {\n const el = urlElements[i] as HTMLLinkElement | HTMLAnchorElement | HTMLImageElement;\n const attr = el.tagName === 'IMG' ? 'src' : 'href';\n const raw = el.getAttribute(attr);\n if (!raw) continue;\n try {\n const url = new URL(raw, window.location.href);\n let changed = false;\n for (const param of TOKEN_PARAMS) {\n if (url.searchParams.has(param)) {\n url.searchParams.set(param, REDACTED);\n changed = true;\n }\n }\n if (changed) el.setAttribute(attr, url.toString());\n } catch {\n // Not a parseable URL (mailto:, tel:, javascript:, etc.) \u2014 skip\n }\n }\n}\n\n/**\n * Run BlockExtractor on the clone BEFORE redaction to produce text entries\n * with content-hash keys that match the live page. Each extracted block\n * element is stamped with `data-lr-snap-idx` so the viewer can map a click\n * back to the pre-computed key without re-running BlockExtractor on the\n * (now redacted) text.\n *\n * No source text is included in the returned entries \u2014 only the key, tag\n * name, and block index \u2014 so the entries are safe to store even though\n * extraction happened before redaction.\n */\nfunction extractTextEntries(clone: HTMLElement): SnapshotTextEntry[] {\n const extractor = new BlockExtractor(\n { emails: true, urls: true, numbers: true, phones: true },\n [],\n 2,\n );\n const blocks = extractor.extract(clone);\n\n return blocks.map((block, index) => {\n // Stamp the element so the viewer can resolve clicks by data attribute\n block.element.setAttribute('data-lr-snap-idx', String(index));\n\n return {\n key: block.key,\n tagName: block.element.tagName,\n blockIndex: index,\n };\n });\n}\n", "/**\n * DOM-tree sanitizer \u2014 the live-tree companion to the pure-string\n * `content/sanitizer/HTMLSanitizer`. Used where a whole fragment of\n * untrusted HTML becomes live DOM: today the snapshot viewer, which mounts\n * server-fetched snapshot HTML in the editor realm (PM session JWT).\n *\n * POLICY NOTE \u2014 this deliberately diverges from the inline `SAFE_TAGS`\n * allowlist (see PHASES.md Phase 4): a snapshot is a full captured page, so\n * structural elements (`div`/`p`/`h1`/`nav`/`table`/\u2026) must survive for the\n * page to render at all. What is enforced instead:\n * - executable/embedding elements are REMOVED outright\n * - every `on*` handler attribute (and `srcdoc`) is dropped\n * - URL-bearing attributes must pass `sanitizeUrl`\n * - inline `style` keeps any property (layout fidelity) but value-level\n * dangers are dropped via `sanitizeCssDeclarations`\n *\n * CONTINUOUS DEVELOPMENT: like SAFE_STYLE_PROPS, these sets grow (or\n * tighten) deliberately as real snapshots surface gaps \u2014 extend REMOVE_TAGS\n * rather than trusting a new embedding surface, add hostile cases to\n * sanitize-element.test.ts, and keep the WP/edge ports in sync if they ever\n * grow a snapshot feature.\n */\n\nimport {\n sanitizeUrl,\n sanitizeCssDeclarations,\n} from '../content/sanitizer/HTMLSanitizer.js';\n\n/** Elements that can execute, embed, or retarget \u2014 removed with their subtree. */\nconst REMOVE_TAGS = new Set([\n 'script', 'iframe', 'frame', 'frameset', 'object', 'embed', 'applet',\n 'base', 'meta', 'link', 'template', 'portal',\n]);\n\n/** Attributes whose values navigate or fetch \u2014 must pass sanitizeUrl. */\nconst URL_ATTRS = new Set([\n 'href', 'src', 'action', 'formaction', 'poster', 'data', 'xlink:href',\n]);\n\n/**\n * Sanitize a live element tree in place (root included).\n */\nexport function sanitizeElement(root: Element): void {\n // Snapshot the list first \u2014 removals during iteration are safe this way.\n const all = [root, ...Array.from(root.querySelectorAll('*'))];\n for (const el of all) {\n const tag = el.tagName.toLowerCase();\n if (REMOVE_TAGS.has(tag)) {\n el.remove();\n continue;\n }\n for (const name of el.getAttributeNames()) {\n const lower = name.toLowerCase();\n if (lower.startsWith('on') || lower === 'srcdoc') {\n el.removeAttribute(name);\n continue;\n }\n if (URL_ATTRS.has(lower)) {\n if (sanitizeUrl(el.getAttribute(name) ?? '') === null) {\n el.removeAttribute(name);\n }\n continue;\n }\n if (lower === 'style') {\n const clean = sanitizeCssDeclarations(el.getAttribute(name) ?? '');\n if (clean === null) el.removeAttribute(name);\n else el.setAttribute(name, clean);\n }\n }\n }\n}\n\n/**\n * Best-effort sanitization of a copied `<style>` sheet's text: strips\n * `@import` (arbitrary stylesheet pull-in), neutralizes `url(...)`\n * references that resolve cross-origin (exfil beacons in a tampered\n * snapshot), and defuses legacy `expression(`. Same-origin, relative, and\n * `data:` URLs are kept so the snapshot still renders its images/fonts.\n * The primary defenses are element removal + attribute scrubbing in\n * `sanitizeElement`; this closes the stylesheet side channel.\n */\nexport function sanitizeStyleSheetText(css: string, baseHref: string): string {\n let out = css.replace(/@import[^;]*(;|$)/gi, '');\n out = out.replace(\n /url\\(\\s*(['\"]?)([^'\")]*)\\1\\s*\\)/gi,\n (full, _quote: string, target: string) => {\n try {\n const resolved = new URL(target, baseHref);\n if (resolved.protocol === 'data:') return full;\n if (resolved.origin === new URL(baseHref).origin) return full;\n } catch {\n /* unparseable target \u2192 neutralize */\n }\n return 'url(\"\")';\n },\n );\n return out.replace(/expression\\s*\\(/gi, 'blocked(');\n}\n", "/**\n * Snapshot Viewer \u2014 Renders a saved snapshot in the current page so the\n * Inspector + Sidebar can be pointed at it for translation.\n *\n * When the editor loads with `?__lr_snapshot={uuid}`, the normal page is\n * replaced with the snapshot HTML inside a sandbox wrapper:\n *\n * <div id=\"__lr_snapshot_root\"> \u2190 scoped root passed to Inspector\n * <html>...snapshot...</html>\n * </div>\n *\n * Auto-init retranslation is NOT re-run against the snapshot \u2014 translations\n * saved via the sidebar flow through the normal batch endpoint and will\n * appear on the real auth page on next load.\n */\n\nimport type { EditorAPI } from './editor-api.js';\nimport type { SnapshotTextEntry } from './snapshot-capture.js';\nimport { sanitizeElement, sanitizeStyleSheetText } from '../sanitizer.js';\n\nexport interface SnapshotViewerResult {\n /** Scope root to pass to Inspector */\n root: HTMLElement;\n /** Snapshot metadata for display in the toolbar / sidebar */\n pageUrl: string;\n title: string;\n /** Pre-computed key map: blockIndex \u2192 key. The Inspector uses this to resolve\n * clicks without re-running BlockExtractor on the redacted HTML. */\n keyMap: Map<number, string>;\n}\n\n/**\n * Fetch a snapshot from the server and mount it into the current page.\n * Returns the root element Inspector should scope against.\n *\n * On failure (404, network error, deleted snapshot) returns null and displays\n * a user-visible notice via the caller's notice helper.\n */\nexport async function activateSnapshotViewer(\n snapshotUuid: string,\n api: EditorAPI,\n): Promise<SnapshotViewerResult | null> {\n const snapshot = await api.getSnapshot(snapshotUuid);\n if (!snapshot) return null;\n\n // Replace the entire body with a sandbox wrapper holding the snapshot HTML.\n // We keep document.head alone so the editor's Shadow DOM host and existing\n // stylesheets (for rendering) still work.\n //\n // The snapshot HTML is a full document (starts with <!DOCTYPE>), so we\n // parse it with DOMParser and extract the <body> children. The snapshot's\n // <head> <link> and <style> tags are appended to the real head so the\n // snapshot renders with its original styles.\n const parser = new DOMParser();\n const doc = parser.parseFromString(snapshot.html, 'text/html');\n\n // Snapshot HTML is server-fetched and must be treated as tamperable \u2014\n // it mounts in the editor realm, which holds the PM's session JWT.\n // Strip executable elements / handlers / dangerous URLs before any of it\n // touches the live document (core Phase 4).\n sanitizeElement(doc.body);\n\n // Copy over stylesheets and inline <style> tags from the snapshot head.\n // We prefix them with a data attribute so the cleanup code can remove them\n // if the viewer is ever unmounted (out of scope for Phase 1).\n // Cross-origin <link> stylesheets are dropped (a tampered snapshot could\n // pull attacker CSS); inline <style> text is scrubbed of @import and\n // cross-origin url() beacons.\n const snapshotStyles = doc.head.querySelectorAll('link[rel=\"stylesheet\"], style');\n for (let i = 0; i < snapshotStyles.length; i++) {\n const source = snapshotStyles[i];\n if (source.tagName.toLowerCase() === 'link') {\n const href = source.getAttribute('href') ?? '';\n let sameOrigin = false;\n try {\n sameOrigin =\n new URL(href, window.location.href).origin === window.location.origin;\n } catch {\n /* unparseable href \u2192 drop */\n }\n if (!sameOrigin) continue;\n const clone = source.cloneNode(true) as HTMLElement;\n clone.setAttribute('data-lr-snapshot-style', '');\n document.head.appendChild(clone);\n } else {\n const style = document.createElement('style');\n style.textContent = sanitizeStyleSheetText(\n source.textContent ?? '',\n window.location.href,\n );\n style.setAttribute('data-lr-snapshot-style', '');\n document.head.appendChild(style);\n }\n }\n\n // Copy <title> so the tab title reflects the captured page\n if (snapshot.title) {\n document.title = `[Snapshot] ${snapshot.title}`;\n }\n\n // Clear the current body content (but preserve the editor's shadow host\n // if it's already mounted \u2014 it's attached to #__lionrapid-editor).\n const editorHost = document.getElementById('__lionrapid-editor');\n while (document.body.firstChild) {\n if (document.body.firstChild === editorHost) {\n // Skip past the editor host without removing it\n const next = document.body.firstChild.nextSibling;\n if (!next) break;\n document.body.removeChild(next);\n } else {\n document.body.removeChild(document.body.firstChild);\n }\n }\n\n // Build sandbox wrapper and inject snapshot body\n const root = document.createElement('div');\n root.id = '__lr_snapshot_root';\n root.setAttribute('data-lr-snapshot-uuid', snapshotUuid);\n root.innerHTML = doc.body.innerHTML;\n\n // Insert wrapper before the editor host so the editor stays on top\n if (editorHost && editorHost.parentNode === document.body) {\n document.body.insertBefore(root, editorHost);\n } else {\n document.body.appendChild(root);\n }\n\n // Build key map from textEntries: blockIndex \u2192 auto_key\n const keyMap = new Map<number, string>();\n const entries: SnapshotTextEntry[] = snapshot.textEntries || [];\n for (const entry of entries) {\n keyMap.set(entry.blockIndex, entry.key);\n }\n\n return {\n root,\n pageUrl: snapshot.pageUrl,\n title: snapshot.title,\n keyMap,\n };\n}\n", "/**\n * Visual Translation Editor \u2014 Entry Point\n * Self-executing IIFE bundle loaded by edge proxy or auto-init when ?__lr_editor is detected.\n *\n * Reads script tag attributes, polls for window.LionRapid, creates Shadow DOM,\n * and instantiates Inspector, Sidebar, and Toolbar.\n *\n * Auth flow: grant token \u2192 exchange for scoped session JWT \u2192 use JWT for API calls.\n */\n\nimport { EDITOR_CSS } from './editor-styles.js';\nimport { Inspector } from './inspector.js';\nimport { Toolbar } from './toolbar.js';\nimport { Sidebar } from './sidebar.js';\nimport { EditorAPI } from './editor-api.js';\nimport type { ResolvedKey } from './key-resolver.js';\nimport { captureSnapshot } from './snapshot-capture.js';\nimport { activateSnapshotViewer } from './snapshot-viewer.js';\nimport { BlockExtractor } from '../block-extractor.js';\nimport { localizeUrl } from './locale-url.js';\n\n// Console diagnostic. Attached at module load time so it's available the\n// instant the editor bundle parses \u2014 before any grant exchange, before any\n// async setup. Sidebar overrides this later with a richer version that also\n// reports each block's translation status.\n//\n// Usage in DevTools: __lr_dump_keys()\nif (typeof window !== 'undefined') {\n (window as unknown as Record<string, unknown>).__lr_dump_keys = () => {\n const extractor = new BlockExtractor();\n const blocks = extractor.extract(document.body, true);\n const rows = blocks.map((b, i) => ({\n i,\n key: b.key,\n text: b.unit.text,\n stylesJson: JSON.stringify(b.unit.styles),\n }));\n console.table(rows);\n return rows;\n };\n}\n\n// ============================================\n// Session helpers\n// ============================================\n\nconst SESSION_KEY = '__lr_editor_session';\n\ninterface StoredSession {\n token: string;\n exp: number;\n}\n\n/**\n * Session-scoped cookie the edge proxy reads to decide whether to fetch\n * draft translations (bypassing the delivery gate). Set in lockstep with\n * the sessionStorage JWT so a live editor session always implies preview.\n *\n * Not sensitive on its own \u2014 it carries no auth material, just a signal.\n * The actual auth is the JWT sent via Authorization headers from this JS.\n */\nconst PREVIEW_COOKIE = '__lr_editor_active';\n\nfunction setPreviewCookie(): void {\n // No Max-Age / Expires \u2192 session cookie, clears when the browser/tab\n // closes. Matches the lifetime of sessionStorage so both signals go\n // away together.\n document.cookie = `${PREVIEW_COOKIE}=1; path=/; SameSite=Lax`;\n}\n\nfunction clearPreviewCookie(): void {\n document.cookie = `${PREVIEW_COOKIE}=; path=/; SameSite=Lax; Max-Age=0`;\n}\n\nfunction getStoredSession(): string | null {\n try {\n const raw = sessionStorage.getItem(SESSION_KEY);\n if (!raw) return null;\n const parsed: StoredSession = JSON.parse(raw);\n // Check if expired (with 30s buffer)\n if (parsed.exp * 1000 < Date.now() + 30_000) {\n sessionStorage.removeItem(SESSION_KEY);\n clearPreviewCookie();\n return null;\n }\n // Re-stamp the preview cookie on every load \u2014 covers the case where\n // the user closed and reopened the tab inside the sessionStorage TTL,\n // but the session cookie expired with the previous tab.\n setPreviewCookie();\n return parsed.token;\n } catch {\n return null;\n }\n}\n\nfunction storeSession(token: string): void {\n try {\n // Parse exp from JWT payload (es_<header>.<payload>.<sig>)\n const raw = token.startsWith('es_') ? token.slice(3) : token;\n const parts = raw.split('.');\n if (parts.length !== 3) return;\n const payload = JSON.parse(atob(parts[1]));\n const session: StoredSession = { token, exp: payload.exp || 0 };\n sessionStorage.setItem(SESSION_KEY, JSON.stringify(session));\n setPreviewCookie();\n } catch {\n // Non-critical \u2014 session just won't persist across navigations\n }\n}\n\nfunction clearSession(): void {\n sessionStorage.removeItem(SESSION_KEY);\n clearPreviewCookie();\n}\n\n/**\n * Decode the role claim from a signed editor session JWT.\n * Used for client-side UI gating (show/hide Capture button etc.) \u2014 the\n * server enforces the same check on every protected endpoint.\n */\nfunction extractSessionRole(token: string): string {\n try {\n const raw = token.startsWith('es_') ? token.slice(3) : token;\n const parts = raw.split('.');\n if (parts.length !== 3) return '';\n const payload = JSON.parse(atob(parts[1]));\n return typeof payload.role === 'string' ? payload.role : '';\n } catch {\n return '';\n }\n}\n\n// ============================================\n// User-visible notice (shown when editor can't activate)\n// ============================================\n\nfunction showEditorNotice(message: string): void {\n const el = document.createElement('div');\n el.setAttribute('data-no-translate', '');\n Object.assign(el.style, {\n position: 'fixed', bottom: '24px', right: '24px', zIndex: '2147483647',\n background: '#1a1a2e', color: '#fff', padding: '16px 20px',\n borderRadius: '8px', fontFamily: 'system-ui, sans-serif', fontSize: '14px',\n maxWidth: '360px', boxShadow: '0 4px 20px rgba(0,0,0,0.3)',\n });\n el.textContent = message;\n const close = document.createElement('button');\n Object.assign(close.style, {\n position: 'absolute', top: '4px', right: '8px',\n background: 'none', border: 'none', color: '#888', cursor: 'pointer', fontSize: '16px',\n });\n close.textContent = '\\u00d7';\n close.addEventListener('click', () => el.remove());\n el.appendChild(close);\n document.body.appendChild(el);\n setTimeout(() => el.remove(), 8000);\n}\n\n// ============================================\n// Init\n// ============================================\n\nfunction initEditor(): void {\n // Support two activation modes:\n // 1. Grant mode \u2014 script tag has data-grant (first-page activation)\n // 2. Session mode \u2014 no grant, but a valid session exists in sessionStorage\n // (subsequent page navigation after grant was consumed)\n //\n // The edge proxy injects the editor script on every page when editor.enabled\n // is true. On pages where there's no active session, the editor silently\n // exits \u2014 no FAB, no network calls.\n const scriptEl =\n (document.querySelector('script[data-grant]') as HTMLScriptElement | null) ||\n (document.querySelector('script[data-lr-editor]') as HTMLScriptElement | null);\n\n if (!scriptEl) {\n // No editor script tag at all \u2014 nothing to do\n return;\n }\n\n const grant = scriptEl.dataset.grant || '';\n const hasValidSession = getStoredSession() !== null;\n\n // Session mode requires an existing stored session; grant mode requires a grant\n if (!grant && !hasValidSession) {\n // No grant and no session \u2014 silently exit (normal visitor, not an editor user)\n return;\n }\n\n const locale = scriptEl.dataset.locale || '';\n const apiBase = scriptEl.dataset.api || 'https://api.lionrapid.com';\n const projectId = scriptEl.dataset.project || '';\n\n // Poll for window.LionRapid (auto-init loads first)\n let attempts = 0;\n const maxAttempts = 100; // 10s at 100ms intervals\n\n const poll = setInterval(() => {\n attempts++;\n const lr = window.LionRapid;\n\n // Duck-type: during auto-init's boot window (or under a WP/edge library\n // bundle) window.LionRapid can hold a module-exports namespace, not the\n // instance. Only the instance has getCurrentLocale.\n if (lr && typeof lr.getCurrentLocale === 'function') {\n clearInterval(poll);\n activate(lr, grant, locale, apiBase, projectId);\n } else if (attempts >= maxAttempts) {\n clearInterval(poll);\n console.warn('[LionRapid Editor] Timed out waiting for window.LionRapid');\n }\n }, 100);\n}\n\nasync function activate(\n lr: NonNullable<typeof window.LionRapid>,\n grant: string,\n locale: string,\n apiBase: string,\n projectId: string,\n): Promise<void> {\n const config = window.__lionrapidConfig;\n const languages = config?.languages || [locale];\n const namespace = config?.namespace || 'default';\n const defaultLocale = config?.defaultLocale || 'en';\n const currentLocale = locale || lr.getCurrentLocale();\n const normalizedApiBase = apiBase.replace(/\\/$/, '');\n\n // --- Session acquisition ---\n let sessionToken = getStoredSession();\n\n if (!sessionToken && grant) {\n // Exchange grant for session JWT\n try {\n const res = await fetch(`${normalizedApiBase}/api/editor/exchange`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ grant, domain: window.location.host }),\n });\n if (!res.ok) {\n console.error('[LionRapid Editor] Grant exchange failed:', res.status);\n showEditorNotice('Editor session expired. Please reopen the editor from the dashboard.');\n return;\n }\n const data = await res.json();\n sessionToken = data.session_token;\n if (sessionToken) {\n storeSession(sessionToken);\n }\n } catch (err) {\n console.error('[LionRapid Editor] Grant exchange error:', err);\n showEditorNotice('Could not connect to LionRapid. Please check your connection and try again.');\n return;\n }\n }\n\n if (!sessionToken) {\n console.error('[LionRapid Editor] No valid session. Please reopen the editor from the dashboard.');\n showEditorNotice('Editor session expired. Please reopen the editor from the dashboard.');\n return;\n }\n\n // Decode the session JWT to extract the user's role. The server signs this\n // token and enforces permissions \u2014 the client-side check here is only to\n // avoid showing actions the server would reject (defense in depth). Never\n // trust client-side role checks for security decisions.\n const role = extractSessionRole(sessionToken);\n const canCaptureSnapshots = role === 'project_manager' || role === 'admin';\n\n // Create Shadow DOM host\n const host = document.createElement('div');\n host.id = '__lionrapid-editor';\n host.setAttribute('data-no-translate', '');\n document.body.appendChild(host);\n const shadow = host.attachShadow({ mode: 'closed' });\n\n // Inject styles\n const styleEl = document.createElement('style');\n styleEl.textContent = EDITOR_CSS;\n shadow.appendChild(styleEl);\n\n // Hide the proxy/auto-init language switcher while editor is active\n const switcherEl = document.querySelector('[data-lionrapid-switcher]') as HTMLElement | null;\n if (switcherEl) switcherEl.style.display = 'none';\n\n // Create API client with session token\n const api = new EditorAPI(normalizedApiBase, sessionToken, projectId);\n api.startAutoFlush();\n\n // --- Snapshot viewer mode ---\n // If the URL has ?__lr_snapshot={uuid}, replace the page with the saved\n // snapshot HTML and scope the Inspector to the sandbox wrapper. Capture\n // button is hidden in viewer mode (a translator shouldn't re-capture).\n //\n // Separately, ?__lr_recapture={uuid} runs on the LIVE page (no viewer)\n // but switches the FAB's Capture button into re-capture mode \u2014 PUT to the\n // existing snapshot uuid instead of POST.\n const currentUrl = new URL(window.location.href);\n const snapshotUuid = currentUrl.searchParams.get('__lr_snapshot');\n const recaptureUuid = currentUrl.searchParams.get('__lr_recapture');\n let inspectorRoot: Element | undefined;\n let snapshotKeyMap: Map<number, string> | undefined;\n if (snapshotUuid) {\n const result = await activateSnapshotViewer(snapshotUuid, api);\n if (!result) {\n showEditorNotice('Snapshot not found or expired. Please check the dashboard.');\n return;\n }\n inspectorRoot = result.root;\n snapshotKeyMap = result.keyMap;\n }\n\n // Create sidebar (needs api + locale)\n const handleBlockSelect = (resolved: ResolvedKey) => {\n sidebar.open(resolved);\n inspector.clearHighlight();\n };\n\n const sidebar = new Sidebar(shadow, api, currentLocale, namespace, defaultLocale, handleBlockSelect, role);\n\n // Create inspector (hover/click \u2192 sidebar)\n const inspector = new Inspector(shadow, handleBlockSelect, inspectorRoot, snapshotKeyMap);\n\n // Create toolbar\n const toolbar = new Toolbar(\n shadow,\n languages,\n currentLocale,\n // Language change\n async (newLocale) => {\n // Edge proxy context: navigate to locale URL instead of client-side translation\n const isEdgeProxy = document.querySelector('script[data-mode=\"dynamic-only\"]') !== null;\n if (isEdgeProxy) {\n const url = new URL(window.location.href);\n // Strip existing locale prefix if present\n const stripped = url.pathname.replace(/^\\/[a-z]{2}(\\/|$)/, '/');\n // Build new URL: /{locale}/path (no locale prefix for the default).\n const prefix = newLocale === defaultLocale ? '' : `/${newLocale}`;\n url.pathname = prefix + (stripped === '/' && prefix ? '' : stripped);\n // Don't re-append ?__lr_editor \u2014 by now the session is established and\n // carried by the __lr_editor_active cookie (the server re-injects the\n // editor on every page from it). The grant is single-use and already\n // consumed; appending an empty one only confused the server's editor\n // detection and dropped the FAB after a language switch.\n window.location.href = url.toString();\n return;\n }\n sidebar.updateLocale(newLocale);\n sidebar.close();\n inspector.clearHighlight();\n await lr.changeLanguage(newLocale);\n await sidebar.hydrateStatus();\n },\n // Exit\n async () => {\n await api.stopAndFlush();\n inspector.destroy();\n toolbar.destroy();\n sidebar.destroy();\n host.remove();\n clearSession();\n },\n // Capture Snapshot \u2014 only available in live-page mode (not viewer) AND\n // only for PM/admin roles. Translators and proofreaders cannot capture;\n // the server enforces the same check on POST /api/editor/snapshots.\n //\n // If ?__lr_recapture={uuid} is present, the same button re-captures that\n // existing snapshot (PUT) instead of creating a new one (POST).\n snapshotUuid || !canCaptureSnapshots\n ? null\n : async () => {\n try {\n const snapshot = captureSnapshot();\n if (recaptureUuid) {\n const result = await api.recaptureSnapshot(recaptureUuid, snapshot);\n if (result) {\n showEditorNotice(\n `Re-captured as v${result.version} (${snapshot.textEntries.length} blocks). You can close this tab.`,\n );\n } else {\n showEditorNotice('Re-capture failed. Please try again.');\n }\n } else {\n const result = await api.createSnapshot(snapshot);\n if (result) {\n showEditorNotice(\n `Snapshot captured (${snapshot.textEntries.length} blocks). Open it from the Snapshots tab in the dashboard.`,\n );\n } else {\n showEditorNotice('Snapshot upload failed. Please try again.');\n }\n }\n } catch (err) {\n console.error('[LionRapid Editor] Snapshot capture error:', err);\n showEditorNotice('Snapshot capture failed. See console for details.');\n }\n },\n // When in recapture mode, surface it to the toolbar so the button label\n // can change from \"Capture\" to \"Save re-capture\".\n recaptureUuid ? { mode: 'recapture' } : undefined,\n // Browse-mode toggle. Suspending the inspector lets the PM click the\n // site's own links to reach another page; the session rides along via the\n // sessionStorage JWT + __lr_editor_active cookie, so the editor re-activates\n // on the destination. Disabled in snapshot viewer mode (static sandbox,\n // nothing to navigate to).\n snapshotUuid\n ? null\n : (browsing: boolean) => {\n inspector.setActive(!browsing);\n if (browsing) sidebar.close();\n },\n );\n\n // Wire progress counter to sidebar status\n sidebar.setProgressCallback((translated, total) => {\n toolbar.updateProgress(translated, total);\n });\n\n // Locale-preserving link interceptor. On /es/, internal links like\n // /about/ would naively navigate to the default-language page; this\n // rewrites them to /es/about/ so the operator stays in the locale they\n // were browsing. Runs in BUBBLE phase so the inspector (capture phase)\n // gets first say \u2014 if the inspector preventDefault'd a block click, this\n // never fires. New-tab modifiers and editor-UI clicks are left alone.\n document.addEventListener('click', (e) => {\n if (e.defaultPrevented) return;\n if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;\n if ((e as MouseEvent).button !== 0) return;\n const target = e.target as Element | null;\n if (!target?.closest) return;\n if (target.closest('#__lionrapid-editor')) return;\n const a = target.closest('a[href]') as HTMLAnchorElement | null;\n if (!a) return;\n if (a.target && a.target !== '_self') return;\n if (a.hasAttribute('download')) return;\n const localized = localizeUrl(a.href, defaultLocale);\n if (localized !== a.href) {\n e.preventDefault();\n window.location.href = localized;\n }\n });\n\n // Hydrate status from server and update progress\n sidebar.hydrateStatus().then(() => {\n toolbar.updateProgress(sidebar.getTranslatedCount(), sidebar.getTotalCount());\n });\n\n // Clean editor params from URL\n const url = new URL(window.location.href);\n let dirty = false;\n if (url.searchParams.has('__lr_editor')) {\n url.searchParams.delete('__lr_editor');\n dirty = true;\n }\n if (url.searchParams.has('__lr_snapshot')) {\n url.searchParams.delete('__lr_snapshot');\n dirty = true;\n }\n if (url.searchParams.has('__lr_recapture')) {\n url.searchParams.delete('__lr_recapture');\n dirty = true;\n }\n if (dirty) {\n history.replaceState(null, '', url.toString());\n }\n}\n\n// Self-execute\ninitEditor();\n"],
5
- "mappings": ";;;;;;;AAKO,MAAM,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA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",
3
+ "sources": ["../../src/browser/editor/editor-styles.ts", "../../src/config/api.config.ts", "../../src/content/canonical-tag.ts", "../../src/browser/markup-parser.ts", "../../src/browser/md5.ts", "../../src/browser/editor/key-resolver.ts", "../../src/browser/editor/inspector.ts", "../../src/browser/editor/toolbar.ts", "../../src/content/sanitizer/HTMLSanitizer.ts", "../../src/content/serializer/HTMLSerializer.ts", "../../src/browser/editor/live-preview.ts", "../../src/browser/editor/editor-api.ts", "../../src/browser/exclude-filter.ts", "../../src/browser/block-extractor.ts", "../../src/browser/editor/locale-url.ts", "../../src/browser/editor/sidebar.ts", "../../src/browser/editor/pii-redactor.ts", "../../src/browser/editor/snapshot-capture.ts", "../../src/browser/sanitizer.ts", "../../src/browser/editor/snapshot-viewer.ts", "../../src/browser/editor/editor-init.ts"],
4
+ "sourcesContent": ["/**\n * Editor CSS Styles\n * All styles are injected into Shadow DOM \u2014 no leakage to host page.\n */\n\nexport const EDITOR_CSS = `\n/* Reset inside shadow DOM */\n:host {\n all: initial;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n font-size: 14px;\n color: #1a1a2e;\n line-height: 1.5;\n box-sizing: border-box;\n}\n\n*, *::before, *::after {\n box-sizing: border-box;\n}\n\n/* \u2500\u2500 FAB (replaces fixed toolbar) \u2500\u2500 */\n.lr-fab {\n position: fixed;\n bottom: 20px;\n right: 20px;\n z-index: 2147483647;\n display: flex;\n align-items: center;\n gap: 0;\n user-select: none;\n}\n\n.lr-fab-btn {\n width: 48px;\n height: 48px;\n border-radius: 50%;\n background: #2d7f7f;\n color: #fff;\n border: none;\n font-weight: 700;\n font-size: 16px;\n cursor: grab;\n box-shadow: 0 4px 12px rgba(0,0,0,0.25);\n transition: box-shadow 0.15s;\n flex-shrink: 0;\n z-index: 1;\n}\n\n.lr-fab-btn:hover {\n box-shadow: 0 6px 20px rgba(0,0,0,0.3);\n}\n\n.lr-fab-btn:active {\n cursor: grabbing;\n}\n\n.lr-fab-panel {\n display: none;\n align-items: center;\n gap: 8px;\n background: #1a1a2e;\n color: #fff;\n padding: 8px 12px;\n border-radius: 8px;\n margin-right: 8px; /* gap between panel (left) and the FAB button (right) */\n box-shadow: 0 4px 12px rgba(0,0,0,0.25);\n white-space: nowrap;\n}\n\n.lr-fab.expanded .lr-fab-panel {\n display: flex;\n}\n\n.lr-lang-dropdown {\n position: relative;\n}\n\n.lr-lang-btn {\n background: rgba(255,255,255,0.1);\n color: #fff;\n border: 1px solid rgba(255,255,255,0.2);\n border-radius: 4px;\n padding: 4px 10px;\n font-size: 13px;\n font-weight: 600;\n cursor: pointer;\n outline: none;\n}\n\n.lr-lang-btn:hover {\n background: rgba(255,255,255,0.2);\n}\n\n.lr-lang-menu {\n display: none;\n position: absolute;\n bottom: calc(100% + 6px);\n left: 0;\n background: #1a1a2e;\n border: 1px solid rgba(255,255,255,0.15);\n border-radius: 6px;\n padding: 4px;\n min-width: 60px;\n box-shadow: 0 4px 16px rgba(0,0,0,0.4);\n}\n\n.lr-lang-menu.open {\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.lr-lang-item {\n background: none;\n color: #ccc;\n border: none;\n padding: 6px 10px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n border-radius: 4px;\n text-align: left;\n}\n\n.lr-lang-item:hover {\n background: rgba(255,255,255,0.1);\n color: #fff;\n}\n\n.lr-lang-item.active {\n background: #2d7f7f;\n color: #fff;\n}\n\n.lr-fab-progress {\n font-size: 13px;\n color: rgba(255,255,255,0.7);\n}\n\n.lr-fab-exit {\n background: none;\n border: 1px solid rgba(255,255,255,0.3);\n color: #fff;\n padding: 4px 10px;\n border-radius: 4px;\n cursor: pointer;\n font-size: 13px;\n}\n\n.lr-fab-exit:hover {\n background: rgba(255,255,255,0.1);\n}\n\n.lr-fab-snapshot {\n background: #2d7f7f;\n border: 1px solid #2d7f7f;\n color: #fff;\n padding: 4px 10px;\n border-radius: 4px;\n cursor: pointer;\n font-size: 13px;\n font-weight: 600;\n}\n\n.lr-fab-snapshot:hover {\n background: #3a9999;\n border-color: #3a9999;\n}\n\n.lr-fab-snapshot:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.lr-fab-browse {\n background: none;\n border: 1px solid rgba(255,255,255,0.3);\n color: #fff;\n padding: 4px 10px;\n border-radius: 4px;\n cursor: pointer;\n font-size: 13px;\n}\n\n.lr-fab-browse:hover {\n background: rgba(255,255,255,0.1);\n}\n\n.lr-fab-browse.active {\n background: #f5a623;\n border-color: #f5a623;\n color: #1a1a2e;\n font-weight: 600;\n}\n\n.lr-link-nav {\n background: none;\n border: 1px solid #2d7f7f;\n color: #2d7f7f;\n padding: 6px 12px;\n border-radius: 4px;\n cursor: pointer;\n font-size: 13px;\n font-weight: 600;\n}\n\n.lr-link-nav:hover {\n background: #2d7f7f;\n color: #fff;\n}\n\n/* Workflow action buttons (Phase 3) */\n.lr-workflow-actions {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n margin: 12px 0 4px;\n padding: 8px 0 0;\n border-top: 1px solid #e5e7eb;\n}\n\n.lr-wf-btn {\n font-size: 12px;\n font-weight: 500;\n padding: 5px 10px;\n border-radius: 4px;\n border: 1px solid transparent;\n cursor: pointer;\n transition: background-color 0.1s;\n}\n\n.lr-wf-btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.lr-wf-primary {\n background: #2563eb;\n color: white;\n border-color: #2563eb;\n}\n.lr-wf-primary:hover:not(:disabled) {\n background: #1d4ed8;\n}\n\n.lr-wf-secondary {\n background: white;\n color: #374151;\n border-color: #d1d5db;\n}\n.lr-wf-secondary:hover:not(:disabled) {\n background: #f3f4f6;\n}\n\n.lr-wf-danger {\n background: white;\n color: #b91c1c;\n border-color: #fca5a5;\n}\n.lr-wf-danger:hover:not(:disabled) {\n background: #fef2f2;\n}\n\n.lr-wf-note {\n font-size: 11px;\n color: #6b7280;\n font-style: italic;\n padding: 4px 0;\n}\n\n/* Workflow context strip (per-row 'you are here' indicator at the top of\n the workflow actions block). Width-flexible: stages wrap on narrow sidebars. */\n.lr-wf-strip-host {\n width: 100%;\n}\n.lr-wf-strip {\n display: flex;\n flex-direction: column;\n gap: 4px;\n margin-bottom: 8px;\n padding: 6px 8px;\n background: #f9fafb;\n border: 1px solid #e5e7eb;\n border-radius: 4px;\n}\n.lr-wf-strip-stages {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 4px;\n font-size: 11px;\n font-weight: 500;\n}\n.lr-wf-strip-stage {\n padding: 2px 8px;\n border-radius: 3px;\n white-space: nowrap;\n}\n.lr-wf-strip-stage.is-active {\n background: #2563eb;\n color: white;\n}\n.lr-wf-strip-stage.is-muted {\n background: #e5e7eb;\n color: #9ca3af;\n}\n.lr-wf-strip-sep {\n color: #d1d5db;\n font-size: 12px;\n user-select: none;\n}\n.lr-wf-strip-caption {\n font-size: 11px;\n color: #4b5563;\n font-weight: 500;\n}\n\n/* Snapshot viewer sandbox wrapper */\n#__lr_snapshot_root {\n isolation: isolate;\n position: relative;\n width: 100%;\n min-height: 100vh;\n}\n\n/* \u2500\u2500 Overlay \u2500\u2500 */\n.lr-overlay {\n position: fixed;\n pointer-events: none;\n border: 2px solid #2d7f7f;\n border-radius: 3px;\n background: rgba(45, 127, 127, 0.06);\n transition: all 0.1s ease;\n z-index: 2147483646;\n display: none;\n}\n\n.lr-overlay.active {\n display: block;\n}\n\n/* \u2500\u2500 Sidebar \u2500\u2500 */\n.lr-sidebar {\n position: fixed;\n top: 0;\n right: 0;\n width: 360px;\n min-width: 280px;\n max-width: 600px;\n bottom: 0;\n background: #fff;\n border-left: 1px solid #e0e0e0;\n z-index: 2147483647;\n overflow-y: auto;\n transform: translateX(100%);\n transition: transform 0.2s ease;\n}\n\n.lr-sidebar.open {\n transform: translateX(0);\n}\n\n/* Resize handle on left edge */\n.lr-sidebar-resize {\n position: absolute;\n top: 0;\n left: -4px;\n width: 8px;\n bottom: 0;\n cursor: col-resize;\n z-index: 1;\n}\n\n.lr-sidebar-resize:hover,\n.lr-sidebar-resize.active {\n background: rgba(45, 127, 127, 0.3);\n}\n\n.lr-sidebar-header {\n padding: 16px;\n border-bottom: 1px solid #e0e0e0;\n display: flex;\n align-items: center;\n justify-content: space-between;\n}\n\n.lr-sidebar-title {\n font-weight: 600;\n font-size: 15px;\n}\n\n.lr-sidebar-close {\n background: none;\n border: none;\n font-size: 20px;\n cursor: pointer;\n color: #666;\n padding: 4px 8px;\n line-height: 1;\n}\n\n.lr-sidebar-close:hover {\n color: #1a1a2e;\n}\n\n.lr-sidebar-body {\n padding: 16px;\n}\n\n.lr-field {\n margin-bottom: 16px;\n}\n\n.lr-field-label {\n display: block;\n font-size: 12px;\n font-weight: 600;\n color: #666;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n margin-bottom: 6px;\n}\n\n.lr-field-key {\n font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;\n font-size: 12px;\n color: #2d7f7f;\n background: #f0f7f7;\n padding: 4px 8px;\n border-radius: 4px;\n word-break: break-all;\n}\n\n.lr-field-original {\n font-size: 13px;\n color: #333;\n background: #f8f8f8;\n padding: 10px 12px;\n border-radius: 6px;\n border: 1px solid #eee;\n white-space: pre-wrap;\n word-break: break-word;\n}\n\n.lr-field-tag {\n display: inline;\n background: #d9eded;\n color: #2d7f7f;\n padding: 0 3px;\n border-radius: 2px;\n font-family: monospace;\n font-size: 12px;\n}\n\n.lr-textarea {\n width: 100%;\n min-height: 100px;\n padding: 10px 12px;\n border: 1px solid #ddd;\n border-radius: 6px;\n font-family: inherit;\n font-size: 14px;\n line-height: 1.5;\n resize: vertical;\n outline: none;\n transition: border-color 0.15s;\n}\n\n.lr-textarea:focus {\n border-color: #2d7f7f;\n box-shadow: 0 0 0 3px rgba(45, 127, 127, 0.1);\n}\n\n.lr-preview {\n padding: 10px 12px;\n background: #f8f8f8;\n border: 1px solid #eee;\n border-radius: 6px;\n font-size: 13px;\n min-height: 40px;\n}\n\n.lr-status {\n padding: 10px 16px;\n font-size: 12px;\n color: #666;\n text-align: center;\n border-top: 1px solid #eee;\n background: #fafafa;\n}\n\n.lr-status.saved {\n color: #22c55e;\n}\n\n.lr-status.error {\n color: #ef4444;\n}\n\n.lr-status.unsaved {\n color: #f59e0b;\n}\n\n/* \u2500\u2500 Page blocks list \u2500\u2500 */\n.lr-blocks-section {\n margin-top: 16px;\n padding-top: 16px;\n border-top: 1px solid #eee;\n}\n\n.lr-blocks-title {\n font-size: 12px;\n font-weight: 600;\n color: #666;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n margin-bottom: 8px;\n}\n\n.lr-block-item {\n padding: 8px 10px;\n border: 1px solid #eee;\n border-radius: 4px;\n margin-bottom: 4px;\n cursor: pointer;\n font-size: 13px;\n color: #333;\n overflow: hidden;\n transition: background 0.1s;\n display: flex;\n align-items: flex-start;\n gap: 8px;\n}\n\n.lr-block-item:hover {\n background: #f0f7f7;\n border-color: #2d7f7f;\n}\n\n.lr-block-item.active {\n background: #e5f2f2;\n border-color: #2d7f7f;\n}\n\n/* \u2500\u2500 Status dots \u2500\u2500 */\n.lr-status-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n flex-shrink: 0;\n margin-top: 5px;\n background: #d1d5db;\n}\n\n.lr-status-dot.missing { background: #d1d5db; }\n.lr-status-dot.mt { background: #60a5fa; }\n.lr-status-dot.edited { background: #f97316; }\n.lr-status-dot.confirmed { background: #22c55e; }\n.lr-status-dot.ready { background: #22c55e; }\n.lr-status-dot.locked { background: #a855f7; }\n.lr-status-dot.unsaved { background: #f59e0b; }\n.lr-status-dot.readonly { background: #9ca3af; opacity: 0.6; } /* grey + faded \u2014 past my stage */\n\n/* \u2500\u2500 Locked textarea \u2500\u2500 */\n.lr-textarea-locked {\n background: #f3f4f6;\n color: #6b7280;\n cursor: not-allowed;\n border-color: #e5e7eb;\n}\n\n/* \u2500\u2500 Block text layout \u2500\u2500 */\n.lr-block-text {\n overflow: hidden;\n min-width: 0;\n}\n\n.lr-block-source {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.lr-block-preview {\n font-size: 11px;\n color: #888;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n margin-top: 2px;\n}\n`;\n", "/**\n * Where the SDK talks to when nobody tells it otherwise.\n *\n * WHY THIS IS A CONSTANT AND NOT TWO LITERALS\n *\n * It used to be two literals \u2014 one in browser auto-init, one in the editor\n * bootstrap \u2014 and both said `https://api.lionrapid.com`, a host that has NEVER\n * had a DNS record. Every bare-snippet install therefore resolved to NXDOMAIN,\n * and so did the editor. The published README documented the same dead host as\n * the escape hatch, so a user who set the attribute by hand failed in exactly\n * the same way as one who omitted it: there was no path to a working default.\n *\n * The API is served by `app.lionrapid.com` \u2014 verified, not assumed:\n * `app.lionrapid.com/api/health` answers 401 (auth required, endpoint present)\n * while `api.lionrapid.com` fails to resolve at all.\n *\n * WHY A DEFAULT AT ALL, given the CLI refuses to invent one\n *\n * The CLI's rule is right for the CLI and does not transfer. `requireInteractive`\n * exists because the CLI PERSISTS what it invents into `lionrapid.yml`, which is\n * committed and outlives the moment \u2014 an invented value there is unrecoverable\n * because nobody can later tell it apart from a chosen one. A browser default\n * persists nothing: it lives for one page load, in front of the developer who\n * just pasted the snippet, and is contradicted by the next attribute they add.\n *\n * Requiring the attribute would also not have prevented this bug. The README's\n * escape hatch named the same dead host, so a mandatory `data-base-url` copied\n * from our own docs fails identically. What prevents it is a default that is\n * checked, which is what `api-default.unit.test.ts` now does.\n *\n * MOVING THE API: change this constant, change the `data-base-url` examples in\n * the published READMEs, and the guard test will tell you if you missed one.\n */\nexport const DEFAULT_API_BASE_URL = 'https://app.lionrapid.com';\n", "/**\n * Canonical opening-tag format for ContentUnit `raw:` styles.\n *\n * All four parsers (edge, core HTML walker, core DOM walker, PHP) must\n * produce byte-identical output for the same source element. Rules:\n * - tag name lowercased\n * - attribute names lowercased\n * - attribute values always wrapped in double-quotes\n * - attribute insertion order preserved from source\n * - boolean attrs emitted as `attr=\"\"`\n * - void elements use the same form (no self-closing slash, no close tag emitted)\n *\n * Attribute values are stored byte-for-byte as captured from the source\n * (the source's own entity encoding is preserved \u2014 already valid HTML).\n * The serializer only re-escapes `\"` \u2192 `&quot;` so the chosen double-quote\n * delimiter stays well-formed when the source used single quotes.\n */\n\nexport const VOID_TAGS = new Set([\n 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',\n 'link', 'meta', 'param', 'source', 'track', 'wbr',\n]);\n\nfunction escapeAttrValue(value: string): string {\n return value.replace(/\"/g, '&quot;');\n}\n\n/**\n * Build the canonical opening tag string. Used by parsers to produce\n * the `raw:<...>` style content.\n */\nexport function serializeOpeningTag(\n tagName: string,\n attrs: Record<string, string>,\n): string {\n const tag = tagName.toLowerCase();\n let result = `<${tag}`;\n for (const [name, value] of Object.entries(attrs)) {\n result += ` ${name.toLowerCase()}=\"${escapeAttrValue(value)}\"`;\n }\n result += '>';\n return result;\n}\n\n/**\n * Extract the tag name from a canonical opening tag string.\n * `<span class=\"x\">` \u2192 `span`. Returns lowercase.\n */\nexport function extractTagName(rawOpenTag: string): string {\n const end = rawOpenTag.search(/[\\s>]/);\n return rawOpenTag.slice(1, end === -1 ? rawOpenTag.length : end).toLowerCase();\n}\n\n/**\n * Return the raw opening tag (the portion after `raw:`) from a style array,\n * or null if no `raw:` style is present.\n */\nexport function readRawStyle(styles: string[]): string | null {\n for (const s of styles) {\n if (s.startsWith('raw:')) return s.slice(4);\n }\n return null;\n}\n", "/**\n * DOM-based Markup Parser (raw-only)\n *\n * Converts a block element's childNodes into ContentUnit by walking the\n * DOM. Every inline element becomes an indexed tag whose style is the\n * verbatim opening tag in canonical form (see content/canonical-tag.ts).\n *\n * Mirrors the regex-based parser in `content/parser/MarkupParser.ts` and\n * the edge proxy parser, producing byte-identical text + styles for the\n * same source so translation keys match across boundaries.\n */\n\nimport type { ContentUnit } from '../content/types.js';\nimport { serializeOpeningTag, VOID_TAGS } from '../content/canonical-tag.js';\n\n/**\n * Parse a block element into ContentUnit using DOM walking.\n */\nexport function parseBlockElement(element: Element): ContentUnit {\n const styles: Record<string, string[]> = {};\n let tagIndex = 0;\n\n function walk(node: Node): string {\n if (node.nodeType === Node.TEXT_NODE) {\n return node.nodeValue ?? '';\n }\n\n if (node.nodeType !== Node.ELEMENT_NODE) {\n return '';\n }\n\n const el = node as Element;\n const tag = el.tagName.toLowerCase();\n\n // Collect attributes preserving source insertion order.\n const attrs: Record<string, string> = {};\n for (let i = 0; i < el.attributes.length; i++) {\n const attr = el.attributes[i];\n attrs[attr.name.toLowerCase()] = attr.value;\n }\n\n tagIndex++;\n const idx = tagIndex;\n styles[String(idx)] = [`raw:${serializeOpeningTag(tag, attrs)}`];\n\n if (VOID_TAGS.has(tag)) {\n return `<${idx}/>`;\n }\n\n let inner = '';\n for (let i = 0; i < node.childNodes.length; i++) {\n inner += walk(node.childNodes[i]);\n }\n return `<${idx}>${inner}</${idx}>`;\n }\n\n let text = '';\n for (let i = 0; i < element.childNodes.length; i++) {\n text += walk(element.childNodes[i]);\n }\n\n return { text, styles };\n}\n", "/**\n * MD5 Hash Implementation\n * Minimal implementation to match PHP's md5() function\n * Used for generating auto_* keys that match server-side\n */\n\n/* eslint-disable no-bitwise */\n\nfunction safeAdd(x: number, y: number): number {\n const lsw = (x & 0xffff) + (y & 0xffff);\n const msw = (x >> 16) + (y >> 16) + (lsw >> 16);\n return (msw << 16) | (lsw & 0xffff);\n}\n\nfunction bitRotateLeft(num: number, cnt: number): number {\n return (num << cnt) | (num >>> (32 - cnt));\n}\n\nfunction md5cmn(\n q: number,\n a: number,\n b: number,\n x: number,\n s: number,\n t: number\n): number {\n return safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b);\n}\n\nfunction md5ff(\n a: number,\n b: number,\n c: number,\n d: number,\n x: number,\n s: number,\n t: number\n): number {\n return md5cmn((b & c) | (~b & d), a, b, x, s, t);\n}\n\nfunction md5gg(\n a: number,\n b: number,\n c: number,\n d: number,\n x: number,\n s: number,\n t: number\n): number {\n return md5cmn((b & d) | (c & ~d), a, b, x, s, t);\n}\n\nfunction md5hh(\n a: number,\n b: number,\n c: number,\n d: number,\n x: number,\n s: number,\n t: number\n): number {\n return md5cmn(b ^ c ^ d, a, b, x, s, t);\n}\n\nfunction md5ii(\n a: number,\n b: number,\n c: number,\n d: number,\n x: number,\n s: number,\n t: number\n): number {\n return md5cmn(c ^ (b | ~d), a, b, x, s, t);\n}\n\nfunction binlMD5(x: number[], len: number): number[] {\n x[len >> 5] |= 0x80 << len % 32;\n x[(((len + 64) >>> 9) << 4) + 14] = len;\n\n let a = 1732584193;\n let b = -271733879;\n let c = -1732584194;\n let d = 271733878;\n\n for (let i = 0; i < x.length; i += 16) {\n const olda = a;\n const oldb = b;\n const oldc = c;\n const oldd = d;\n\n a = md5ff(a, b, c, d, x[i], 7, -680876936);\n d = md5ff(d, a, b, c, x[i + 1], 12, -389564586);\n c = md5ff(c, d, a, b, x[i + 2], 17, 606105819);\n b = md5ff(b, c, d, a, x[i + 3], 22, -1044525330);\n a = md5ff(a, b, c, d, x[i + 4], 7, -176418897);\n d = md5ff(d, a, b, c, x[i + 5], 12, 1200080426);\n c = md5ff(c, d, a, b, x[i + 6], 17, -1473231341);\n b = md5ff(b, c, d, a, x[i + 7], 22, -45705983);\n a = md5ff(a, b, c, d, x[i + 8], 7, 1770035416);\n d = md5ff(d, a, b, c, x[i + 9], 12, -1958414417);\n c = md5ff(c, d, a, b, x[i + 10], 17, -42063);\n b = md5ff(b, c, d, a, x[i + 11], 22, -1990404162);\n a = md5ff(a, b, c, d, x[i + 12], 7, 1804603682);\n d = md5ff(d, a, b, c, x[i + 13], 12, -40341101);\n c = md5ff(c, d, a, b, x[i + 14], 17, -1502002290);\n b = md5ff(b, c, d, a, x[i + 15], 22, 1236535329);\n\n a = md5gg(a, b, c, d, x[i + 1], 5, -165796510);\n d = md5gg(d, a, b, c, x[i + 6], 9, -1069501632);\n c = md5gg(c, d, a, b, x[i + 11], 14, 643717713);\n b = md5gg(b, c, d, a, x[i], 20, -373897302);\n a = md5gg(a, b, c, d, x[i + 5], 5, -701558691);\n d = md5gg(d, a, b, c, x[i + 10], 9, 38016083);\n c = md5gg(c, d, a, b, x[i + 15], 14, -660478335);\n b = md5gg(b, c, d, a, x[i + 4], 20, -405537848);\n a = md5gg(a, b, c, d, x[i + 9], 5, 568446438);\n d = md5gg(d, a, b, c, x[i + 14], 9, -1019803690);\n c = md5gg(c, d, a, b, x[i + 3], 14, -187363961);\n b = md5gg(b, c, d, a, x[i + 8], 20, 1163531501);\n a = md5gg(a, b, c, d, x[i + 13], 5, -1444681467);\n d = md5gg(d, a, b, c, x[i + 2], 9, -51403784);\n c = md5gg(c, d, a, b, x[i + 7], 14, 1735328473);\n b = md5gg(b, c, d, a, x[i + 12], 20, -1926607734);\n\n a = md5hh(a, b, c, d, x[i + 5], 4, -378558);\n d = md5hh(d, a, b, c, x[i + 8], 11, -2022574463);\n c = md5hh(c, d, a, b, x[i + 11], 16, 1839030562);\n b = md5hh(b, c, d, a, x[i + 14], 23, -35309556);\n a = md5hh(a, b, c, d, x[i + 1], 4, -1530992060);\n d = md5hh(d, a, b, c, x[i + 4], 11, 1272893353);\n c = md5hh(c, d, a, b, x[i + 7], 16, -155497632);\n b = md5hh(b, c, d, a, x[i + 10], 23, -1094730640);\n a = md5hh(a, b, c, d, x[i + 13], 4, 681279174);\n d = md5hh(d, a, b, c, x[i + 0], 11, -358537222);\n c = md5hh(c, d, a, b, x[i + 3], 16, -722521979);\n b = md5hh(b, c, d, a, x[i + 6], 23, 76029189);\n a = md5hh(a, b, c, d, x[i + 9], 4, -640364487);\n d = md5hh(d, a, b, c, x[i + 12], 11, -421815835);\n c = md5hh(c, d, a, b, x[i + 15], 16, 530742520);\n b = md5hh(b, c, d, a, x[i + 2], 23, -995338651);\n\n a = md5ii(a, b, c, d, x[i], 6, -198630844);\n d = md5ii(d, a, b, c, x[i + 7], 10, 1126891415);\n c = md5ii(c, d, a, b, x[i + 14], 15, -1416354905);\n b = md5ii(b, c, d, a, x[i + 5], 21, -57434055);\n a = md5ii(a, b, c, d, x[i + 12], 6, 1700485571);\n d = md5ii(d, a, b, c, x[i + 3], 10, -1894986606);\n c = md5ii(c, d, a, b, x[i + 10], 15, -1051523);\n b = md5ii(b, c, d, a, x[i + 1], 21, -2054922799);\n a = md5ii(a, b, c, d, x[i + 8], 6, 1873313359);\n d = md5ii(d, a, b, c, x[i + 15], 10, -30611744);\n c = md5ii(c, d, a, b, x[i + 6], 15, -1560198380);\n b = md5ii(b, c, d, a, x[i + 13], 21, 1309151649);\n a = md5ii(a, b, c, d, x[i + 4], 6, -145523070);\n d = md5ii(d, a, b, c, x[i + 11], 10, -1120210379);\n c = md5ii(c, d, a, b, x[i + 2], 15, 718787259);\n b = md5ii(b, c, d, a, x[i + 9], 21, -343485551);\n\n a = safeAdd(a, olda);\n b = safeAdd(b, oldb);\n c = safeAdd(c, oldc);\n d = safeAdd(d, oldd);\n }\n\n return [a, b, c, d];\n}\n\nfunction binl2hex(binarray: number[]): string {\n const hexTab = '0123456789abcdef';\n let str = '';\n for (let i = 0; i < binarray.length * 4; i++) {\n str +=\n hexTab.charAt((binarray[i >> 2] >> ((i % 4) * 8 + 4)) & 0xf) +\n hexTab.charAt((binarray[i >> 2] >> ((i % 4) * 8)) & 0xf);\n }\n return str;\n}\n\n/**\n * Encode a string to its UTF-8 bytes. PHP's md5() operates on bytes, so to\n * match its output we must hash the UTF-8 representation \u2014 not raw UTF-16\n * code units. The previous code did `charCodeAt & 0xff`, which truncated\n * any character above U+00FF to a single low byte: U+2019 (curly `'`) was\n * hashed as 0x19 instead of its real UTF-8 form `E2 80 99`. That silently\n * broke key parity with PHP for every non-ASCII character on the page.\n */\nfunction utf8Bytes(str: string): Uint8Array {\n if (typeof TextEncoder !== 'undefined') {\n return new TextEncoder().encode(str);\n }\n // Fallback (older Node/test envs without TextEncoder).\n const out: number[] = [];\n for (let i = 0; i < str.length; i++) {\n const c = str.charCodeAt(i);\n if (c < 0x80) {\n out.push(c);\n } else if (c < 0x800) {\n out.push(0xc0 | (c >> 6), 0x80 | (c & 0x3f));\n } else if (c >= 0xd800 && c < 0xdc00 && i + 1 < str.length) {\n const c2 = str.charCodeAt(++i);\n const cp = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00);\n out.push(\n 0xf0 | (cp >> 18),\n 0x80 | ((cp >> 12) & 0x3f),\n 0x80 | ((cp >> 6) & 0x3f),\n 0x80 | (cp & 0x3f),\n );\n } else {\n out.push(\n 0xe0 | (c >> 12),\n 0x80 | ((c >> 6) & 0x3f),\n 0x80 | (c & 0x3f),\n );\n }\n }\n return new Uint8Array(out);\n}\n\nfunction bytes2binl(bytes: Uint8Array): number[] {\n const bin: number[] = [];\n for (let i = 0; i < bytes.length; i++) {\n bin[i >> 2] |= bytes[i] << ((i % 4) * 8);\n }\n return bin;\n}\n\n/**\n * Generate MD5 hash of a string.\n * Hashes the UTF-8 bytes of `str` so output matches PHP's `md5()` for any\n * input \u2014 ASCII or not.\n */\nexport function md5(str: string): string {\n const bytes = utf8Bytes(str);\n return binl2hex(binlMD5(bytes2binl(bytes), bytes.length * 8));\n}\n\n/**\n * Generate auto key matching PHP's TranslationStateManager::generateKey()\n * Format: auto_ + first 12 chars of md5 hash\n */\nexport function generateAutoKey(text: string): string {\n const trimmed = text.trim();\n return 'auto_' + md5(trimmed).substring(0, 12);\n}\n", "/**\n * Key Resolver\n * Resolves a clicked element to its translation key and ContentUnit.\n */\n\nimport type { ContentUnit } from '../../content/types.js';\nimport { parseBlockElement } from '../markup-parser.js';\nimport { generateAutoKey } from '../md5.js';\n\nconst BLOCK_SELECTOR =\n 'p,h1,h2,h3,h4,h5,h6,li,td,th,label,button,figcaption,dt,dd,summary,caption';\n\nexport interface ResolvedKey {\n key: string;\n /** Source ContentUnit (original text with indexed tags) */\n unit: ContentUnit;\n /** Current translation as ContentUnit (if element is already translated) */\n translatedUnit: ContentUnit | null;\n element: Element;\n}\n\n/**\n * Resolve an element to its translation key.\n * Priority: data-lr-block attr \u2192 parseBlockElement \u2192 fallback textContent\n */\nexport function resolveKey(target: Element): ResolvedKey | null {\n // Priority 1: Already translated element with data-lr-block\n const blockEl = target.closest('[data-lr-block]');\n if (blockEl) {\n const key = blockEl.getAttribute('data-lr-block')!;\n const original = blockEl.getAttribute('data-lr-original');\n let unit: ContentUnit;\n let translatedUnit: ContentUnit | null = null;\n\n if (original) {\n // Parse original HTML \u2192 source ContentUnit\n const temp = document.createElement(blockEl.tagName);\n temp.innerHTML = original;\n unit = parseBlockElement(temp);\n // Parse current DOM \u2192 translated ContentUnit\n translatedUnit = parseBlockElement(blockEl);\n } else {\n unit = parseBlockElement(blockEl);\n }\n return { key, unit, translatedUnit, element: blockEl };\n }\n\n // Priority 2: Find nearest block element and parse it\n const nearest = target.closest(BLOCK_SELECTOR);\n if (nearest) {\n const unit = parseBlockElement(nearest);\n const key = generateAutoKey(unit.text);\n return { key, unit, translatedUnit: null, element: nearest };\n }\n\n // Priority 3: Fallback to element text\n const text = target.textContent?.trim();\n if (text && text.length >= 2) {\n const key = generateAutoKey(text);\n const unit: ContentUnit = { text, styles: {} };\n return { key, unit, translatedUnit: null, element: target };\n }\n\n return null;\n}\n", "/**\n * Inspector \u2014 Hover/click handling\n * Shows overlay on hover, resolves key on click, opens sidebar.\n */\n\nimport { resolveKey, ResolvedKey } from './key-resolver.js';\nimport { parseBlockElement } from '../markup-parser.js';\n\nconst SKIP_TAGS = new Set(['SCRIPT', 'STYLE', 'CODE', 'PRE', 'TEXTAREA', 'NOSCRIPT', 'SVG']);\n\nconst BLOCK_SELECTOR =\n 'p,h1,h2,h3,h4,h5,h6,li,td,th,label,button,figcaption,dt,dd,summary,caption';\n\nexport type OnSelectCallback = (resolved: ResolvedKey) => void;\n\nexport class Inspector {\n private overlay: HTMLDivElement;\n private currentTarget: Element | null = null;\n private onSelect: OnSelectCallback;\n private rafId: number | null = null;\n private active = true;\n private rootElement: Element;\n /** When set, clicks resolve keys from this map (snapshot viewer mode)\n * instead of re-computing from the (redacted) DOM text. */\n private snapshotKeyMap: Map<number, string> | null;\n\n constructor(\n shadowRoot: ShadowRoot,\n onSelect: OnSelectCallback,\n rootElement?: Element,\n snapshotKeyMap?: Map<number, string>,\n ) {\n this.onSelect = onSelect;\n // Default scope is the live document body; snapshot viewer passes a\n // sandbox wrapper so blocks outside it are ignored.\n this.rootElement = rootElement || document.body;\n this.snapshotKeyMap = snapshotKeyMap || null;\n\n // Create overlay inside shadow DOM\n this.overlay = document.createElement('div');\n this.overlay.className = 'lr-overlay';\n shadowRoot.appendChild(this.overlay);\n\n // Bind handlers\n this.handleMouseOver = this.handleMouseOver.bind(this);\n this.handleClick = this.handleClick.bind(this);\n this.handleScroll = this.handleScroll.bind(this);\n\n // Listen on document (capture phase to get events before page handlers)\n document.addEventListener('mouseover', this.handleMouseOver, true);\n document.addEventListener('click', this.handleClick, true);\n window.addEventListener('scroll', this.handleScroll, true);\n window.addEventListener('resize', this.handleScroll);\n }\n\n destroy(): void {\n this.active = false;\n document.removeEventListener('mouseover', this.handleMouseOver, true);\n document.removeEventListener('click', this.handleClick, true);\n window.removeEventListener('scroll', this.handleScroll, true);\n window.removeEventListener('resize', this.handleScroll);\n if (this.rafId !== null) cancelAnimationFrame(this.rafId);\n this.overlay.remove();\n }\n\n clearHighlight(): void {\n this.currentTarget = null;\n this.overlay.classList.remove('active');\n }\n\n /**\n * Suspend/resume the inspector. When inactive, hover/click handlers\n * early-return so page links navigate natively \u2014 this is what the toolbar's\n * \"Browse\" mode flips so a PM can click through the site's nav to reach\n * another page, instead of every click opening the edit drawer.\n */\n setActive(active: boolean): void {\n this.active = active;\n if (!active) this.clearHighlight();\n }\n\n isActive(): boolean {\n return this.active;\n }\n\n private handleMouseOver(e: Event): void {\n if (!this.active) return;\n const target = e.target as Element;\n if (!target || !target.closest) return;\n\n // Skip editor UI elements (inside shadow DOM won't trigger this, but be safe)\n if (target.closest('#__lionrapid-editor')) return;\n\n // Skip anything outside the scope root (snapshot viewer mode)\n if (!this.rootElement.contains(target)) return;\n\n // Skip non-translatable elements\n if (this.shouldSkip(target)) {\n this.clearHighlight();\n return;\n }\n\n // Find nearest block element\n const block = target.closest(BLOCK_SELECTOR) || target.closest('[data-lr-block]');\n if (!block) {\n this.clearHighlight();\n return;\n }\n\n if (block === this.currentTarget) return;\n\n this.currentTarget = block;\n this.positionOverlay(block);\n }\n\n private handleClick(e: Event): void {\n if (!this.active) return;\n const target = e.target as Element;\n if (!target || !target.closest) return;\n\n // Skip editor UI\n if (target.closest('#__lionrapid-editor')) return;\n\n // Skip anything outside the scope root (snapshot viewer mode)\n if (!this.rootElement.contains(target)) return;\n\n // Find the block to resolve\n const block = target.closest(BLOCK_SELECTOR) || target.closest('[data-lr-block]');\n if (!block) return;\n\n if (this.shouldSkip(block)) return;\n\n e.preventDefault();\n e.stopPropagation();\n\n // Snapshot viewer mode: look up pre-computed key by data-lr-snap-idx\n // instead of re-running resolveKey on the redacted text.\n if (this.snapshotKeyMap) {\n const idxAttr = block.getAttribute('data-lr-snap-idx');\n if (idxAttr !== null) {\n const idx = parseInt(idxAttr, 10);\n const key = this.snapshotKeyMap.get(idx);\n if (key) {\n const unit = parseBlockElement(block);\n this.onSelect({ key, unit, translatedUnit: null, element: block });\n return;\n }\n }\n // Block has no snap-idx \u2014 not a pre-extracted block, skip\n return;\n }\n\n const resolved = resolveKey(block);\n if (resolved) {\n this.onSelect(resolved);\n }\n }\n\n private handleScroll(): void {\n if (!this.currentTarget) return;\n if (this.rafId !== null) cancelAnimationFrame(this.rafId);\n this.rafId = requestAnimationFrame(() => {\n if (this.currentTarget) {\n this.positionOverlay(this.currentTarget);\n }\n });\n }\n\n private positionOverlay(element: Element): void {\n const rect = element.getBoundingClientRect();\n this.overlay.style.top = `${rect.top}px`;\n this.overlay.style.left = `${rect.left}px`;\n this.overlay.style.width = `${rect.width}px`;\n this.overlay.style.height = `${rect.height}px`;\n this.overlay.classList.add('active');\n }\n\n private shouldSkip(element: Element): boolean {\n // Skip SKIP_TAGS\n let current: Element | null = element;\n while (current) {\n if (SKIP_TAGS.has(current.tagName)) return true;\n if (current.hasAttribute('data-no-translate')) return true;\n if (current.getAttribute('translate') === 'no') return true;\n current = current.parentElement;\n }\n return false;\n }\n}\n", "/**\n * Toolbar \u2014 Draggable FAB that expands to show controls\n * Collapsed: small circle with \"LR\" logo\n * Expanded: horizontal bar with language selector, progress, exit\n */\n\nexport type OnLanguageChange = (locale: string) => void;\nexport type OnExit = () => void;\nexport type OnCaptureSnapshot = () => void;\n/** Fired when the user toggles Browse mode. `browsing=true` means the\n * inspector should be suspended so page links navigate natively. */\nexport type OnBrowseToggle = (browsing: boolean) => void;\n\nexport interface ToolbarCaptureOptions {\n /** 'capture' = new snapshot, 'recapture' = overwrite an existing snapshot's version */\n mode?: 'capture' | 'recapture';\n}\n\nexport class Toolbar {\n private el: HTMLDivElement;\n private langBtnEl: HTMLButtonElement;\n private langMenuEl: HTMLDivElement;\n private progressEl: HTMLSpanElement;\n private expanded = false;\n private currentLocale: string;\n private onLanguageChange: OnLanguageChange;\n private onExit: OnExit;\n private onCaptureSnapshot: OnCaptureSnapshot | null;\n private onBrowseToggle: OnBrowseToggle | null;\n private browsing = false;\n private browseBtnEl: HTMLButtonElement | null = null;\n\n // Drag state\n private dragging = false;\n private dragOffsetX = 0;\n private dragOffsetY = 0;\n\n constructor(\n shadowRoot: ShadowRoot,\n languages: string[],\n currentLocale: string,\n onLanguageChange: OnLanguageChange,\n onExit: OnExit,\n onCaptureSnapshot: OnCaptureSnapshot | null = null,\n captureOptions: ToolbarCaptureOptions = {},\n onBrowseToggle: OnBrowseToggle | null = null,\n ) {\n this.onLanguageChange = onLanguageChange;\n this.onExit = onExit;\n this.onCaptureSnapshot = onCaptureSnapshot;\n this.onBrowseToggle = onBrowseToggle;\n this.currentLocale = currentLocale;\n const captureMode = captureOptions.mode ?? 'capture';\n\n this.el = document.createElement('div');\n this.el.className = 'lr-fab';\n\n // FAB button (always visible)\n const fabBtn = document.createElement('button');\n fabBtn.className = 'lr-fab-btn';\n fabBtn.textContent = 'LR';\n fabBtn.addEventListener('click', (e) => {\n // Don't toggle if we just finished dragging\n if (this.dragging) return;\n e.stopPropagation();\n this.toggle();\n });\n // Note: fabBtn is appended to this.el at the END (after panel), so flex\n // lays out panel-then-button \u2014 i.e. the panel expands to the LEFT of the\n // button. The button's default position is near the right edge of the\n // viewport, so leftward expansion keeps the panel on-screen.\n\n // Expanded panel\n const panel = document.createElement('div');\n panel.className = 'lr-fab-panel';\n\n // Custom language dropdown\n const langWrap = document.createElement('div');\n langWrap.className = 'lr-lang-dropdown';\n\n this.langBtnEl = document.createElement('button');\n this.langBtnEl.className = 'lr-lang-btn';\n this.langBtnEl.textContent = currentLocale.toUpperCase();\n this.langBtnEl.addEventListener('click', (e) => {\n e.stopPropagation();\n this.langMenuEl.classList.toggle('open');\n });\n langWrap.appendChild(this.langBtnEl);\n\n this.langMenuEl = document.createElement('div');\n this.langMenuEl.className = 'lr-lang-menu';\n for (const lang of languages) {\n const item = document.createElement('button');\n item.className = 'lr-lang-item';\n if (lang === currentLocale) item.classList.add('active');\n item.textContent = lang.toUpperCase();\n item.addEventListener('click', (e) => {\n e.stopPropagation();\n this.currentLocale = lang;\n this.langBtnEl.textContent = lang.toUpperCase();\n this.langMenuEl.classList.remove('open');\n // Update active state\n this.langMenuEl.querySelectorAll('.lr-lang-item').forEach((el) => el.classList.remove('active'));\n item.classList.add('active');\n this.onLanguageChange(lang);\n });\n this.langMenuEl.appendChild(item);\n }\n langWrap.appendChild(this.langMenuEl);\n panel.appendChild(langWrap);\n\n // Capture Snapshot button (only in capture mode, not viewer mode).\n // Label/title change when recapture mode is active so the PM knows this\n // click will overwrite an existing snapshot's version instead of making\n // a new one.\n if (this.onCaptureSnapshot) {\n const captureBtn = document.createElement('button');\n captureBtn.className = 'lr-fab-snapshot';\n if (captureMode === 'recapture') {\n captureBtn.textContent = 'Save re-capture';\n captureBtn.title = 'Save this page as a new version of the existing snapshot';\n } else {\n captureBtn.textContent = 'Capture';\n captureBtn.title = 'Capture a redacted snapshot of this page for translators';\n }\n captureBtn.addEventListener('click', (e) => {\n e.stopPropagation();\n if (this.onCaptureSnapshot) this.onCaptureSnapshot();\n });\n panel.appendChild(captureBtn);\n }\n\n // Browse-mode toggle \u2014 suspends the inspector so the PM can click the\n // site's own nav links to reach another page without every click opening\n // the edit drawer. Only shown when a handler is wired (live-page editing,\n // not snapshot viewer mode).\n if (this.onBrowseToggle) {\n const browseBtn = document.createElement('button');\n browseBtn.className = 'lr-fab-browse';\n browseBtn.textContent = 'Browse';\n browseBtn.title = 'Pause editing so you can click links and navigate to another page';\n browseBtn.addEventListener('click', (e) => {\n e.stopPropagation();\n this.setBrowsing(!this.browsing);\n });\n this.browseBtnEl = browseBtn;\n panel.appendChild(browseBtn);\n }\n\n // Progress\n this.progressEl = document.createElement('span');\n this.progressEl.className = 'lr-fab-progress';\n panel.appendChild(this.progressEl);\n\n // Exit button\n const exitBtn = document.createElement('button');\n exitBtn.className = 'lr-fab-exit';\n exitBtn.textContent = 'Exit';\n exitBtn.addEventListener('click', () => this.onExit());\n panel.appendChild(exitBtn);\n\n this.el.appendChild(panel);\n this.el.appendChild(fabBtn);\n shadowRoot.appendChild(this.el);\n\n // Dragging\n this.handleMouseDown = this.handleMouseDown.bind(this);\n this.handleMouseMove = this.handleMouseMove.bind(this);\n this.handleMouseUp = this.handleMouseUp.bind(this);\n fabBtn.addEventListener('mousedown', this.handleMouseDown);\n }\n\n toggle(): void {\n this.expanded = !this.expanded;\n this.el.classList.toggle('expanded', this.expanded);\n }\n\n private setBrowsing(browsing: boolean): void {\n this.browsing = browsing;\n if (this.browseBtnEl) {\n this.browseBtnEl.textContent = browsing ? 'Editing' : 'Browse';\n this.browseBtnEl.classList.toggle('active', browsing);\n this.browseBtnEl.title = browsing\n ? 'Resume editing \u2014 clicks open the edit drawer again'\n : 'Pause editing so you can click links and navigate to another page';\n }\n this.el.classList.toggle('lr-fab--browsing', browsing);\n if (this.onBrowseToggle) this.onBrowseToggle(browsing);\n }\n\n updateProgress(translated: number, total: number): void {\n this.progressEl.textContent = `${translated}/${total}`;\n }\n\n getCurrentLocale(): string {\n return this.currentLocale;\n }\n\n destroy(): void {\n document.removeEventListener('mousemove', this.handleMouseMove);\n document.removeEventListener('mouseup', this.handleMouseUp);\n this.el.remove();\n }\n\n private handleMouseDown(e: MouseEvent): void {\n this.dragging = false;\n this.dragOffsetX = e.clientX - this.el.getBoundingClientRect().left;\n this.dragOffsetY = e.clientY - this.el.getBoundingClientRect().top;\n document.addEventListener('mousemove', this.handleMouseMove);\n document.addEventListener('mouseup', this.handleMouseUp);\n }\n\n private handleMouseMove(e: MouseEvent): void {\n this.dragging = true;\n const x = Math.max(0, Math.min(e.clientX - this.dragOffsetX, window.innerWidth - this.el.offsetWidth));\n const y = Math.max(0, Math.min(e.clientY - this.dragOffsetY, window.innerHeight - this.el.offsetHeight));\n\n // Anchor by `right` (not `left`) so the FAB grows the panel LEFTWARD\n // when expanded, keeping the button visually fixed where the user\n // dropped it. With `left`-anchoring the container's left edge stays\n // pinned and the panel pushes the button rightward on expand \u2014 that's\n // the \"FAB jumps off-screen after drag\" bug. Switching to `right`\n // here means the button's right edge stays put across the drag /\n // expand / language-switch cycle.\n const rightOffset = window.innerWidth - x - this.el.offsetWidth;\n this.el.style.right = `${Math.max(0, rightOffset)}px`;\n this.el.style.top = `${y}px`;\n this.el.style.left = 'auto';\n this.el.style.bottom = 'auto';\n }\n\n private handleMouseUp(): void {\n document.removeEventListener('mousemove', this.handleMouseMove);\n document.removeEventListener('mouseup', this.handleMouseUp);\n // Reset dragging flag after a tick so the click handler can check it\n if (this.dragging) {\n setTimeout(() => { this.dragging = false; }, 0);\n }\n }\n}\n", "/**\n * Shared allowlist HTML sanitizer \u2014 pure string functions, Node-safe (no DOM).\n *\n * One implementation for every sink that turns translation data into HTML:\n * core's HTMLSerializer/auto-translator, the editor text sinks, the snapshot\n * viewer, react `<Trans>`, and WordPress TS. The WordPress PHP port must\n * mirror `SAFE_TAGS`/`SAFE_ATTRS` exactly (see improvement-plan \u00A74 item 2).\n *\n * Threat model: translation values (and their `raw:` styles) come from the\n * server/KV and may be attacker-controlled \u2014 they are NOT guaranteed to be in\n * canonical form. Everything here is allowlist-based: unknown tags are\n * rejected, unknown attributes dropped, URL schemes checked after entity\n * decoding.\n *\n * @module content/sanitizer/HTMLSanitizer\n */\n\nimport { extractTagName, readRawStyle, serializeOpeningTag } from '../canonical-tag.js';\nimport type { ContentUnit } from '../types.js';\n\n/**\n * Inline formatting elements allowed inside translated content. Block\n * structure lives outside ContentUnit (block-level pipeline), so anything\n * that isn't inline text formatting is rejected outright.\n */\nexport const SAFE_TAGS = new Set([\n 'a', 'abbr', 'b', 'bdi', 'bdo', 'br', 'cite', 'code', 'del', 'dfn',\n 'em', 'i', 'img', 'ins', 'kbd', 'mark', 'q', 's', 'samp', 'small',\n 'span', 'strong', 'sub', 'sup', 'time', 'u', 'var', 'wbr',\n]);\n\n/**\n * Flat attribute allowlist (plus `data-*`/`aria-*` prefixes, handled in\n * `sanitizeOpeningTag`). Kept flat \u2014 not per-tag \u2014 so the PHP mirror stays\n * trivial; an allowed attribute on the wrong tag is inert.\n */\nexport const SAFE_ATTRS = new Set([\n 'alt', 'cite', 'class', 'datetime', 'dir', 'height', 'href', 'id',\n 'lang', 'loading', 'rel', 'role', 'src', 'target', 'title', 'width',\n]);\n\n/** Attributes whose values are URLs and must pass `sanitizeUrl`. */\nconst URL_ATTRS = new Set(['href', 'src', 'cite']);\n\n/**\n * CSS properties allowed through an inline `style` attribute.\n *\n * RISK NOTE \u2014 why `style` never passes through verbatim: with zero\n * JavaScript, an attacker who controls a style value can render a\n * full-viewport overlay (`position:fixed;inset:0` \u2192 pixel-perfect fake\n * login UI on the customer's real domain), fire tracking/exfiltration\n * beacons via `url(...)`, or hide and deface content. Translation data is\n * attacker-writable in our threat model, so `sanitizeStyle` re-parses the\n * declarations and keeps only the properties below: cosmetic text/font\n * properties whose value grammar cannot fetch a URL and cannot reposition\n * or restack the element (no `position`/`inset`/`display`/`z-index`/\n * `opacity`/`transform`, no `background` shorthand). A smuggled `url(...)`\n * inside an allowed property is doubly inert: the value is rejected here,\n * and these properties ignore URL values anyway.\n *\n * CONTINUOUS DEVELOPMENT: this allowlist is intentionally minimal and is\n * expected to grow deliberately as real sites surface legitimate inline\n * styling the strict set drops (tracked as a WATCH item in\n * IMPLEMENTATION.md). When extending it: (1) never add a property that\n * accepts `url()`/`image()`/`element()` values or that affects layout,\n * positioning, or stacking; (2) add both hostile and benign cases to\n * html-sanitizer.test.ts; (3) mirror the change in the WordPress PHP port.\n */\nexport const SAFE_STYLE_PROPS = new Set([\n 'color', 'background-color', 'font-family', 'font-size', 'font-style',\n 'font-weight', 'letter-spacing', 'line-height', 'text-decoration',\n 'text-decoration-color', 'text-decoration-line', 'text-decoration-style',\n 'text-transform', 'vertical-align', 'white-space', 'word-spacing',\n]);\n\n/**\n * Value-level rejections inside an otherwise-allowed declaration: URL/code\n * carriers, and backslashes because CSS escape sequences (`\\75\\72\\6c`)\n * can spell `url(` past a textual check.\n */\nconst DANGEROUS_CSS_VALUE =\n /url\\s*\\(|expression\\s*\\(|image-set\\s*\\(|element\\s*\\(|paint\\s*\\(|javascript:|\\\\|[<>{}@]/i;\n\nconst SAFE_SCHEMES = new Set(['http', 'https', 'mailto', 'tel']);\n\n/**\n * The named entities that matter for scheme smuggling (`&colon;`, `&Tab;`)\n * plus the basic set. Names are case-sensitive, as in HTML.\n */\nconst NAMED_ENTITIES: Record<string, string> = {\n amp: '&', lt: '<', gt: '>', quot: '\"', apos: \"'\", nbsp: '\u00A0',\n colon: ':', semi: ';', sol: '/', bsol: '\\\\',\n Tab: '\\t', NewLine: '\\n',\n};\n\n/**\n * Decode entities exactly once \u2014 mirroring the single decode a browser does\n * when parsing an attribute value out of HTML.\n */\nfunction decodeEntities(value: string): string {\n return value.replace(\n /&(?:#[xX]([0-9a-fA-F]+);?|#(\\d+);?|([a-zA-Z]+);)/g,\n (full, hex: string, dec: string, name: string) => {\n if (hex) return String.fromCodePoint(parseInt(hex, 16));\n if (dec) return String.fromCodePoint(parseInt(dec, 10));\n return NAMED_ENTITIES[name] ?? full;\n },\n );\n}\n\n/** HTML-escape text for safe use in element content or attribute values. */\nexport function escapeHtml(text: string): string {\n return text\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;');\n}\n\n/**\n * Validate a URL attribute value. Returns the value unchanged when safe,\n * or `null` when it must be dropped.\n *\n * Entity-decodes first, then strips the control/whitespace characters\n * browsers ignore inside a scheme (`jav\\tascript:`), then requires any\n * explicit scheme to be on the allowlist. Scheme-less (relative) URLs pass.\n */\nexport function sanitizeUrl(value: string): string | null {\n const decoded = decodeEntities(value);\n // eslint-disable-next-line no-control-regex -- stripping C0 controls/space/DEL is the point\n const probe = decoded.replace(/[\\u0000-\\u0020\\u007f]/g, '').toLowerCase();\n const scheme = /^([a-z][a-z0-9+.-]*):/.exec(probe);\n if (!scheme) return value;\n return SAFE_SCHEMES.has(scheme[1]) ? value : null;\n}\n\n/**\n * Sanitize an inline `style` attribute value by re-parsing its declarations\n * and keeping only `SAFE_STYLE_PROPS` with clean values (see the risk note\n * on `SAFE_STYLE_PROPS`). Returns the rebuilt declaration list in canonical\n * `prop: value; prop: value` form, or `null` when nothing survives \u2014 the\n * caller then drops the attribute entirely.\n */\nexport function sanitizeStyle(value: string): string | null {\n return filterCssDeclarations(value, true);\n}\n\n/**\n * Relaxed CSS profile for the snapshot viewer: keeps ANY standard property\n * (a snapshot is a full captured page \u2014 `position:fixed` navbars and layout\n * styles are legitimate and must render) but still rejects value-level\n * dangers: `url(...)` beacons, `expression()`, CSS escape smuggling. The\n * residual risk (page-authored overlay styles) is accepted for snapshots:\n * the PM is deliberately viewing arbitrary captured pages, and the editor\n * UI itself lives in a Shadow DOM host stacked above the snapshot root.\n */\nexport function sanitizeCssDeclarations(value: string): string | null {\n return filterCssDeclarations(value, false);\n}\n\nfunction filterCssDeclarations(\n value: string,\n requirePropAllowlist: boolean,\n): string | null {\n const decoded = decodeEntities(value);\n const kept: string[] = [];\n for (const declaration of decoded.split(';')) {\n const colon = declaration.indexOf(':');\n if (colon === -1) continue;\n const prop = declaration.slice(0, colon).trim().toLowerCase();\n const val = declaration.slice(colon + 1).trim();\n if (!/^[a-z-]+$/.test(prop)) continue; // also blocks --custom-prop indirection\n if (requirePropAllowlist && !SAFE_STYLE_PROPS.has(prop)) continue;\n if (val === '' || DANGEROUS_CSS_VALUE.test(val)) continue;\n kept.push(`${prop}: ${val}`);\n }\n return kept.length > 0 ? kept.join('; ') : null;\n}\n\n/**\n * Attribute scanner for a (possibly hostile, non-canonical) opening tag.\n * Handles double-quoted, single-quoted, unquoted, and bare boolean attrs.\n */\nconst ATTR_PATTERN =\n /([^\\s=/>\"']+)(?:\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\s>]*)))?/g;\n\n/**\n * Sanitize a raw opening tag (`raw:` style payload). Returns the canonical\n * rebuilt tag with only allowlisted attributes, or `null` when the element\n * itself is not allowed \u2014 callers must then drop the tag entirely (emit the\n * indexed marker verbatim / skip the element).\n */\nexport function sanitizeOpeningTag(rawOpen: string): string | null {\n if (!rawOpen.startsWith('<')) return null;\n const tagName = extractTagName(rawOpen);\n if (!/^[a-z][a-z0-9-]*$/.test(tagName) || !SAFE_TAGS.has(tagName)) {\n return null;\n }\n\n // The slice between `<tagname` and the closing `>` (or end of string \u2014\n // hostile input may be truncated).\n const end = rawOpen.endsWith('/>')\n ? rawOpen.length - 2\n : rawOpen.endsWith('>')\n ? rawOpen.length - 1\n : rawOpen.length;\n const attrSource = rawOpen.slice(1 + tagName.length, end);\n\n const attrs: Record<string, string> = {};\n for (const match of attrSource.matchAll(ATTR_PATTERN)) {\n const name = match[1].toLowerCase();\n const value = match[2] ?? match[3] ?? match[4] ?? '';\n if (name in attrs) continue; // first occurrence wins, as in HTML\n if (name === 'style') {\n // Never verbatim \u2014 re-parsed against the CSS property allowlist.\n const cleanStyle = sanitizeStyle(value);\n if (cleanStyle !== null) attrs[name] = cleanStyle;\n continue;\n }\n const allowed =\n SAFE_ATTRS.has(name) ||\n /^data-[a-z0-9-]*$/.test(name) ||\n /^aria-[a-z0-9-]*$/.test(name);\n if (!allowed) continue;\n if (URL_ATTRS.has(name)) {\n if (sanitizeUrl(value) === null) continue;\n }\n attrs[name] = value;\n }\n\n return serializeOpeningTag(tagName, attrs);\n}\n\n/**\n * Sanitize a ContentUnit at an ingest boundary: run every `raw:` style\n * through {@link sanitizeOpeningTag} so no dangerous element/attribute/scheme\n * enters the translation store. Render-time sinks (HTMLSerializer etc.)\n * sanitize again \u2014 this is the store-side half, protecting every other\n * consumer of stored units (server, MT/TM, exports, the WP PHP port).\n *\n * `text` is never modified, so `generateAutoKey(unit.text)` is unaffected \u2014\n * key parity across surfaces holds even for hostile input. A rejected\n * element's `raw:` style is dropped entirely; the indexed tag left in the\n * text is emitted by serializers as an inert marker. Returns the SAME object\n * when nothing changed, so benign units round-trip identically.\n */\nexport function sanitizeContentUnit(unit: ContentUnit): ContentUnit {\n let changed = false;\n const styles: Record<string, string[]> = {};\n\n for (const [index, styleList] of Object.entries(unit.styles)) {\n const raw = readRawStyle(styleList);\n if (raw === null) {\n styles[index] = styleList; // no raw tag \u2192 nothing to sanitize\n continue;\n }\n const safe = sanitizeOpeningTag(raw);\n if (safe === null) {\n // Element rejected \u2014 drop the raw style (keep any non-raw entries).\n changed = true;\n const rest = styleList.filter((s) => !s.startsWith('raw:'));\n if (rest.length > 0) styles[index] = rest;\n } else if (safe === raw) {\n styles[index] = styleList;\n } else {\n changed = true;\n styles[index] = styleList.map((s) => (s.startsWith('raw:') ? `raw:${safe}` : s));\n }\n }\n\n return changed ? { text: unit.text, styles } : unit;\n}\n", "/**\n * HTML serializer \u2014 converts ContentUnit back to HTML strings (raw-only).\n *\n * Every indexed tag carries a `raw:<opening tag>` style. The serializer\n * routes the stored opening tag through the shared allowlist sanitizer and\n * derives the close tag from the embedded tag name. Void elements get no\n * close tag. Text segments are HTML-escaped (ContentUnit.text holds decoded\n * Unicode \u2014 see MarkupParser.decodeEntities), so serializer output is inert\n * even when the translation value is attacker-controlled. `ContentUnit.text`\n * itself is never modified \u2014 auto_key hashing is unaffected.\n *\n * @module content/serializer/HTMLSerializer\n */\n\nimport type { ISerializer } from './ISerializer.js';\nimport type { ContentUnit } from '../types.js';\nimport { extractTagName, readRawStyle, VOID_TAGS } from '../canonical-tag.js';\nimport { escapeHtml, sanitizeOpeningTag } from '../sanitizer/HTMLSanitizer.js';\n\n/**\n * Serializer that converts ContentUnit format back to HTML strings.\n *\n * @example\n * ```typescript\n * const serializer = new HTMLSerializer();\n * serializer.serialize({\n * text: 'Read our <1>Terms</1>.',\n * styles: { '1': ['raw:<b>'] }\n * });\n * // \u2192 'Read our <b>Terms</b>.'\n * ```\n */\nexport class HTMLSerializer implements ISerializer<string> {\n serialize(unit: ContentUnit): string {\n const { text, styles } = unit;\n const tagPattern = /<(\\/?)?(\\d+)(\\/)?>/g;\n\n let result = '';\n let lastIndex = 0;\n let match;\n\n while ((match = tagPattern.exec(text)) !== null) {\n result += escapeHtml(text.slice(lastIndex, match.index));\n\n const [fullMatch, closingSlash, indexStr] = match;\n const styleList = styles[indexStr] ?? [];\n const rawOpen = readRawStyle(styleList);\n const safeOpen = rawOpen === null ? null : sanitizeOpeningTag(rawOpen);\n\n if (rawOpen && safeOpen !== null) {\n const tagName = extractTagName(rawOpen);\n const isVoid = VOID_TAGS.has(tagName);\n\n if (closingSlash) {\n if (!isVoid) result += `</${tagName}>`;\n } else {\n // Both <N> and <N/> emit the opening tag. For void tags that's the\n // complete element.\n result += safeOpen;\n }\n } else {\n // No raw style, or the sanitizer rejected the element \u2014 preserve the\n // indexed marker (parses as inert text) so the problem is visible\n // rather than silently dropping content, and never emit a close tag\n // for a rejected element.\n result += fullMatch;\n }\n\n lastIndex = match.index + fullMatch.length;\n }\n\n result += escapeHtml(text.slice(lastIndex));\n return result;\n }\n}\n", "/**\n * Live Preview\n * Optimistically updates DOM elements as the user types translations.\n */\n\nimport type { ContentUnit } from '../../content/types.js';\nimport { HTMLSerializer } from '../../content/serializer/HTMLSerializer.js';\n\nconst htmlSerializer = new HTMLSerializer();\n\n/**\n * Update a single element's content with a new translation.\n * Writes to core cache and updates the DOM without full page retranslate.\n */\nexport function applyLivePreview(\n element: Element,\n text: string,\n styles: Record<string, string[]>,\n key: string,\n locale: string,\n namespace: string,\n sourceText?: string,\n rowState?: string,\n): void {\n // Save original HTML before first edit\n if (!element.hasAttribute('data-lr-original')) {\n element.setAttribute('data-lr-original', element.innerHTML);\n }\n\n // Update core cache if available\n const lr = (window as { LionRapid?: { autoTranslator?: { core?: { cache?: { set?: (k: string, v: string, s?: Record<string, string[]>) => void } } } } }).LionRapid;\n const cache = (lr as Record<string, unknown>)?.autoTranslator\n ? getCache()\n : null;\n if (cache) {\n const fullKey = `${locale}:${namespace}:${key}`;\n cache.set(fullKey, text, styles);\n }\n\n // Update DOM. Three branches:\n //\n // 1. Translation has its own indexed markers (`<1>...</1>`) \u2192 serialize\n // normally; the serializer re-wraps each marker with its raw style.\n // Translators position markers freely (Finnish \"Valitse\" wrapping the\n // whole word; Spanish \"<1>aqu\u00ED</1>\" wrapping just one word) and this\n // branch renders whatever they chose.\n //\n // 2. Translation is plain AND the row is still machine-translated AND the\n // source was entirely wrapped in one indexed tag \u2192 MT silently dropped\n // the marker that was in the source. Auto-wrap to rescue the wrapper\n // so links survive the MT step. Gated on `machine_translated` so that\n // a human who deliberately typed plain text (rowState='edited') has\n // their choice respected \u2014 we don't put a link back they removed.\n //\n // 3. Anything else \u2192 write plain textContent. The wrapper, if any, is\n // gone \u2014 that's either by design (human chose plain) or unavoidable\n // (mixed inline content the regex above can't reconstruct).\n const hasIndexedMarkers = /<\\/?[0-9]+\\/?>/.test(text);\n const isMachineTranslated = rowState === 'machine_translated';\n const fullyWrapped = sourceText\n ? sourceText.match(/^<(\\d+)>([\\s\\S]*)<\\/\\1>$/)\n : null;\n\n if (hasIndexedMarkers) {\n const unit: ContentUnit = { text, styles };\n element.innerHTML = htmlSerializer.serialize(unit);\n } else if (isMachineTranslated && fullyWrapped && styles && Object.keys(styles).length > 0) {\n const idx = fullyWrapped[1];\n const wrappedText = `<${idx}>${text}</${idx}>`;\n const unit: ContentUnit = { text: wrappedText, styles };\n element.innerHTML = htmlSerializer.serialize(unit);\n } else {\n element.textContent = text;\n }\n\n // Mark as translated\n element.setAttribute('data-lr-block', key);\n element.setAttribute('data-lionrapid-client', '1');\n}\n\nfunction getCache(): { set: (key: string, value: string, styles?: Record<string, string[]>) => void } | null {\n try {\n const lr = (window as unknown as { LionRapid?: Record<string, unknown> }).LionRapid;\n if (!lr) return null;\n const at = lr.autoTranslator as Record<string, unknown> | undefined;\n if (!at) return null;\n const core = at.core as Record<string, unknown> | undefined;\n if (!core) return null;\n const cache = core.cache as { set?: (key: string, value: string, styles?: Record<string, string[]>) => void } | undefined;\n if (!cache || typeof cache.set !== 'function') return null;\n return cache as { set: (key: string, value: string, styles?: Record<string, string[]>) => void };\n } catch {\n return null;\n }\n}\n", "/**\n * Editor API \u2014 Batch save queue + session auth\n * Queues translation edits and flushes them in a single batch API call.\n * Uses editor session JWTs (es_ prefixed) for authentication with auto-renewal.\n */\n\nimport type { CapturedSnapshot, SnapshotTextEntry } from './snapshot-capture.js';\n\nexport interface QueuedTranslation {\n key: string;\n locale: string;\n namespace: string;\n text: string;\n styles: Record<string, string[]>;\n}\n\n// ============================================\n// Snapshot API types\n// ============================================\n\nexport interface SnapshotListItem {\n snapshotUuid: string;\n pageUrl: string;\n title: string;\n capturedBy: string;\n createdAt: string;\n expiresAt: string;\n textEntryCount: number;\n status: string;\n}\n\nexport interface SnapshotDetail {\n snapshotUuid: string;\n pageUrl: string;\n title: string;\n html: string;\n textEntries: SnapshotTextEntry[];\n viewport: { width: number; height: number; devicePixelRatio: number };\n createdAt: string;\n expiresAt: string;\n}\n\nexport interface TargetStatus {\n text: string;\n rowState: string; // \"empty\" | \"machine_translated\" | \"edited\"\n isConfirmed: boolean;\n /** APPROVED for delivery \u2014 NOT the same as actually served. This is the label\n * AND the action of the PM's Mark/Unmark toggle, so it must keep meaning\n * approval. For \"would GET /translations serve this?\", read willDeliver. */\n isReady: boolean;\n /** Whether GET /translations would ACTUALLY serve this key right now.\n * Server-derived from the delivery axes (approved-or-locked, non-empty\n * target, translatable source, live target) \u2014 a sibling fact to isReady,\n * not a restatement: an APPROVED row with an EMPTY target is approved and\n * still not delivered. Optional for backward compatibility \u2014 a backend\n * predating the field omits it, leaving this undefined (fetchStatus returns\n * the parsed body unmodified, so absence survives as undefined rather than\n * false). Callers must fall back to isReady, not to false. */\n willDeliver?: boolean;\n isLocked: boolean; // workflow locked OR delivery locked\n /** Whether the calling editor session can confirm/reject this row.\n * Server-computed via can_user_edit_row() \u2014 reflects PM/admin override,\n * postreview, and per-stage assignment. False for rows past the user's\n * stage (e.g. translator looking at a row they've already pushed forward).\n * Optional for backward compatibility with older backend versions. */\n canEdit?: boolean;\n /** Current workflow stage index. Optional for backward compatibility. */\n stageIndex?: number;\n}\n\n/** One stage in the project's workflow. */\nexport interface WorkflowStageInfo {\n index: number;\n name: string;\n role: string | null;\n /** True for the Postreview stage \u2014 rows at/past it cannot Advance. */\n isPostreview: boolean;\n}\n\n/** Workflow context for the calling editor session. Cached per locale with a\n * TTL (see fetchWorkflowInfo) \u2014 the workflow can change mid-session and can\n * differ per locale via a per-target override. */\nexport interface EditorWorkflowInfo {\n stages: WorkflowStageInfo[];\n /** Stage indices the user is assigned to via UserTargetLink. Empty for PM/admin. */\n userAssignedStages: number[];\n isPmAdmin: boolean;\n workflowMode: 'per_row' | 'per_file';\n /** Number of non-Postreview (work) stages \u2014 the server's advance boundary:\n * a row with stageIndex >= this is in postreview and cannot Advance.\n * 0 also covers a project with no workflow configured at all. */\n workStageCount: number;\n}\n\n/** Whether a row sits at/past the workflow's postreview boundary \u2014 the same\n * check the server's advance endpoint enforces (\"Row is already past the\n * final stage\"). False when workflow info or the row's stage is unknown:\n * the sidebar then keeps its old behavior and lets the server decide. */\nexport function isRowAtPostreview(\n info: EditorWorkflowInfo | null,\n stageIndex: number | undefined,\n): boolean {\n return info !== null && typeof stageIndex === 'number' && stageIndex >= info.workStageCount;\n}\n\n/** Per-key outcome for a batch item the server refused to write (per-row\n * authorization). The batch analogue of the dashboard save's 423 \"locked\" /\n * 403 \"forbidden\" rejection with a row snapshot. */\nexport interface DeniedRow {\n key: string;\n /** \"locked\" (row locked, PM-only), \"forbidden\" (not assigned to this row/stage),\n * or \"busy\" (target's MT fill in flight \u2014 transient, the edit is retried). */\n reason: 'locked' | 'forbidden' | 'busy' | string;\n /** Server's current translation text (null if the row doesn't exist yet). */\n current_text: string | null;\n}\n\nexport type StatusCallback = (status: 'unsaved' | 'saving' | 'saved' | 'error', message?: string) => void;\n\nexport class EditorAPI {\n private queue: QueuedTranslation[] = [];\n private apiBase: string;\n private sessionToken: string;\n private projectId: string;\n private flushInterval: ReturnType<typeof setInterval> | null = null;\n private renewalInterval: ReturnType<typeof setInterval> | null = null;\n private onStatus: StatusCallback = () => {};\n private onFlushed: (keys: string[]) => void = () => {};\n private onSessionExpired: (() => void) | null = null;\n // Workflow context cache \u2014 keyed by locale (a per-target override can give\n // one locale a different workflow) and TTL-bound (the PM can change the\n // project's workflow from the dashboard mid-session; a boundary pinned at\n // first fetch would render buttons the server refuses). Promises are\n // cached so concurrent callers share one in-flight fetch.\n private workflowInfoCache = new Map<\n string,\n { promise: Promise<EditorWorkflowInfo | null>; fetchedAt: number }\n >();\n private static readonly WORKFLOW_INFO_TTL_MS = 5 * 60 * 1000;\n /** Whether this session may lazily create rows for keys with no target row\n * yet. Mirrors the server's create gate (creation is stage-0 work \u2014 PM/admin\n * or stage-0 assignees); refreshed from targets/status `can_create`.\n * Default true: older backends don't send it and don't gate creates. */\n canCreateMissing = true;\n\n /** True while this locale's target has an MT fill queued/running (refreshed\n * from targets/status `target_busy`). canEdit stays the permission truth\n * during the window; stage transitions would 423 until it clears, and\n * saves are denied as \"busy\" and auto-retried by the flush loop. */\n targetBusy = false;\n\n constructor(apiBase: string, sessionToken: string, projectId: string) {\n this.apiBase = apiBase.replace(/\\/$/, '');\n this.sessionToken = sessionToken;\n this.projectId = projectId;\n }\n\n setStatusCallback(cb: StatusCallback): void {\n this.onStatus = cb;\n }\n\n setFlushedCallback(cb: (keys: string[]) => void): void {\n this.onFlushed = cb;\n }\n\n setSessionExpiredCallback(cb: () => void): void {\n this.onSessionExpired = cb;\n }\n\n /**\n * Start auto-flush (30s interval + visibilitychange) and session renewal (60s check)\n */\n startAutoFlush(): void {\n this.flushInterval = setInterval(() => this.flush(), 30_000);\n document.addEventListener('visibilitychange', this.handleVisibility);\n\n // Check session renewal every 60s\n this.renewalInterval = setInterval(() => this.maybeRenewSession(), 60_000);\n }\n\n /**\n * Stop auto-flush and flush remaining items\n */\n async stopAndFlush(): Promise<void> {\n if (this.flushInterval) {\n clearInterval(this.flushInterval);\n this.flushInterval = null;\n }\n if (this.renewalInterval) {\n clearInterval(this.renewalInterval);\n this.renewalInterval = null;\n }\n document.removeEventListener('visibilitychange', this.handleVisibility);\n await this.flush();\n }\n\n /**\n * Queue a translation for saving\n */\n queueSave(item: QueuedTranslation): void {\n // Replace existing entry for same key+locale\n const idx = this.queue.findIndex(\n (q) => q.key === item.key && q.locale === item.locale && q.namespace === item.namespace\n );\n if (idx >= 0) {\n this.queue[idx] = item;\n } else {\n this.queue.push(item);\n }\n this.onStatus('unsaved', `${this.queue.length} unsaved`);\n\n // Auto-flush if queue is large\n if (this.queue.length > 50) {\n this.flush();\n }\n }\n\n /**\n * Flush all queued translations to the server\n */\n async flush(): Promise<void> {\n if (this.queue.length === 0) return;\n\n const items = [...this.queue];\n this.onStatus('saving');\n\n try {\n const response = await this.authFetch(\n `${this.apiBase}/api/integrations/targets/batch`,\n {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n translations: items.map((t) => ({\n key: `${t.locale}:${t.namespace}:${t.key}`,\n value: { text: t.text, styles: t.styles },\n })),\n }),\n }\n );\n\n if (response.status === 401) {\n this.handleSessionExpired();\n return;\n }\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`);\n }\n\n // Per-key authorization outcomes (dashboard parity): the server writes\n // what it can and reports refused rows in denied_rows instead of failing\n // the batch. A 200 with denied rows must NOT read as \"all saved\".\n const body = (await response.json().catch(() => null)) as {\n denied?: number;\n denied_rows?: DeniedRow[];\n } | null;\n const deniedRows = body?.denied_rows ?? [];\n // \"busy\" (TARGET_BUSY: the target's MT fill is in flight) is transient \u2014\n // keep those edits queued and retry after the fill's sweep window,\n // instead of reverting the draft like a real authorization denial.\n const busyKeys = new Set(\n deniedRows.filter((d) => d.reason === 'busy').map((d) => d.key)\n );\n const hardDenied = deniedRows.filter((d) => d.reason !== 'busy');\n\n // Remove flushed items from queue (new items may have been added during\n // fetch) \u2014 except busy ones, which stay queued for the retry flush.\n const retained: QueuedTranslation[] = [];\n for (const item of items) {\n const idx = this.queue.indexOf(item);\n if (busyKeys.has(`${item.locale}:${item.namespace}:${item.key}`)) {\n retained.push(item);\n continue;\n }\n if (idx >= 0) this.queue.splice(idx, 1);\n }\n\n // Notify sidebar to re-fetch status for flushed keys \u2014 for denied rows\n // the refetch restores the server's current text in place of the\n // refused edit (the batch analogue of the dashboard's 423/403 snapshot).\n // Busy rows are excluded: their draft is still pending, not refused.\n this.onFlushed(\n items\n .filter((i) => !busyKeys.has(`${i.locale}:${i.namespace}:${i.key}`))\n .map((i) => i.key)\n );\n\n if (retained.length > 0) {\n this.scheduleBusyRetry();\n }\n\n if (hardDenied.length > 0) {\n const lockedCount = hardDenied.filter((d) => d.reason === 'locked').length;\n const message =\n hardDenied.length === 1\n ? lockedCount === 1\n ? '1 change not saved \u2014 row is locked (only a PM can edit it)'\n : '1 change not saved \u2014 you are not assigned to that row'\n : `${hardDenied.length} changes not saved \u2014 locked or not assigned to you`;\n this.onStatus('error', message);\n } else if (retained.length > 0) {\n this.onStatus(\n 'unsaved',\n `${retained.length} waiting \u2014 machine translation is filling this page, retrying shortly`\n );\n } else if (this.queue.length === 0) {\n this.onStatus('saved', 'All changes saved');\n } else {\n this.onStatus('unsaved', `${this.queue.length} unsaved`);\n }\n } catch (err) {\n this.onStatus('error', `Save failed: ${(err as Error).message}`);\n }\n }\n\n private busyRetryTimer: ReturnType<typeof setTimeout> | null = null;\n\n /**\n * Re-flush edits refused with reason \"busy\" (target MT fill in flight).\n * One pipeline sweep (~30 s worst case) clears the fill; a single pending\n * timer is enough \u2014 each retry re-schedules itself while busy rows remain.\n */\n private scheduleBusyRetry(): void {\n if (this.busyRetryTimer) return;\n this.busyRetryTimer = setTimeout(() => {\n this.busyRetryTimer = null;\n this.flush();\n }, 8000);\n }\n\n private statusAbort: AbortController | null = null;\n\n /**\n * Fetch translation status for a batch of keys (includes unconfirmed rows).\n * Cancels any in-flight status request. Caps at 200 keys per request.\n */\n async fetchStatus(\n keys: string[],\n locale: string,\n namespace: string\n ): Promise<Record<string, TargetStatus | null>> {\n if (keys.length === 0) return {};\n\n // Cancel previous in-flight request\n if (this.statusAbort) this.statusAbort.abort();\n this.statusAbort = new AbortController();\n\n // Cap keys to avoid exceeding URL length limits\n const batch = keys.slice(0, 200);\n\n const params = new URLSearchParams({ locale, namespace, keys: batch.join(',') });\n try {\n const response = await this.authFetch(\n `${this.apiBase}/api/integrations/targets/status?${params}`,\n { signal: this.statusAbort.signal }\n );\n if (response.status === 401) {\n this.handleSessionExpired();\n return {};\n }\n if (!response.ok) return {};\n const data = await response.json();\n // Server sends can_create when the caller is an editor session \u2014 false\n // means keys WITHOUT a row must render read-only (the batch save would\n // refuse the lazy create). Absent (older backend) = allowed.\n this.canCreateMissing = data.can_create !== false;\n // Absent (older backend) = not busy \u2014 old servers bake the fill gate\n // into canEdit instead, so nothing extra to surface.\n this.targetBusy = data.target_busy === true;\n return data.translations;\n } catch (err) {\n if ((err as Error).name === 'AbortError') return {};\n return {};\n }\n }\n\n get pendingCount(): number {\n return this.queue.length;\n }\n\n /**\n * Fetch workflow context (stages, user's stage assignments, PM/admin flag,\n * advance boundary) for the EFFECTIVE workflow of `locale` \u2014 the server\n * resolves the per-target override (`target ?? project`) when a locale is\n * given. Cached per locale with a 5-min TTL so a mid-session workflow\n * change from the dashboard settings is picked up without a page reload.\n * Returns null if the fetch fails; callers should hide the strip in that case.\n */\n async fetchWorkflowInfo(locale?: string): Promise<EditorWorkflowInfo | null> {\n const cacheKey = locale ?? '';\n const cached = this.workflowInfoCache.get(cacheKey);\n if (cached && Date.now() - cached.fetchedAt < EditorAPI.WORKFLOW_INFO_TTL_MS) {\n return cached.promise;\n }\n const promise = (async (): Promise<EditorWorkflowInfo | null> => {\n try {\n const url =\n `${this.apiBase}/api/integrations/editor/workflow` +\n (locale ? `?locale=${encodeURIComponent(locale)}` : '');\n const response = await this.authFetch(url);\n if (response.status === 401) {\n this.handleSessionExpired();\n return null;\n }\n if (!response.ok) return null;\n const data = await response.json();\n const stages: WorkflowStageInfo[] = (Array.isArray(data.stages) ? data.stages : []).map(\n (s: { index: number; name: string; role: string | null; is_postreview?: boolean }) => ({\n index: s.index,\n name: s.name,\n role: s.role ?? null,\n isPostreview: Boolean(s.is_postreview),\n })\n );\n return {\n stages,\n userAssignedStages: Array.isArray(data.user_assigned_stages) ? data.user_assigned_stages : [],\n isPmAdmin: Boolean(data.is_pm_admin),\n workflowMode: data.workflow_mode === 'per_file' ? 'per_file' : 'per_row',\n // Older backends don't send work_stage_count; before Postreview\n // stages existed the boundary was len(stages), so fall back to that.\n workStageCount:\n typeof data.work_stage_count === 'number' ? data.work_stage_count : stages.length,\n };\n } catch {\n return null;\n }\n })();\n this.workflowInfoCache.set(cacheKey, { promise, fetchedAt: Date.now() });\n return promise;\n }\n\n // ============================================\n // Session renewal\n // ============================================\n\n /**\n * Check if session is within 5min of expiry, and renew if so.\n *\n * The threshold is intentionally generous because background tabs throttle\n * setInterval to once-per-minute (Chrome) or even less aggressively, and\n * a sleeping laptop pauses timers entirely. Renewing early gives us\n * headroom for those cases \u2014 the renewal interval still runs before the\n * 15min server-side TTL elapses even if it fires a minute or two late.\n */\n private async maybeRenewSession(): Promise<void> {\n const exp = this.getSessionExp();\n if (!exp) return;\n\n const timeLeft = exp * 1000 - Date.now();\n if (timeLeft > 300_000) return; // More than 5 min left, skip\n\n await this.renewSession();\n }\n\n /**\n * Attempt one renewal and signal whether the token actually changed.\n * Used by the 401-retry path in API calls.\n */\n private async tryRenew(): Promise<boolean> {\n const before = this.sessionToken;\n await this.renewSession();\n return this.sessionToken !== before;\n }\n\n private async renewSession(): Promise<void> {\n try {\n const response = await fetch(`${this.apiBase}/api/editor/renew`, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${this.sessionToken}`,\n },\n });\n\n if (response.status === 401) {\n this.handleSessionExpired();\n return;\n }\n\n if (!response.ok) return;\n\n const data = await response.json();\n this.sessionToken = data.session_token;\n\n // Update sessionStorage + preview cookie. The cookie tells the edge\n // proxy this is an active editor session so it should fetch drafts.\n try {\n const raw = this.sessionToken.startsWith('es_') ? this.sessionToken.slice(3) : this.sessionToken;\n const parts = raw.split('.');\n if (parts.length === 3) {\n const payload = JSON.parse(atob(parts[1]));\n sessionStorage.setItem('__lr_editor_session', JSON.stringify({\n token: this.sessionToken,\n exp: payload.exp || 0,\n }));\n document.cookie = '__lr_editor_active=1; path=/; SameSite=Lax';\n }\n } catch { /* non-critical */ }\n } catch {\n // Network error \u2014 will retry on next interval\n }\n }\n\n private getSessionExp(): number | null {\n try {\n const raw = this.sessionToken.startsWith('es_') ? this.sessionToken.slice(3) : this.sessionToken;\n const parts = raw.split('.');\n if (parts.length !== 3) return null;\n const payload = JSON.parse(atob(parts[1]));\n return payload.exp || null;\n } catch {\n return null;\n }\n }\n\n /**\n * Authenticated fetch with one-shot 401 retry. If the request comes back\n * 401, we attempt a session renewal and replay the request once with the\n * new token. If renewal fails (token past the server's 2min grace), the\n * caller still gets a 401 and falls through to handleSessionExpired.\n *\n * Always passes `init.signal` straight through so abortable callers\n * (e.g. fetchStatus) still cancel correctly.\n */\n private async authFetch(url: string, init: RequestInit = {}): Promise<Response> {\n const buildHeaders = (): HeadersInit => {\n const h = new Headers(init.headers);\n h.set('Authorization', `Bearer ${this.sessionToken}`);\n return h;\n };\n\n let response = await fetch(url, { ...init, headers: buildHeaders() });\n if (response.status === 401) {\n const renewed = await this.tryRenew();\n if (renewed) {\n response = await fetch(url, { ...init, headers: buildHeaders() });\n }\n }\n return response;\n }\n\n private handleSessionExpired(): void {\n // Clear the preview cookie too so the edge stops serving drafts as\n // soon as the session is gone, even if the page stays open.\n document.cookie = '__lr_editor_active=; path=/; SameSite=Lax; Max-Age=0';\n this.onStatus('error', 'Session expired. Please reopen the editor from the dashboard.');\n if (this.onSessionExpired) {\n this.onSessionExpired();\n }\n }\n\n private handleVisibility = (): void => {\n if (document.visibilityState === 'hidden') {\n this.flush();\n } else if (document.visibilityState === 'visible') {\n // Tab returned to foreground. Background timers throttle/pause, so\n // by the time the user comes back the session may be close to (or\n // past) expiry. Try a renewal immediately \u2014 if still within the\n // 2min server grace, this saves the next API call from 401.\n this.maybeRenewSession();\n }\n };\n\n // ============================================\n // Snapshot API\n // ============================================\n\n /**\n * Upload a captured snapshot. Returns the created snapshot's UUID so the\n * caller can build a share URL (`?__lr_snapshot={uuid}`).\n */\n async createSnapshot(\n snapshot: CapturedSnapshot,\n ): Promise<{ snapshotUuid: string } | null> {\n try {\n const response = await fetch(`${this.apiBase}/api/editor/snapshots`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.sessionToken}`,\n },\n body: JSON.stringify({\n pageUrl: snapshot.pageUrl,\n title: snapshot.title,\n viewport: snapshot.viewport,\n textEntries: snapshot.textEntries,\n html: snapshot.html,\n }),\n });\n\n if (response.status === 401) {\n this.handleSessionExpired();\n return null;\n }\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`);\n }\n\n const data = await response.json();\n return { snapshotUuid: data.snapshot_uuid || data.snapshotUuid };\n } catch (err) {\n this.onStatus('error', `Snapshot failed: ${(err as Error).message}`);\n return null;\n }\n }\n\n /**\n * Re-capture an existing snapshot. Server appends a new version and keeps\n * the same `snapshotUuid`. Returns the new version number on success.\n */\n async recaptureSnapshot(\n snapshotUuid: string,\n snapshot: CapturedSnapshot,\n ): Promise<{ snapshotUuid: string; version: number } | null> {\n try {\n const response = await fetch(\n `${this.apiBase}/api/editor/snapshots/${encodeURIComponent(snapshotUuid)}`,\n {\n method: 'PUT',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.sessionToken}`,\n },\n body: JSON.stringify({\n pageUrl: snapshot.pageUrl,\n title: snapshot.title,\n viewport: snapshot.viewport,\n textEntries: snapshot.textEntries,\n html: snapshot.html,\n }),\n },\n );\n\n if (response.status === 401) {\n this.handleSessionExpired();\n return null;\n }\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`);\n }\n\n const data = await response.json();\n return {\n snapshotUuid: data.snapshot_uuid || data.snapshotUuid,\n version: data.version || 1,\n };\n } catch (err) {\n this.onStatus('error', `Re-capture failed: ${(err as Error).message}`);\n return null;\n }\n }\n\n /**\n * Fetch a single snapshot by UUID. Returns null on auth failure or 404.\n */\n async getSnapshot(snapshotUuid: string): Promise<SnapshotDetail | null> {\n try {\n const response = await fetch(\n `${this.apiBase}/api/editor/snapshots/${encodeURIComponent(snapshotUuid)}`,\n {\n headers: { 'Authorization': `Bearer ${this.sessionToken}` },\n },\n );\n if (response.status === 401) {\n this.handleSessionExpired();\n return null;\n }\n if (!response.ok) return null;\n return (await response.json()) as SnapshotDetail;\n } catch {\n return null;\n }\n }\n\n /**\n * List all snapshots the current editor session can see.\n */\n async listSnapshots(): Promise<SnapshotListItem[]> {\n try {\n const response = await fetch(`${this.apiBase}/api/editor/snapshots`, {\n headers: { 'Authorization': `Bearer ${this.sessionToken}` },\n });\n if (response.status === 401) {\n this.handleSessionExpired();\n return [];\n }\n if (!response.ok) return [];\n const data = await response.json();\n return data.snapshots || [];\n } catch {\n return [];\n }\n }\n\n /**\n * Delete (soft) a snapshot by UUID.\n */\n async deleteSnapshot(snapshotUuid: string): Promise<boolean> {\n try {\n const response = await fetch(\n `${this.apiBase}/api/editor/snapshots/${encodeURIComponent(snapshotUuid)}`,\n {\n method: 'DELETE',\n headers: { 'Authorization': `Bearer ${this.sessionToken}` },\n },\n );\n if (response.status === 401) {\n this.handleSessionExpired();\n return false;\n }\n return response.ok;\n } catch {\n return false;\n }\n }\n\n /**\n * Workflow transitions (Phase 3). Per-key endpoints on the integrations API\n * that mirror the dashboard's row-level transitions but are addressable by\n * composite editor key (locale:namespace:auto_xxx).\n *\n * Returns the parsed JSON on success, or null on failure (caller usually\n * shows a toast / re-fetches status).\n */\n async transitionTarget(\n composite: string,\n action: 'confirm' | 'unconfirm' | 'reject' | 'advance' | 'mark-ready' | 'unmark-ready',\n ): Promise<Record<string, unknown> | null> {\n try {\n const response = await this.authFetch(\n `${this.apiBase}/api/integrations/targets/${encodeURIComponent(composite)}/${action}`,\n { method: 'POST' },\n );\n if (response.status === 401) {\n this.handleSessionExpired();\n return null;\n }\n if (!response.ok) {\n // 423 TARGET_BUSY: the target's MT fill is in flight \u2014 transient\n // (clears within one pipeline sweep), not a permissions failure.\n const errBody = (await response.json().catch(() => null)) as {\n error?: { code?: string };\n } | null;\n if (response.status === 423 && errBody?.error?.code === 'TARGET_BUSY') {\n this.onStatus(\n 'error',\n 'Machine translation is filling this page \u2014 try again in a few seconds.'\n );\n } else {\n this.onStatus('error', `${action} failed: HTTP ${response.status}`);\n }\n return null;\n }\n return (await response.json()) as Record<string, unknown>;\n } catch (err) {\n this.onStatus('error', `${action} failed: ${(err as Error).message}`);\n return null;\n }\n }\n}\n", "/**\n * Exclude Filter\n * Filters out text that should not be translated\n * Mirrors PHP's HtmlTranslator logic\n */\n\nexport interface ExcludePatterns {\n emails: boolean;\n urls: boolean;\n numbers: boolean;\n phones: boolean;\n}\n\nconst DEFAULT_PATTERNS: ExcludePatterns = {\n emails: true,\n urls: true,\n numbers: true,\n phones: true,\n};\n\n// Regex patterns matching PHP's HtmlTranslator\nconst PATTERNS = {\n email: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$/,\n url: /^(https?:\\/\\/|www\\.)[^\\s]+$/i,\n number: /^[\\d\\s.,+-]+$/,\n phone: /^[+\\d][\\d\\s().-]{6,}$/,\n};\n\nexport class ExcludeFilter {\n private patterns: ExcludePatterns;\n private minTextLength: number;\n\n constructor(patterns: Partial<ExcludePatterns> = {}, minTextLength = 2) {\n this.patterns = { ...DEFAULT_PATTERNS, ...patterns };\n this.minTextLength = minTextLength;\n }\n\n /**\n * Check if text should be excluded from translation\n */\n shouldExclude(text: string): boolean {\n const trimmed = text.trim();\n\n // Too short\n if (trimmed.length < this.minTextLength) {\n return true;\n }\n\n // Check auto-exclude patterns\n if (this.patterns.emails && PATTERNS.email.test(trimmed)) {\n return true;\n }\n\n if (this.patterns.urls && PATTERNS.url.test(trimmed)) {\n return true;\n }\n\n if (this.patterns.numbers && PATTERNS.number.test(trimmed)) {\n return true;\n }\n\n if (this.patterns.phones && PATTERNS.phone.test(trimmed)) {\n return true;\n }\n\n return false;\n }\n}\n", "/**\n * Block Extractor\n * Finds translatable block elements in DOM and parses them into ContentUnit.\n * Mirrors WordPress TS BlockExtractor and edge proxy block-level approach.\n */\n\nimport type { ContentUnit } from '../content/types.js';\nimport { ExcludeFilter, ExcludePatterns } from './exclude-filter.js';\nimport { parseBlockElement } from './markup-parser.js';\nimport { generateAutoKey } from './md5.js';\n\nconst BLOCK_SELECTOR =\n 'p,h1,h2,h3,h4,h5,h6,li,td,th,label,button,figcaption,dt,dd,summary,caption';\n\n// Inline elements eligible for \"orphan\" extraction when they have no block\n// ancestor. Without this, markup like `<div><a>Transfer</a></div>` produces\n// zero translatable units because <div>/<a> are absent from BLOCK_SELECTOR\n// and the link has no <p>/<li>/etc. parent to absorb it as an indexed style.\n// Conservative on purpose: only <a>. <span>/<button> can join later if real\n// usage demands it (button is already a block tag).\nconst INLINE_BLOCK_SELECTOR = 'a';\n\n// Tags to skip (same as PHP/edge)\nconst SKIP_TAGS = new Set([\n 'SCRIPT',\n 'STYLE',\n 'CODE',\n 'PRE',\n 'TEXTAREA',\n 'NOSCRIPT',\n 'SVG',\n]);\n\n// Attributes that mark elements to skip\nconst SKIP_ATTRIBUTES = [\n 'data-lionrapid-translated', // Server already translated\n 'data-lionrapid-client', // Client already translated\n 'data-no-translate', // Explicitly marked to skip\n // Edge proxy already rendered this block with the correct translation\n // and styles. Skip in normal extraction so the client-side auto-translator\n // can't overwrite the styled DOM with a stale (or plain-text) cache entry.\n // The editor passes includeTranslated=true to bypass this and re-extract\n // edge-rendered blocks for inspection.\n 'data-lr-block',\n];\n\nexport interface ExtractedBlock {\n element: Element;\n unit: ContentUnit;\n key: string;\n}\n\nexport class BlockExtractor {\n private excludeFilter: ExcludeFilter;\n private excludeSelectors: string[];\n\n constructor(\n excludePatterns: Partial<ExcludePatterns> = {},\n excludeSelectors: string[] = [],\n minTextLength = 2\n ) {\n this.excludeFilter = new ExcludeFilter(excludePatterns, minTextLength);\n this.excludeSelectors = excludeSelectors;\n }\n\n /**\n * Extract all translatable block elements from a root.\n * @param includeTranslated - If true, include elements already marked as translated (for editor mode)\n */\n extract(root: Element | Document, includeTranslated = false): ExtractedBlock[] {\n const results: ExtractedBlock[] = [];\n const descendants = root.querySelectorAll(BLOCK_SELECTOR);\n\n // Include the root itself if it matches the block selector\n const elements: Element[] = [];\n if (root instanceof Element && root.matches(BLOCK_SELECTOR)) {\n elements.push(root);\n }\n for (let i = 0; i < descendants.length; i++) {\n elements.push(descendants[i]);\n }\n\n for (let i = 0; i < elements.length; i++) {\n const el = elements[i];\n\n // Skip if inside a SKIP_TAG ancestor\n if (this.isInsideSkipTag(el)) {\n continue;\n }\n\n // Skip if has skip attributes (unless editor mode includes translated)\n if (!includeTranslated && this.hasSkipAttribute(el)) {\n continue;\n }\n\n // Skip if matches exclude selectors\n if (this.matchesExcludeSelector(el)) {\n continue;\n }\n\n // Skip if has translate=\"no\"\n if (el.getAttribute('translate') === 'no') {\n continue;\n }\n\n // Skip nested block elements (let the inner blocks be extracted)\n if (el.querySelector(BLOCK_SELECTOR)) {\n continue;\n }\n\n // For already-translated elements, use original HTML for key + unit\n const lrBlock = el.getAttribute('data-lr-block');\n const lrOriginal = el.getAttribute('data-lr-original');\n\n let unit: ContentUnit;\n if (lrOriginal) {\n // Parse original (source) HTML \u2192 correct ContentUnit for key generation\n const temp = document.createElement(el.tagName);\n temp.innerHTML = lrOriginal;\n unit = parseBlockElement(temp);\n } else {\n unit = parseBlockElement(el);\n }\n\n // Strip indexed tags for exclude filter\n const strippedText = unit.text.replace(/<\\/?[0-9]+\\/?>/g, '');\n\n // Apply exclude filter\n if (this.excludeFilter.shouldExclude(strippedText)) {\n continue;\n }\n\n // Key priority: data-lr-block (translated) \u2192 data-i18n (named) \u2192 auto-generate\n let key: string;\n if (lrBlock) {\n key = lrBlock;\n } else {\n const dataI18n = el.getAttribute('data-i18n');\n if (dataI18n) {\n key = dataI18n.includes(':') ? dataI18n : `common:${dataI18n}`;\n } else {\n key = generateAutoKey(unit.text);\n }\n }\n\n results.push({ element: el, unit, key });\n }\n\n this.extractOrphanInline(root, results, includeTranslated);\n\n return results;\n }\n\n /**\n * Second pass: extract inline elements (currently <a>) that have no block\n * ancestor. These would otherwise slip through both the block pass (not in\n * BLOCK_SELECTOR) and the inline-style capture (no parent block to host\n * them as `<1>...</1>`). Common shape: a `<div>` action group containing\n * only links.\n */\n private extractOrphanInline(\n root: Element | Document,\n results: ExtractedBlock[],\n includeTranslated: boolean\n ): void {\n const candidates = root.querySelectorAll(INLINE_BLOCK_SELECTOR);\n const elements: Element[] = [];\n if (root instanceof Element && root.matches(INLINE_BLOCK_SELECTOR)) {\n elements.push(root);\n }\n for (let i = 0; i < candidates.length; i++) {\n elements.push(candidates[i]);\n }\n\n for (let i = 0; i < elements.length; i++) {\n const el = elements[i];\n\n if (this.isInsideSkipTag(el)) continue;\n if (!includeTranslated && this.hasSkipAttribute(el)) continue;\n if (this.matchesExcludeSelector(el)) continue;\n if (el.getAttribute('translate') === 'no') continue;\n\n // Has a block ancestor \u2014 already captured as `<N>...</N>` by the\n // block pass (or its parent block was skipped, in which case we leave\n // it alone to avoid double-counting from a partially-skipped block).\n if (el.parentElement?.closest(BLOCK_SELECTOR)) continue;\n\n // Outer orphan wins: `<a><span>X</span></a>` \u2192 only the outer <a>.\n // (Currently INLINE_BLOCK_SELECTOR is just <a>, so this guards\n // against nested links in malformed HTML.)\n if (el.parentElement?.closest(INLINE_BLOCK_SELECTOR)) continue;\n\n // Contains a nested block \u2014 let the inner block be extracted instead.\n if (el.querySelector(BLOCK_SELECTOR)) continue;\n\n const lrOriginal = el.getAttribute('data-lr-original');\n let unit: ContentUnit;\n if (lrOriginal) {\n const temp = document.createElement(el.tagName);\n temp.innerHTML = lrOriginal;\n unit = parseBlockElement(temp);\n } else {\n unit = parseBlockElement(el);\n }\n\n const strippedText = unit.text.replace(/<\\/?[0-9]+\\/?>/g, '');\n if (this.excludeFilter.shouldExclude(strippedText)) continue;\n\n const lrBlock = el.getAttribute('data-lr-block');\n let key: string;\n if (lrBlock) {\n key = lrBlock;\n } else {\n const dataI18n = el.getAttribute('data-i18n');\n if (dataI18n) {\n key = dataI18n.includes(':') ? dataI18n : `common:${dataI18n}`;\n } else {\n key = generateAutoKey(unit.text);\n }\n }\n\n results.push({ element: el, unit, key });\n }\n }\n\n private isInsideSkipTag(element: Element): boolean {\n let current: Element | null = element.parentElement;\n while (current) {\n // Foreign elements (SVG/MathML) report a case-preserved tagName (\"svg\",\n // not \"SVG\"), so match case-insensitively or the SVG skip never fires.\n if (SKIP_TAGS.has(current.tagName.toUpperCase())) {\n return true;\n }\n current = current.parentElement;\n }\n return false;\n }\n\n private hasSkipAttribute(element: Element): boolean {\n let current: Element | null = element;\n while (current) {\n for (const attr of SKIP_ATTRIBUTES) {\n if (current.hasAttribute(attr)) {\n return true;\n }\n }\n current = current.parentElement;\n }\n return false;\n }\n\n private matchesExcludeSelector(element: Element): boolean {\n for (const selector of this.excludeSelectors) {\n try {\n if (element.matches(selector) || element.closest(selector)) {\n return true;\n }\n } catch {\n // Invalid selector, skip\n }\n }\n return false;\n }\n}\n", "/**\n * Locale-preserving URL helper.\n *\n * When the operator is browsing on a localized URL (e.g. `/es/about/`) and\n * follows an internal link whose href doesn't include the locale prefix\n * (e.g. `/contact/`), naive navigation drops them back to the default\n * language. This helper rewrites same-origin URLs to keep the current\n * locale prefix in front, matching the `/{lang}/{path}` shape the server\n * already routes.\n *\n * No-op when:\n * - href is cross-origin (don't touch external links)\n * - href is malformed (can't parse \u2014 leave as-is)\n * - the current locale IS the default locale (no prefix needed)\n * - the URL already has a locale prefix (don't double-prefix)\n */\nexport function localizeUrl(href: string, defaultLocale: string): string {\n let url: URL;\n try {\n url = new URL(href, window.location.href);\n } catch {\n return href;\n }\n if (url.origin !== window.location.origin) return href;\n\n const currentMatch = window.location.pathname.match(/^\\/([a-z]{2})(\\/|$)/);\n const currentLocale = currentMatch ? currentMatch[1] : defaultLocale;\n if (currentLocale === defaultLocale) return url.href;\n\n // Already locale-prefixed \u2014 leave alone.\n if (/^\\/[a-z]{2}(\\/|$)/.test(url.pathname)) return url.href;\n\n url.pathname = '/' + currentLocale + url.pathname;\n return url.href;\n}\n", "/**\n * Sidebar \u2014 Translation editing panel\n * Shows original text, translation textarea, live preview, and page blocks list.\n */\n\nimport type { ContentUnit } from '../../content/types.js';\nimport type { ResolvedKey } from './key-resolver.js';\nimport { HTMLSerializer } from '../../content/serializer/HTMLSerializer.js';\nimport { escapeHtml } from '../../content/sanitizer/HTMLSanitizer.js';\nimport { applyLivePreview } from './live-preview.js';\nimport type { EditorAPI, TargetStatus, EditorWorkflowInfo } from './editor-api.js';\nimport { isRowAtPostreview } from './editor-api.js';\nimport { BlockExtractor } from '../block-extractor.js';\nimport { generateAutoKey } from '../md5.js';\nimport { localizeUrl } from './locale-url.js';\n\nconst htmlSerializer = new HTMLSerializer();\n\nexport class Sidebar {\n private el: HTMLDivElement;\n private bodyEl: HTMLDivElement;\n private statusEl: HTMLDivElement;\n private api: EditorAPI;\n private locale: string;\n private namespace: string;\n private defaultLocale: string;\n private current: ResolvedKey | null = null;\n private blockExtractor: BlockExtractor;\n private onBlockClick: (resolved: ResolvedKey) => void;\n private statusMap: Map<string, TargetStatus | null> = new Map();\n private unsavedKeys: Set<string> = new Set();\n private onProgressUpdate: ((translated: number, total: number) => void) | null = null;\n private lastFetchTime = 0;\n private lastFlushTime = 0;\n\n private userRole: string;\n // Latest known workflow context for this.locale. Revalidated on every strip\n // render through the API's TTL-bound, per-locale cache \u2014 the workflow can\n // change mid-session (dashboard settings) and can differ per locale\n // (per-target override), so it must not be pinned at first fetch.\n private workflowInfo: EditorWorkflowInfo | null = null;\n\n constructor(\n shadowRoot: ShadowRoot,\n api: EditorAPI,\n locale: string,\n namespace: string,\n defaultLocale: string,\n onBlockClick: (resolved: ResolvedKey) => void,\n userRole: string = '',\n ) {\n this.api = api;\n this.locale = locale;\n this.namespace = namespace;\n this.defaultLocale = defaultLocale;\n this.onBlockClick = onBlockClick;\n this.userRole = userRole;\n this.blockExtractor = new BlockExtractor();\n\n this.el = document.createElement('div');\n this.el.className = 'lr-sidebar';\n\n // Resize handle\n const resizeHandle = document.createElement('div');\n resizeHandle.className = 'lr-sidebar-resize';\n this.initResize(resizeHandle);\n this.el.appendChild(resizeHandle);\n\n // Header\n const header = document.createElement('div');\n header.className = 'lr-sidebar-header';\n const title = document.createElement('span');\n title.className = 'lr-sidebar-title';\n title.textContent = 'Edit Translation';\n header.appendChild(title);\n const closeBtn = document.createElement('button');\n closeBtn.className = 'lr-sidebar-close';\n closeBtn.innerHTML = '&times;';\n closeBtn.addEventListener('click', () => this.close());\n header.appendChild(closeBtn);\n this.el.appendChild(header);\n\n // Body\n this.bodyEl = document.createElement('div');\n this.bodyEl.className = 'lr-sidebar-body';\n this.el.appendChild(this.bodyEl);\n\n // Status bar (rendered inline in renderContent, above preview)\n this.statusEl = document.createElement('div');\n this.statusEl.className = 'lr-status';\n\n // Wire up API status\n this.api.setStatusCallback((status, message) => {\n this.statusEl.textContent = message || status;\n this.statusEl.className = `lr-status ${status}`;\n });\n\n // After flush, re-fetch status for saved keys and refresh blocks list\n this.api.setFlushedCallback(async (keys) => {\n this.lastFlushTime = Date.now();\n const freshStatuses = await this.api.fetchStatus(keys, this.locale, this.namespace);\n for (const [key, status] of Object.entries(freshStatuses)) {\n this.statusMap.set(key, status);\n this.unsavedKeys.delete(key);\n }\n this.refreshBlocksList();\n this.emitProgress();\n });\n\n // Console diagnostic: dump every block's key + full ContentUnit text +\n // styles so the operator can compare against what's stored on the\n // server. Useful when the same source sentence appears with multiple\n // auto_keys (PHP\u2194JS parity divergence) \u2014 paste the output here and\n // we'll spot the difference.\n //\n // Usage from DevTools: __lr_dump_keys()\n if (typeof window !== 'undefined') {\n (window as unknown as Record<string, unknown>).__lr_dump_keys = () => {\n const blocks = this.blockExtractor.extract(document.body, true);\n const rows = blocks.map((b, i) => {\n const s = this.statusMap.get(b.key);\n return {\n i,\n key: b.key,\n text: b.unit.text,\n stylesJson: JSON.stringify(b.unit.styles),\n statusText: s?.text ?? '',\n isReady: !!s?.isReady,\n hasTranslation: !!s?.text,\n };\n });\n console.table(rows);\n return rows;\n };\n\n // Diagnostic/e2e hooks that drive the SAME save path the sidebar\n // textarea uses (queueSave \u2192 batched flush \u2192 POST /targets/batch \u2192\n // flushed-callback status re-fetch). The editor UI lives in a closed\n // shadow root \u2014 intentionally unreachable from page JS and from\n // Playwright \u2014 so automated tests exercise the save round-trip\n // through these instead of synthetic textarea events.\n //\n // __lr_editor_edit('auto_xxx', 'nuevo texto') \u2014 queue an edit\n // await __lr_editor_flush() \u2014 force the batch POST now\n (window as unknown as Record<string, unknown>).__lr_editor_edit = (key: string, text: string) => {\n const blocks = this.blockExtractor.extract(document.body, true);\n const block = blocks.find((b) => b.key === key);\n if (!block) throw new Error(`[__lr_editor_edit] no block on page with key ${key}`);\n this.unsavedKeys.add(key);\n this.api.queueSave({\n key,\n locale: this.locale,\n namespace: this.namespace,\n text,\n styles: block.unit.styles,\n });\n };\n (window as unknown as Record<string, unknown>).__lr_editor_flush = () => this.api.flush();\n }\n\n shadowRoot.appendChild(this.el);\n }\n\n open(resolved: ResolvedKey): void {\n this.current = resolved;\n this.el.classList.add('open');\n this.renderContent(resolved);\n }\n\n close(): void {\n this.current = null;\n this.el.classList.remove('open');\n }\n\n isOpen(): boolean {\n return this.el.classList.contains('open');\n }\n\n updateLocale(locale: string): void {\n this.locale = locale;\n // Clear status on locale change \u2014 will be re-fetched\n this.statusMap.clear();\n this.unsavedKeys.clear();\n }\n\n setProgressCallback(cb: (translated: number, total: number) => void): void {\n this.onProgressUpdate = cb;\n }\n\n /**\n * Hydrate the status map with server data for all page blocks.\n * Called once on editor init.\n */\n async hydrateStatus(): Promise<void> {\n const blocks = this.blockExtractor.extract(document.body, true);\n const keys = blocks.map((b) => b.key);\n if (keys.length === 0) return;\n\n const statuses = await this.api.fetchStatus(keys, this.locale, this.namespace);\n for (const [key, status] of Object.entries(statuses)) {\n this.statusMap.set(key, status);\n }\n\n // Apply draft translations to page blocks that the server didn't render\n // (server only ships is_ready_for_delivery; the editor shows drafts too).\n //\n // Blocks without a server translation are the NORMAL state of any page\n // mid-translation (theme chrome, nav, fresh content) \u2014 not warning-\n // worthy by themselves. But this is also the one spot where a real\n // JS\u2194server key-parity bug would surface, and for untranslated\n // STAMPED blocks we can check it definitively: the element's current\n // markup IS the source (nothing was replaced), so recomputing the\n // auto_ key client-side must reproduce the server's stamp exactly.\n // A mismatch means the two sides hash the same source differently \u2014\n // entity decoding, marker syntax, trim rules \u2014 and every lookup for\n // that block will silently miss. Shout about those; only debug-log\n // the plain untranslated count.\n const divergent: Array<{ stamped: string; recomputed: string; text: string }> = [];\n let untranslatedCount = 0;\n for (const block of blocks) {\n const status = this.statusMap.get(block.key);\n if (status && status.text && !status.isReady) {\n applyLivePreview(block.element, status.text, block.unit.styles, block.key, this.locale, this.namespace, block.unit.text, status.rowState);\n } else if (!status || !status.text) {\n untranslatedCount++;\n const stamp = block.element.getAttribute('data-lr-block');\n if (stamp && stamp.startsWith('auto_')) {\n const recomputed = generateAutoKey(block.unit.text);\n if (recomputed !== stamp) {\n divergent.push({ stamped: stamp, recomputed, text: block.unit.text.slice(0, 80) });\n }\n }\n }\n }\n if (divergent.length > 0) {\n console.warn(\n `[LionRapid Editor] ${divergent.length} block(s) where the client-side auto_ key does NOT ` +\n 'match the server\\'s data-lr-block stamp \u2014 JS and server key generation are diverging ' +\n 'for these inputs. Sample (first 5):',\n divergent.slice(0, 5),\n );\n } else if (untranslatedCount > 0) {\n console.debug(\n `[LionRapid Editor] ${untranslatedCount} block(s) untranslated (no server text yet) \u2014 normal for in-progress pages.`,\n );\n }\n\n this.emitProgress();\n }\n\n getTranslatedCount(): number {\n let count = 0;\n for (const status of this.statusMap.values()) {\n if (status && status.rowState !== 'empty') count++;\n }\n return count;\n }\n\n getTotalCount(): number {\n return this.blockExtractor.extract(document.body, true).length;\n }\n\n private emitProgress(): void {\n if (this.onProgressUpdate) {\n this.onProgressUpdate(this.getTranslatedCount(), this.getTotalCount());\n }\n }\n\n destroy(): void {\n this.el.remove();\n }\n\n private async renderContent(resolved: ResolvedKey): Promise<void> {\n const { key, unit, element } = resolved;\n this.bodyEl.innerHTML = '';\n\n // Smart fetch: if no flush since last full fetch, only fetch the clicked key.\n // Otherwise re-fetch all blocks to pick up server-side changes from our saves.\n const isStale = this.lastFlushTime > this.lastFetchTime || this.lastFetchTime === 0;\n\n if (isStale) {\n const blocks = this.blockExtractor.extract(document.body, true);\n const allKeys = blocks.map((b) => b.key);\n if (!allKeys.includes(key)) allKeys.push(key);\n const freshStatuses = await this.api.fetchStatus(allKeys, this.locale, this.namespace);\n for (const [k, s] of Object.entries(freshStatuses)) {\n this.statusMap.set(k, s);\n }\n this.lastFetchTime = Date.now();\n } else {\n // Fresh \u2014 only fetch the clicked key\n const freshStatus = await this.api.fetchStatus([key], this.locale, this.namespace);\n if (freshStatus[key] !== undefined) {\n this.statusMap.set(key, freshStatus[key]);\n }\n }\n const status = this.statusMap.get(key) ?? null;\n\n // Key field\n this.addField('Key', () => {\n const keyEl = document.createElement('div');\n keyEl.className = 'lr-field-key';\n keyEl.textContent = key;\n return keyEl;\n });\n\n // Original text\n this.addField('Original', () => {\n const origEl = document.createElement('div');\n origEl.className = 'lr-field-original';\n origEl.innerHTML = this.renderTextWithTags(unit.text);\n return origEl;\n });\n\n // If this block is (or contains) a same-origin link \u2014 e.g. a nav menu\n // item \u2014 offer an explicit way to follow it. Clicking page links is\n // intercepted by the inspector (so the drawer can open), so without this\n // the PM has no way to navigate to that page to translate it. Navigation\n // is deliberate (no accidental loss of edits) and the session rides along\n // via the sessionStorage JWT + __lr_editor_active cookie.\n this.renderLinkNav(element);\n\n const isSourceLocale = this.locale === this.defaultLocale;\n const isLocked = isSourceLocale || status?.isLocked || status?.isReady || false;\n // Dashboard parity: rows the caller can't act on render read-only, same as\n // the dashboard row list. Existing row \u2192 server-computed canEdit\n // (can_user_edit_row: stage assignment, postreview, PM override). Missing\n // row \u2192 can_create from targets/status (creation is stage-0 work; the\n // batch save refuses it for e.g. a stage-1 proofreader, so don't offer\n // an edit that can't be saved).\n const cannotEdit = status ? status.canEdit === false : !this.api.canCreateMissing;\n const readOnly = isLocked || cannotEdit;\n\n // Translation textarea\n const textarea = document.createElement('textarea');\n this.addField('Translation', () => {\n textarea.className = 'lr-textarea';\n // Pre-fill: server status > in-DOM translation > empty\n textarea.value = status?.text || resolved.translatedUnit?.text || '';\n textarea.placeholder = unit.text; // Show source as placeholder\n if (readOnly) {\n textarea.readOnly = true;\n textarea.classList.add('lr-textarea-locked');\n textarea.title = isSourceLocale\n ? 'Source language \u2014 switch to a target language to edit'\n : status?.isReady ? 'Approved for delivery \u2014 read only'\n : isLocked ? 'Locked \u2014 read only'\n : status ? 'Assigned to another stage \u2014 read only'\n : 'Not yet translated \u2014 rows are created at the translation stage';\n }\n return textarea;\n });\n\n // Status bar (above preview)\n this.bodyEl.appendChild(this.statusEl);\n\n // Preview\n const previewEl = document.createElement('div');\n this.addField('Preview', () => {\n previewEl.className = 'lr-preview';\n if (textarea.value) {\n previewEl.innerHTML = this.renderPreview(textarea.value, unit.styles);\n }\n return previewEl;\n });\n\n // Workflow actions (Phase 3) \u2014 only on target locales (not source).\n // Visibility per role + state is enforced server-side; we hide buttons\n // the server would refuse so users don't see dead ends.\n if (!isSourceLocale) {\n this.renderWorkflowActions(key, status);\n }\n\n // Live preview on input (only if editable)\n if (!readOnly) {\n textarea.addEventListener('input', () => {\n const text = textarea.value;\n previewEl.innerHTML = this.renderPreview(text, unit.styles);\n if (text) {\n // When the operator types in the textarea the row becomes 'edited'\n // \u2014 pass that so applyLivePreview respects the typed text exactly\n // (no auto-wrap rescue: if they typed plain, they meant plain).\n applyLivePreview(element, text, unit.styles, key, this.locale, this.namespace, unit.text, 'edited');\n }\n this.unsavedKeys.add(key);\n this.api.queueSave({\n key,\n locale: this.locale,\n namespace: this.namespace,\n text,\n styles: unit.styles,\n });\n });\n }\n\n // Page blocks list\n this.renderBlocksList();\n\n // Focus textarea (only if editable)\n if (!readOnly) {\n setTimeout(() => textarea.focus(), 100);\n }\n }\n\n /**\n * Render Confirm/Reject/Mark-Ready/Unmark-Ready buttons under the preview.\n * Visibility:\n * - Confirm: shown when row is not locked and not already ready\n * - Reject: shown when row is not locked and not already ready (server\n * returns 400 if stage_index is already 0)\n * - Unconfirm: shown when isConfirmed\n * - Mark Ready: PM/admin only, hidden when already ready\n * - Unmark Ready: PM/admin only, only when ready\n * After any successful click we re-fetch status for this key so the UI\n * reflects the new state and the unaffected buttons hide/show themselves.\n */\n private renderWorkflowActions(key: string, status: TargetStatus | null | undefined): void {\n const isPM = this.userRole === 'project_manager' || this.userRole === 'admin';\n const isReady = !!status?.isReady;\n const isLocked = !!status?.isLocked;\n const isConfirmed = !!status?.isConfirmed;\n // Server-computed canEdit reflects can_user_edit_row() \u2014 false for\n // rows past the user's stage assignment, postreview rows, etc.\n // Default to true if absent (older backend or unknown \u2014 let the user\n // try; server enforces 403 anyway). Missing rows fall back to the\n // session's create permission (targets/status can_create).\n const canEdit = status ? status.canEdit !== false : this.api.canCreateMissing;\n\n const composite = `${this.locale}:${this.namespace}:${key}`;\n\n const wrap = document.createElement('div');\n wrap.className = 'lr-workflow-actions';\n\n // Workflow context strip \u2014 renders synchronously from cache when present,\n // otherwise inserts a placeholder and back-fills after lazy-fetch resolves.\n // The strip lives at the top of the actions wrap so the user sees 'where\n // am I' before the action buttons / out-of-scope note.\n const stripHost = document.createElement('div');\n stripHost.className = 'lr-wf-strip-host';\n wrap.appendChild(stripHost);\n this.renderWorkflowStrip(stripHost, status);\n\n const addBtn = (\n label: string,\n action: 'confirm' | 'unconfirm' | 'reject' | 'advance' | 'mark-ready' | 'unmark-ready',\n variant: 'primary' | 'secondary' | 'danger' = 'secondary',\n ): void => {\n const btn = document.createElement('button');\n btn.className = `lr-wf-btn lr-wf-${variant}`;\n btn.textContent = label;\n btn.addEventListener('click', async () => {\n btn.disabled = true;\n const result = await this.api.transitionTarget(composite, action);\n btn.disabled = false;\n if (result) {\n // Refresh status for this key so the buttons recompute\n await this.refreshStatusForKey(key);\n }\n });\n wrap.appendChild(btn);\n };\n\n // In-flight MT fill: stage transitions would 423 (TARGET_BUSY) until the\n // pipeline sweep finishes, so swap the buttons for an honest note. Editing\n // itself stays available \u2014 canEdit is the permission truth (the server\n // ignores the transient gate for status) and busy saves queue + retry.\n const fillBusy = this.api.targetBusy;\n if (fillBusy && canEdit && !isLocked && !isReady) {\n const note = document.createElement('span');\n note.className = 'lr-wf-note';\n note.textContent =\n 'Machine translation is filling this page \u2014 edits save automatically; stage actions unlock in a moment.';\n wrap.appendChild(note);\n }\n\n // Translator-side actions: only show when the caller can actually act\n // on the row. Without canEdit gating, a translator sees Confirm on a\n // row they've already pushed past their stage and gets a 403.\n //\n // Two-step flow (Confirm \u2192 Advance) mirrors the dashboard:\n // - Confirm: marks is_confirmed=True at current stage (does NOT move stage)\n // - Advance: bumps stage_index, clears is_confirmed for the next reviewer\n // This keeps Confirm and Advance legible as separate intents on both surfaces.\n if (!fillBusy && canEdit && !isLocked && !isReady) {\n if (!isConfirmed) {\n addBtn('Confirm', 'confirm', 'primary');\n // A stage-0 row has nowhere to be sent back to \u2014 the server refuses\n // the reject (\"already at the first stage\"), so don't offer it.\n // Unknown stageIndex (older backend) stays permissive: server decides.\n if (status?.stageIndex !== 0) {\n addBtn('Reject', 'reject', 'danger');\n }\n } else {\n // Confirmed at current stage \u2014 show Advance (per_row only) and Unconfirm\n // as the next-step options. Hide Advance for per_file workflows (rows\n // advance together as a target from the dashboard) and for rows at/past\n // the workflow's postreview boundary \u2014 the server rejects that advance\n // (\"already past the final stage\"), so don't offer a button that can\n // only 400.\n const isPerFile = this.workflowInfo?.workflowMode === 'per_file';\n const atPostreview = isRowAtPostreview(this.workflowInfo, status?.stageIndex);\n if (!isPerFile && !atPostreview) {\n addBtn('Advance', 'advance', 'primary');\n }\n addBtn('Unconfirm', 'unconfirm');\n if (!isPerFile && atPostreview) {\n const note = document.createElement('span');\n note.className = 'lr-wf-note';\n // A project with no workflow at all (continuous projects may defer\n // choosing one) reads differently from a row parked at final review.\n note.textContent =\n this.workflowInfo && this.workflowInfo.stages.length === 0\n ? 'No translation workflow configured \u2014 set one in project settings to use stages.'\n : 'In final review \u2014 no further stages to advance.';\n wrap.appendChild(note);\n }\n }\n }\n\n // PM-only actions \u2014 PM/admin can always Mark/Unmark, no canEdit gate\n // (PM/admin is_pm_admin override in can_user_edit_row keeps canEdit\n // True for them anyway).\n if (isPM) {\n if (!isReady) {\n addBtn('Mark Ready', 'mark-ready', 'primary');\n } else {\n addBtn('Unmark Ready', 'unmark-ready', 'danger');\n }\n }\n\n // Helpful breadcrumb for translators when they see a row they can't\n // edit \u2014 tells them why no action buttons appeared.\n if (!canEdit && !isPM && !isReady && !isLocked) {\n const note = document.createElement('span');\n note.className = 'lr-wf-note';\n note.textContent =\n typeof status?.stageIndex === 'number' && status.stageIndex > 0\n ? `Row already advanced (stage ${status.stageIndex + 1}) \u2014 out of your scope.`\n : 'You are not assigned to this row at the current stage.';\n wrap.appendChild(note);\n }\n\n if (wrap.children.length > 0) {\n this.bodyEl.appendChild(wrap);\n }\n }\n\n /**\n * Render the workflow context strip ('Workflow: A \u2192 B \u2192 C \u2014 you are at stage X').\n * Highlights the user's POV stage and greys the rest.\n *\n * Highlight rule:\n * - PM/admin: highlight = row's current stage_index (deep-dive view)\n * - Non-PM: highlight = first userAssignedStages entry (fixed POV)\n *\n * Renders immediately from the latest known shape, then revalidates through\n * the API's TTL-bound per-locale cache (cache hits resolve instantly) so a\n * mid-session workflow change or a per-target override is picked up. While\n * the first fetch is in flight, the host stays empty (no flash).\n */\n private renderWorkflowStrip(host: HTMLElement, status: TargetStatus | null | undefined): void {\n if (this.workflowInfo) {\n const node = this.buildWorkflowStripNode(this.workflowInfo, status);\n host.replaceChildren(...(node ? [node] : []));\n }\n void this.api.fetchWorkflowInfo(this.locale).then((info) => {\n // Same object \u21D2 cache hit, nothing changed. null keeps the last known\n // shape rather than blanking the strip on a transient fetch failure.\n if (!info || info === this.workflowInfo) return;\n this.workflowInfo = info;\n // The user may have clicked another row by now. Only render if still\n // on the same key \u2014 re-resolve status from the cache.\n if (!this.current) return;\n const fresh = this.statusMap.get(this.current.key) ?? status ?? null;\n const node = this.buildWorkflowStripNode(info, fresh);\n if (host.isConnected) host.replaceChildren(...(node ? [node] : []));\n });\n }\n\n private buildWorkflowStripNode(\n info: EditorWorkflowInfo,\n status: TargetStatus | null | undefined,\n ): HTMLElement | null {\n if (!info.stages || info.stages.length === 0) return null;\n\n let highlightIndex: number | null = null;\n if (info.isPmAdmin) {\n highlightIndex = typeof status?.stageIndex === 'number' ? status.stageIndex : null;\n } else if (info.userAssignedStages.length > 0) {\n highlightIndex = info.userAssignedStages[0];\n }\n\n const strip = document.createElement('div');\n strip.className = 'lr-wf-strip';\n\n const stagesRow = document.createElement('div');\n stagesRow.className = 'lr-wf-strip-stages';\n info.stages.forEach((stage, i) => {\n const item = document.createElement('span');\n item.className = 'lr-wf-strip-stage';\n if (highlightIndex !== null && stage.index === highlightIndex) {\n item.classList.add('is-active');\n } else {\n item.classList.add('is-muted');\n }\n item.textContent = stage.name;\n stagesRow.appendChild(item);\n if (i < info.stages.length - 1) {\n const sep = document.createElement('span');\n sep.className = 'lr-wf-strip-sep';\n sep.textContent = '\u2192';\n stagesRow.appendChild(sep);\n }\n });\n strip.appendChild(stagesRow);\n\n // Caption: 'You are here \u2014 stage N (Name)' or row position for PM\n const caption = document.createElement('div');\n caption.className = 'lr-wf-strip-caption';\n if (highlightIndex !== null) {\n const stage = info.stages.find((s) => s.index === highlightIndex);\n const name = stage?.name ?? `Stage ${highlightIndex + 1}`;\n const label = info.isPmAdmin ? 'Row currently at' : 'You are here';\n caption.textContent = `${label}: stage ${highlightIndex + 1} of ${info.stages.length} \u2014 ${name}`;\n } else if (!info.isPmAdmin && info.userAssignedStages.length === 0) {\n caption.textContent = 'You have no stage assignment on this project.';\n } else {\n caption.textContent = `${info.stages.length} stages`;\n }\n strip.appendChild(caption);\n\n return strip;\n }\n\n /** Re-fetch status for one key and re-render the current view. */\n private async refreshStatusForKey(key: string): Promise<void> {\n const fresh = await this.api.fetchStatus([key], this.locale, this.namespace);\n for (const [k, s] of Object.entries(fresh)) {\n this.statusMap.set(k, s);\n }\n if (this.current) {\n // Re-render the current key to pick up new status\n this.renderContent(this.current);\n }\n this.emitProgress();\n }\n\n /**\n * Render an \"Open linked page \u2192\" button when the selected block is or\n * contains a same-origin link. Cross-origin links are skipped \u2014 the editor\n * session only applies to this site. No-op when there's no usable link.\n */\n private renderLinkNav(element: Element): void {\n const anchor = (element.matches?.('a[href]')\n ? element\n : element.querySelector?.('a[href]')) as HTMLAnchorElement | null;\n if (!anchor) return;\n\n let dest: URL;\n try {\n dest = new URL(anchor.href, window.location.href);\n } catch {\n return;\n }\n if (dest.origin !== window.location.origin) return;\n // Skip a link that just points at the current page (e.g. \"#\" anchors).\n if (dest.pathname === window.location.pathname && !dest.hash) return;\n\n this.addField('Linked page', () => {\n const btn = document.createElement('button');\n btn.className = 'lr-link-nav';\n btn.textContent = 'Open linked page \u2192';\n btn.title = dest.pathname + dest.search;\n btn.addEventListener('click', () => {\n // Localize so we stay in the operator's current locale (e.g. /es).\n window.location.href = localizeUrl(dest.href, this.defaultLocale);\n });\n return btn;\n });\n }\n\n private addField(label: string, contentFn: () => HTMLElement): void {\n const field = document.createElement('div');\n field.className = 'lr-field';\n const labelEl = document.createElement('span');\n labelEl.className = 'lr-field-label';\n labelEl.textContent = label;\n field.appendChild(labelEl);\n field.appendChild(contentFn());\n this.bodyEl.appendChild(field);\n }\n\n /**\n * Get CSS class for the status dot based on actual row state, with one\n * viewer-relative override: rows that have advanced past the viewer's\n * stage assignment render as 'readonly' (greyed) \u2014 same data for everyone,\n * different signal per viewer. Avoids the new-color-per-stage explosion the\n * user was originally considering; reuses existing semantics.\n */\n private getDotClass(key: string): string {\n if (this.unsavedKeys.has(key)) return 'unsaved';\n const s = this.statusMap.get(key);\n // Missing row: grey it out when this session can't create rows\n // (creation is stage-0 work \u2014 mirrors the batch save's create gate).\n if (!s) return this.api.canCreateMissing ? 'missing' : 'readonly';\n if (s.isLocked) return 'locked';\n // Viewer-relative read-only: row is past my stage and I can't act on it.\n // Sits above ready/confirmed/edited so the dot signals 'hands off' even\n // if the row's underlying state would otherwise be green/orange.\n if (s.canEdit === false && typeof s.stageIndex === 'number' && s.stageIndex > 0) return 'readonly';\n // Green means WOULD ACTUALLY BE SERVED, not merely approved. isReady is\n // approval only: an approved row with an EMPTY target is approved and\n // delivers nothing, and used to show green here. willDeliver is the\n // server's delivery answer (approved-or-locked AND non-empty target AND\n // translatable source AND live target).\n // Fallback is isReady, NOT false: a backend predating willDeliver omits\n // the key, and `?? false` would turn every dot non-ready against such a\n // server. `?? isReady` keeps today's behaviour when absent and is correct\n // when present. Only the DOT moves \u2014 the Mark/Unmark toggle below still\n // reads isReady, because it is the only control that un-approves a row.\n if (s.willDeliver ?? s.isReady) return 'ready';\n if (s.isConfirmed) return 'confirmed';\n if (s.rowState === 'edited') return 'edited';\n if (s.rowState === 'machine_translated') return 'mt';\n return 'missing';\n }\n\n private renderTextWithTags(text: string): string {\n // Highlight indexed tags like <1>, </1>. The surrounding prose is\n // page-controlled (any visitor-visible text, e.g. a hostile comment),\n // and this string goes to innerHTML in the editor realm \u2014 which holds\n // the PM's session JWT \u2014 so everything is escaped; only the marker\n // wrapper spans are real markup.\n const tagPattern = /<(\\/?)?(\\d+)(\\/)?>/g;\n let result = '';\n let lastIndex = 0;\n let match;\n while ((match = tagPattern.exec(text)) !== null) {\n result += escapeHtml(text.slice(lastIndex, match.index));\n result += `<span class=\"lr-field-tag\">${escapeHtml(match[0])}</span>`;\n lastIndex = match.index + match[0].length;\n }\n return result + escapeHtml(text.slice(lastIndex));\n }\n\n private renderPreview(text: string, styles: Record<string, string[]>): string {\n if (!text) return '';\n if (Object.keys(styles).length > 0) {\n const unit: ContentUnit = { text, styles };\n // Serializer output is sanitized (core Phase 2).\n return htmlSerializer.serialize(unit);\n }\n // Plain-text branch: textarea content is user/page-controlled prose.\n return escapeHtml(text);\n }\n\n private initResize(handle: HTMLDivElement): void {\n let startX = 0;\n let startWidth = 0;\n\n const onMouseMove = (e: MouseEvent) => {\n const newWidth = startWidth + (startX - e.clientX);\n const clamped = Math.max(280, Math.min(600, newWidth));\n this.el.style.width = `${clamped}px`;\n };\n\n const onMouseUp = () => {\n handle.classList.remove('active');\n document.removeEventListener('mousemove', onMouseMove);\n document.removeEventListener('mouseup', onMouseUp);\n };\n\n handle.addEventListener('mousedown', (e) => {\n e.preventDefault();\n startX = e.clientX;\n startWidth = this.el.offsetWidth;\n handle.classList.add('active');\n document.addEventListener('mousemove', onMouseMove);\n document.addEventListener('mouseup', onMouseUp);\n });\n }\n\n private refreshBlocksList(): void {\n const existing = this.bodyEl.querySelector('.lr-blocks-section');\n if (existing) existing.remove();\n this.renderBlocksList();\n }\n\n private renderBlocksList(): void {\n const section = document.createElement('div');\n section.className = 'lr-blocks-section';\n\n const blocks = this.blockExtractor.extract(document.body, true);\n const translatedCount = this.getTranslatedCount();\n\n const title = document.createElement('div');\n title.className = 'lr-blocks-title';\n title.textContent = `Blocks on this page (${translatedCount}/${blocks.length} translated)`;\n section.appendChild(title);\n\n for (const block of blocks) {\n const item = document.createElement('div');\n item.className = 'lr-block-item';\n\n const isActive = this.current && block.element === this.current.element;\n if (isActive) item.classList.add('active');\n\n // Status dot\n const dot = document.createElement('span');\n dot.className = 'lr-status-dot';\n dot.classList.add(this.getDotClass(block.key));\n item.appendChild(dot);\n\n // Source text\n const textWrap = document.createElement('div');\n textWrap.className = 'lr-block-text';\n const displayText = block.unit.text.replace(/<\\/?[0-9]+\\/?>/g, '');\n const sourceSpan = document.createElement('div');\n sourceSpan.className = 'lr-block-source';\n sourceSpan.textContent = displayText.length > 60 ? displayText.slice(0, 60) + '...' : displayText;\n sourceSpan.title = displayText;\n textWrap.appendChild(sourceSpan);\n\n // Translation preview (if exists)\n const blockStatus = this.statusMap.get(block.key);\n if (blockStatus && blockStatus.text) {\n const previewSpan = document.createElement('div');\n previewSpan.className = 'lr-block-preview';\n const previewText = blockStatus.text.replace(/<\\/?[0-9]+\\/?>/g, '');\n previewSpan.textContent = previewText.length > 50 ? previewText.slice(0, 50) + '...' : previewText;\n textWrap.appendChild(previewSpan);\n }\n\n item.appendChild(textWrap);\n\n item.addEventListener('click', () => {\n block.element.scrollIntoView({ behavior: 'smooth', block: 'center' });\n this.onBlockClick({ key: block.key, unit: block.unit, translatedUnit: null, element: block.element });\n });\n\n section.appendChild(item);\n }\n\n this.bodyEl.appendChild(section);\n }\n}\n", "/**\n * PII Redactor \u2014 fine-grained inline replacement of PII patterns in text.\n *\n * Phase 4 of the snapshot feature. Phase 1 redacts whole text nodes only;\n * this module replaces matches *inline*, preserving surrounding prose:\n * \"Contact us at foo@bar.com for help\" \u2192 \"Contact us at [REDACTED] for help\"\n *\n * Two entry points:\n * - `redactString(text)` \u2014 pure string-in/string-out, used in tests and\n * callable independently if a non-DOM caller ever needs it.\n * - `redactTextNodes(root)` \u2014 walks every text node under `root` and\n * replaces PII inline, skipping subtrees marked with `data-no-redact`,\n * and skipping `<code>` / `<pre>` (legitimate examples).\n *\n * Design notes:\n * - Patterns are tuned to avoid common false positives. SSN requires\n * dashes/spaces (so 9-digit order numbers survive); credit cards must\n * pass a Luhn check (so 16-digit tracking IDs survive); IPs reject any\n * octet > 255 (so version strings like 1.2.3.999 survive).\n * - We bias toward under-redaction in ambiguous cases \u2014 better to leak\n * a tracking number than corrupt a customer's real prose.\n */\n\nconst REDACTED = '[REDACTED]';\n\n// ----- Regex catalog ---------------------------------------------------------\n//\n// Every pattern uses the `g` flag so .replace() can hit multiple matches in\n// one string. Anchors (`\\b` or surrounding-char checks) are tuned per pattern.\n\n// Email: standard local@domain.tld, fairly permissive on the local part.\nconst EMAIL_RE =\n /[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,}/gi;\n\n// US/intl phone: optional `+1 ` prefix, optional parens around area code,\n// 3-3-4 digit groups separated by space/dash/dot. Requires at least one\n// separator so bare 10-digit numbers don't match (those are usually IDs).\nconst PHONE_RE =\n /(?:\\+\\d{1,3}[\\s.-]?)?(?:\\(\\d{3}\\)|\\d{3})[\\s.-]\\d{3}[\\s.-]\\d{4}/g;\n\n// SSN: XXX-XX-XXXX, dashes or spaces (NOT bare 9 digits \u2014 too many false\n// positives on order numbers, IDs, etc.).\nconst SSN_RE = /\\b\\d{3}[\\s-]\\d{2}[\\s-]\\d{4}\\b/g;\n\n// IPv4: four 1-3 digit octets, dot-separated. We post-filter for \"all\n// octets <= 255\" so version strings like 1.2.3.999 don't match.\nconst IPV4_RE = /\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b/g;\n\n// URL: http(s):// or www. then non-whitespace. Greedy through path/query.\nconst URL_RE = /\\b(?:https?:\\/\\/|www\\.)[^\\s<>\"']+/gi;\n\n// Credit card candidate: 13-19 digits in 4-4-4-4 (or contiguous) format,\n// allowing dashes/spaces between groups. Luhn-checked before redacting.\n// Must end on a digit (not a separator) \u2014 prevents greedy matching of\n// trailing whitespace.\nconst CC_CANDIDATE_RE = /\\b\\d(?:[ -]?\\d){12,18}\\b/g;\n\n// ----- Luhn check ------------------------------------------------------------\n\nfunction luhnValid(digits: string): boolean {\n // digits already stripped of separators by caller\n if (digits.length < 13 || digits.length > 19) return false;\n let sum = 0;\n let alt = false;\n for (let i = digits.length - 1; i >= 0; i--) {\n let n = digits.charCodeAt(i) - 48; // '0' = 48\n if (n < 0 || n > 9) return false;\n if (alt) {\n n *= 2;\n if (n > 9) n -= 9;\n }\n sum += n;\n alt = !alt;\n }\n return sum % 10 === 0;\n}\n\n// ----- IPv4 octet bounds check ----------------------------------------------\n\nfunction ipv4Valid(match: string): boolean {\n const parts = match.split('.');\n if (parts.length !== 4) return false;\n for (const p of parts) {\n const n = Number(p);\n if (!Number.isInteger(n) || n < 0 || n > 255) return false;\n }\n return true;\n}\n\n// ----- Public: string redactor ----------------------------------------------\n\n/**\n * Replace every PII match in `text` with `[REDACTED]`. Surrounding prose\n * is preserved.\n *\n * Order matters: URLs first (so `http://...` doesn't get mangled by IPv4\n * matching), then emails, then more specific numeric patterns.\n */\nexport function redactString(text: string): string {\n if (!text) return text;\n let out = text;\n\n out = out.replace(URL_RE, REDACTED);\n out = out.replace(EMAIL_RE, REDACTED);\n out = out.replace(PHONE_RE, REDACTED);\n out = out.replace(SSN_RE, REDACTED);\n out = out.replace(IPV4_RE, (m) => (ipv4Valid(m) ? REDACTED : m));\n out = out.replace(CC_CANDIDATE_RE, (m) => {\n const digits = m.replace(/[\\s-]/g, '');\n return luhnValid(digits) ? REDACTED : m;\n });\n\n return out;\n}\n\n// ----- Public: DOM walker ---------------------------------------------------\n\n/**\n * Tag names whose text we never touch \u2014 code samples, preformatted blocks,\n * and `<style>` content. `<script>` is already removed earlier in the\n * snapshot pipeline, so it's not in this list.\n */\nconst SKIP_TAGS = new Set(['CODE', 'PRE', 'STYLE', 'TEXTAREA']);\n\n/**\n * Walks every text node under `root` and redacts PII inline. Skips:\n * - text nodes inside any ancestor with `[data-no-redact]`\n * - text nodes inside `<code>`, `<pre>`, `<style>`, `<textarea>`\n *\n * Mutates the DOM in place.\n */\nexport function redactTextNodes(root: Element): void {\n const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);\n // Collect first, mutate after \u2014 walker behaviour with mutation is\n // implementation-defined.\n const toUpdate: { node: Text; value: string }[] = [];\n\n let node = walker.nextNode() as Text | null;\n while (node) {\n if (!isInSkippedSubtree(node)) {\n const original = node.nodeValue || '';\n const replaced = redactString(original);\n if (replaced !== original) {\n toUpdate.push({ node, value: replaced });\n }\n }\n node = walker.nextNode() as Text | null;\n }\n\n for (const { node: textNode, value } of toUpdate) {\n textNode.nodeValue = value;\n }\n}\n\nfunction isInSkippedSubtree(node: Node): boolean {\n let cur: Node | null = node.parentNode;\n while (cur && cur.nodeType === Node.ELEMENT_NODE) {\n const el = cur as Element;\n if (SKIP_TAGS.has(el.tagName)) return true;\n if (el.hasAttribute('data-no-redact')) return true;\n cur = el.parentNode;\n }\n return false;\n}\n", "/**\n * Snapshot Capture \u2014 Serializes the live DOM with PII redaction.\n *\n * Used by the visual editor to let an authenticated admin capture an auth page\n * so a translator (without credentials) can later translate it against a\n * frozen copy. The serialized HTML is uploaded to the server and retrieved in\n * viewer mode (?__lr_snapshot={uuid}) where Inspector + Sidebar boot against\n * the snapshot root instead of document.body.\n *\n * Redaction strategy:\n * - Remove: <script>, <noscript>, <iframe>, <embed>, <object>, editor host\n * - Clear all form <input>, <textarea>, <select> values\n * - Replace [data-pii] / [data-sensitive] text with [REDACTED]\n * - Strip all inline event handlers (onclick, onmouseover, etc.)\n * - Inline PII redaction in text nodes (email / phone / URL / SSN /\n * credit card / IPv4) \u2014 see pii-redactor.ts. `[data-no-redact]`\n * subtrees, `<code>`, and `<pre>` are skipped.\n * - Strip CSRF/CSP meta tags and auth tokens from link/script URLs\n */\n\nimport { BlockExtractor } from '../block-extractor.js';\nimport { redactTextNodes } from './pii-redactor.js';\n\nconst REDACTED = '[REDACTED]';\n\nexport interface SnapshotTextEntry {\n /** auto_xxxxxxxxxxxx \u2014 12-char md5 hash, generated BEFORE redaction so it matches the live page */\n key: string;\n /** The source tag name (P, H1, LI, etc.) \u2014 useful for future analytics */\n tagName: string;\n /** Index of this block among all block elements in the snapshot DOM. Used by\n * the viewer to map a clicked element back to its pre-computed key without\n * re-running BlockExtractor on the redacted text. */\n blockIndex: number;\n}\n\nexport interface SnapshotViewport {\n width: number;\n height: number;\n devicePixelRatio: number;\n}\n\nexport interface CapturedSnapshot {\n html: string;\n textEntries: SnapshotTextEntry[];\n pageUrl: string;\n title: string;\n viewport: SnapshotViewport;\n}\n\n/**\n * Capture the current page as a redacted snapshot.\n *\n * Pure function \u2014 does NOT mutate the live DOM. Clones first, then:\n * 1. Extracts block keys BEFORE redaction (so auto_keys match the live page)\n * 2. Redacts the clone (strips PII, scripts, forms, etc.)\n * 3. Stamps each block element with a data-lr-snap-idx attribute so the\n * viewer can map a click back to the pre-computed key\n * 4. Serializes to HTML\n *\n * The textEntries carry pre-computed keys (no source text) and the HTML is\n * safe to store. The viewer uses textEntries to look up the correct key by\n * index when the translator clicks a block.\n */\nexport function captureSnapshot(): CapturedSnapshot {\n // 1. Clone the entire document element.\n const clone = document.documentElement.cloneNode(true) as HTMLElement;\n\n // 2. Extract blocks BEFORE redaction so keys match the live page.\n // The real text is used for md5 hashing, producing the same auto_keys\n // the edge proxy / auto-init generates on the live page.\n const textEntries = extractTextEntries(clone);\n\n // 3. Stamp each extracted block with a data attribute linking it to its\n // entry index. The viewer uses this to resolve clicks to pre-computed\n // keys without re-running BlockExtractor on the redacted text.\n // (extractTextEntries stores the element reference so we can stamp it)\n\n // 4. Redact the clone. Order: remove dangerous elements first so we don't\n // waste time walking their subtrees.\n removeDangerousElements(clone);\n clearFormValues(clone);\n redactSensitiveElements(clone);\n stripInlineEventHandlers(clone);\n redactTextNodePII(clone);\n stripSensitiveMetaAndHeaders(clone);\n\n // 5. Serialize.\n const html = '<!DOCTYPE html>\\n' + clone.outerHTML;\n\n return {\n html,\n textEntries,\n pageUrl: window.location.href,\n title: document.title,\n viewport: {\n width: window.innerWidth,\n height: window.innerHeight,\n devicePixelRatio: window.devicePixelRatio || 1,\n },\n };\n}\n\n/**\n * Remove elements that should never appear in a snapshot:\n * - <script>, <noscript>: could re-execute or leak source\n * - <iframe>, <embed>, <object>: third-party content, likely auth-bound\n * - #__lionrapid-editor: the editor's own shadow host\n * - <link rel=\"prefetch\"> / <link rel=\"preload\"> that may leak auth URLs\n */\nfunction removeDangerousElements(root: HTMLElement): void {\n const selectors = [\n 'script',\n 'noscript',\n 'iframe',\n 'embed',\n 'object',\n '#__lionrapid-editor',\n 'link[rel=\"prefetch\"]',\n 'link[rel=\"preload\"]',\n 'link[rel=\"modulepreload\"]',\n ];\n for (const selector of selectors) {\n const matches = root.querySelectorAll(selector);\n for (let i = 0; i < matches.length; i++) {\n matches[i].remove();\n }\n }\n}\n\n/**\n * Clear all form field values so no user input leaks.\n * This covers <input> (all types), <textarea>, and <select>.\n */\nfunction clearFormValues(root: HTMLElement): void {\n const inputs = root.querySelectorAll('input, textarea, select');\n for (let i = 0; i < inputs.length; i++) {\n const el = inputs[i] as HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;\n\n // Clear live value\n if ('value' in el) el.value = '';\n\n // Clear the serialized attribute \u2014 cloneNode copies the attribute,\n // not the .value property, so we need to touch both.\n if (el.hasAttribute('value')) el.setAttribute('value', '');\n\n // <textarea> content lives in text children, not an attribute\n if (el.tagName === 'TEXTAREA') el.textContent = '';\n\n // Clear option selection on <select>\n if (el.tagName === 'SELECT') {\n const options = el.querySelectorAll('option');\n for (let j = 0; j < options.length; j++) {\n (options[j] as HTMLOptionElement).selected = false;\n options[j].removeAttribute('selected');\n }\n }\n\n // Remove data-* attributes that often hold state\n const attrs = Array.from(el.attributes);\n for (const attr of attrs) {\n if (attr.name.startsWith('data-value') || attr.name === 'data-initial') {\n el.removeAttribute(attr.name);\n }\n }\n }\n}\n\n/**\n * Replace the text content of elements flagged as sensitive with [REDACTED].\n * Preserves the element itself so layout stays intact.\n */\nfunction redactSensitiveElements(root: HTMLElement): void {\n const sensitive = root.querySelectorAll('[data-pii], [data-sensitive]');\n for (let i = 0; i < sensitive.length; i++) {\n const el = sensitive[i] as HTMLElement;\n el.textContent = REDACTED;\n // Strip all attributes except structural ones so classes/ids still position it.\n const attrs = Array.from(el.attributes);\n for (const attr of attrs) {\n if (attr.name !== 'class' && attr.name !== 'id' && attr.name !== 'style') {\n el.removeAttribute(attr.name);\n }\n }\n }\n}\n\n/**\n * Walk every element and strip any attribute that starts with 'on' (onclick,\n * onmouseover, onload, etc.). These could re-execute in the viewer.\n */\nfunction stripInlineEventHandlers(root: HTMLElement): void {\n const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);\n let node = walker.nextNode() as HTMLElement | null;\n while (node) {\n const attrs = Array.from(node.attributes);\n for (const attr of attrs) {\n if (attr.name.toLowerCase().startsWith('on')) {\n node.removeAttribute(attr.name);\n }\n }\n node = walker.nextNode() as HTMLElement | null;\n }\n}\n\n/**\n * Walk text nodes and replace PII matches inline. Phase 4: matches are\n * replaced where they appear (\"Contact foo@bar.com today\" \u2192\n * \"Contact [REDACTED] today\") instead of redacting the whole text node.\n * See pii-redactor.ts for pattern details and the data-no-redact escape\n * hatch.\n */\nfunction redactTextNodePII(root: HTMLElement): void {\n redactTextNodes(root);\n}\n\n/**\n * Strip meta tags and link/script URLs that commonly carry auth material.\n */\nfunction stripSensitiveMetaAndHeaders(root: HTMLElement): void {\n // CSRF / CSP nonces\n const sensitiveMetas = root.querySelectorAll(\n 'meta[name=\"csrf-token\"], meta[name=\"csp-nonce\"], meta[name=\"csrf-param\"], meta[http-equiv=\"set-cookie\"]',\n );\n for (let i = 0; i < sensitiveMetas.length; i++) {\n sensitiveMetas[i].remove();\n }\n\n // Strip token-bearing query params from <link> and <a href> URLs\n const TOKEN_PARAMS = ['token', 'auth', 'access_token', 'session', 'sid', 'key', 'api_key', 'apikey'];\n const urlElements = root.querySelectorAll('link[href], a[href], img[src]');\n for (let i = 0; i < urlElements.length; i++) {\n const el = urlElements[i] as HTMLLinkElement | HTMLAnchorElement | HTMLImageElement;\n const attr = el.tagName === 'IMG' ? 'src' : 'href';\n const raw = el.getAttribute(attr);\n if (!raw) continue;\n try {\n const url = new URL(raw, window.location.href);\n let changed = false;\n for (const param of TOKEN_PARAMS) {\n if (url.searchParams.has(param)) {\n url.searchParams.set(param, REDACTED);\n changed = true;\n }\n }\n if (changed) el.setAttribute(attr, url.toString());\n } catch {\n // Not a parseable URL (mailto:, tel:, javascript:, etc.) \u2014 skip\n }\n }\n}\n\n/**\n * Run BlockExtractor on the clone BEFORE redaction to produce text entries\n * with content-hash keys that match the live page. Each extracted block\n * element is stamped with `data-lr-snap-idx` so the viewer can map a click\n * back to the pre-computed key without re-running BlockExtractor on the\n * (now redacted) text.\n *\n * No source text is included in the returned entries \u2014 only the key, tag\n * name, and block index \u2014 so the entries are safe to store even though\n * extraction happened before redaction.\n */\nfunction extractTextEntries(clone: HTMLElement): SnapshotTextEntry[] {\n const extractor = new BlockExtractor(\n { emails: true, urls: true, numbers: true, phones: true },\n [],\n 2,\n );\n const blocks = extractor.extract(clone);\n\n return blocks.map((block, index) => {\n // Stamp the element so the viewer can resolve clicks by data attribute\n block.element.setAttribute('data-lr-snap-idx', String(index));\n\n return {\n key: block.key,\n tagName: block.element.tagName,\n blockIndex: index,\n };\n });\n}\n", "/**\n * DOM-tree sanitizer \u2014 the live-tree companion to the pure-string\n * `content/sanitizer/HTMLSanitizer`. Used where a whole fragment of\n * untrusted HTML becomes live DOM: today the snapshot viewer, which mounts\n * server-fetched snapshot HTML in the editor realm (PM session JWT).\n *\n * POLICY NOTE \u2014 this deliberately diverges from the inline `SAFE_TAGS`\n * allowlist (see PHASES.md Phase 4): a snapshot is a full captured page, so\n * structural elements (`div`/`p`/`h1`/`nav`/`table`/\u2026) must survive for the\n * page to render at all. What is enforced instead:\n * - executable/embedding elements are REMOVED outright\n * - every `on*` handler attribute (and `srcdoc`) is dropped\n * - URL-bearing attributes must pass `sanitizeUrl`\n * - inline `style` keeps any property (layout fidelity) but value-level\n * dangers are dropped via `sanitizeCssDeclarations`\n *\n * CONTINUOUS DEVELOPMENT: like SAFE_STYLE_PROPS, these sets grow (or\n * tighten) deliberately as real snapshots surface gaps \u2014 extend REMOVE_TAGS\n * rather than trusting a new embedding surface, add hostile cases to\n * sanitize-element.test.ts, and keep the WP/edge ports in sync if they ever\n * grow a snapshot feature.\n */\n\nimport {\n sanitizeUrl,\n sanitizeCssDeclarations,\n} from '../content/sanitizer/HTMLSanitizer.js';\n\n/** Elements that can execute, embed, or retarget \u2014 removed with their subtree. */\nconst REMOVE_TAGS = new Set([\n 'script', 'iframe', 'frame', 'frameset', 'object', 'embed', 'applet',\n 'base', 'meta', 'link', 'template', 'portal',\n]);\n\n/** Attributes whose values navigate or fetch \u2014 must pass sanitizeUrl. */\nconst URL_ATTRS = new Set([\n 'href', 'src', 'action', 'formaction', 'poster', 'data', 'xlink:href',\n]);\n\n/**\n * Sanitize a live element tree in place (root included).\n */\nexport function sanitizeElement(root: Element): void {\n // Snapshot the list first \u2014 removals during iteration are safe this way.\n const all = [root, ...Array.from(root.querySelectorAll('*'))];\n for (const el of all) {\n const tag = el.tagName.toLowerCase();\n if (REMOVE_TAGS.has(tag)) {\n el.remove();\n continue;\n }\n for (const name of el.getAttributeNames()) {\n const lower = name.toLowerCase();\n if (lower.startsWith('on') || lower === 'srcdoc') {\n el.removeAttribute(name);\n continue;\n }\n if (URL_ATTRS.has(lower)) {\n if (sanitizeUrl(el.getAttribute(name) ?? '') === null) {\n el.removeAttribute(name);\n }\n continue;\n }\n if (lower === 'style') {\n const clean = sanitizeCssDeclarations(el.getAttribute(name) ?? '');\n if (clean === null) el.removeAttribute(name);\n else el.setAttribute(name, clean);\n }\n }\n }\n}\n\n/**\n * Best-effort sanitization of a copied `<style>` sheet's text: strips\n * `@import` (arbitrary stylesheet pull-in), neutralizes `url(...)`\n * references that resolve cross-origin (exfil beacons in a tampered\n * snapshot), and defuses legacy `expression(`. Same-origin, relative, and\n * `data:` URLs are kept so the snapshot still renders its images/fonts.\n * The primary defenses are element removal + attribute scrubbing in\n * `sanitizeElement`; this closes the stylesheet side channel.\n */\nexport function sanitizeStyleSheetText(css: string, baseHref: string): string {\n let out = css.replace(/@import[^;]*(;|$)/gi, '');\n out = out.replace(\n /url\\(\\s*(['\"]?)([^'\")]*)\\1\\s*\\)/gi,\n (full, _quote: string, target: string) => {\n try {\n const resolved = new URL(target, baseHref);\n if (resolved.protocol === 'data:') return full;\n if (resolved.origin === new URL(baseHref).origin) return full;\n } catch {\n /* unparseable target \u2192 neutralize */\n }\n return 'url(\"\")';\n },\n );\n return out.replace(/expression\\s*\\(/gi, 'blocked(');\n}\n", "/**\n * Snapshot Viewer \u2014 Renders a saved snapshot in the current page so the\n * Inspector + Sidebar can be pointed at it for translation.\n *\n * When the editor loads with `?__lr_snapshot={uuid}`, the normal page is\n * replaced with the snapshot HTML inside a sandbox wrapper:\n *\n * <div id=\"__lr_snapshot_root\"> \u2190 scoped root passed to Inspector\n * <html>...snapshot...</html>\n * </div>\n *\n * Auto-init retranslation is NOT re-run against the snapshot \u2014 translations\n * saved via the sidebar flow through the normal batch endpoint and will\n * appear on the real auth page on next load.\n */\n\nimport type { EditorAPI } from './editor-api.js';\nimport type { SnapshotTextEntry } from './snapshot-capture.js';\nimport { sanitizeElement, sanitizeStyleSheetText } from '../sanitizer.js';\n\nexport interface SnapshotViewerResult {\n /** Scope root to pass to Inspector */\n root: HTMLElement;\n /** Snapshot metadata for display in the toolbar / sidebar */\n pageUrl: string;\n title: string;\n /** Pre-computed key map: blockIndex \u2192 key. The Inspector uses this to resolve\n * clicks without re-running BlockExtractor on the redacted HTML. */\n keyMap: Map<number, string>;\n}\n\n/**\n * Fetch a snapshot from the server and mount it into the current page.\n * Returns the root element Inspector should scope against.\n *\n * On failure (404, network error, deleted snapshot) returns null and displays\n * a user-visible notice via the caller's notice helper.\n */\nexport async function activateSnapshotViewer(\n snapshotUuid: string,\n api: EditorAPI,\n): Promise<SnapshotViewerResult | null> {\n const snapshot = await api.getSnapshot(snapshotUuid);\n if (!snapshot) return null;\n\n // Replace the entire body with a sandbox wrapper holding the snapshot HTML.\n // We keep document.head alone so the editor's Shadow DOM host and existing\n // stylesheets (for rendering) still work.\n //\n // The snapshot HTML is a full document (starts with <!DOCTYPE>), so we\n // parse it with DOMParser and extract the <body> children. The snapshot's\n // <head> <link> and <style> tags are appended to the real head so the\n // snapshot renders with its original styles.\n const parser = new DOMParser();\n const doc = parser.parseFromString(snapshot.html, 'text/html');\n\n // Snapshot HTML is server-fetched and must be treated as tamperable \u2014\n // it mounts in the editor realm, which holds the PM's session JWT.\n // Strip executable elements / handlers / dangerous URLs before any of it\n // touches the live document (core Phase 4).\n sanitizeElement(doc.body);\n\n // Copy over stylesheets and inline <style> tags from the snapshot head.\n // We prefix them with a data attribute so the cleanup code can remove them\n // if the viewer is ever unmounted (out of scope for Phase 1).\n // Cross-origin <link> stylesheets are dropped (a tampered snapshot could\n // pull attacker CSS); inline <style> text is scrubbed of @import and\n // cross-origin url() beacons.\n const snapshotStyles = doc.head.querySelectorAll('link[rel=\"stylesheet\"], style');\n for (let i = 0; i < snapshotStyles.length; i++) {\n const source = snapshotStyles[i];\n if (source.tagName.toLowerCase() === 'link') {\n const href = source.getAttribute('href') ?? '';\n let sameOrigin = false;\n try {\n sameOrigin =\n new URL(href, window.location.href).origin === window.location.origin;\n } catch {\n /* unparseable href \u2192 drop */\n }\n if (!sameOrigin) continue;\n const clone = source.cloneNode(true) as HTMLElement;\n clone.setAttribute('data-lr-snapshot-style', '');\n document.head.appendChild(clone);\n } else {\n const style = document.createElement('style');\n style.textContent = sanitizeStyleSheetText(\n source.textContent ?? '',\n window.location.href,\n );\n style.setAttribute('data-lr-snapshot-style', '');\n document.head.appendChild(style);\n }\n }\n\n // Copy <title> so the tab title reflects the captured page\n if (snapshot.title) {\n document.title = `[Snapshot] ${snapshot.title}`;\n }\n\n // Clear the current body content (but preserve the editor's shadow host\n // if it's already mounted \u2014 it's attached to #__lionrapid-editor).\n const editorHost = document.getElementById('__lionrapid-editor');\n while (document.body.firstChild) {\n if (document.body.firstChild === editorHost) {\n // Skip past the editor host without removing it\n const next = document.body.firstChild.nextSibling;\n if (!next) break;\n document.body.removeChild(next);\n } else {\n document.body.removeChild(document.body.firstChild);\n }\n }\n\n // Build sandbox wrapper and inject snapshot body\n const root = document.createElement('div');\n root.id = '__lr_snapshot_root';\n root.setAttribute('data-lr-snapshot-uuid', snapshotUuid);\n root.innerHTML = doc.body.innerHTML;\n\n // Insert wrapper before the editor host so the editor stays on top\n if (editorHost && editorHost.parentNode === document.body) {\n document.body.insertBefore(root, editorHost);\n } else {\n document.body.appendChild(root);\n }\n\n // Build key map from textEntries: blockIndex \u2192 auto_key\n const keyMap = new Map<number, string>();\n const entries: SnapshotTextEntry[] = snapshot.textEntries || [];\n for (const entry of entries) {\n keyMap.set(entry.blockIndex, entry.key);\n }\n\n return {\n root,\n pageUrl: snapshot.pageUrl,\n title: snapshot.title,\n keyMap,\n };\n}\n", "/**\n * Visual Translation Editor \u2014 Entry Point\n * Self-executing IIFE bundle loaded by edge proxy or auto-init when ?__lr_editor is detected.\n *\n * Reads script tag attributes, polls for window.LionRapid, creates Shadow DOM,\n * and instantiates Inspector, Sidebar, and Toolbar.\n *\n * Auth flow: grant token \u2192 exchange for scoped session JWT \u2192 use JWT for API calls.\n */\n\nimport { EDITOR_CSS } from './editor-styles.js';\nimport { DEFAULT_API_BASE_URL } from '../../config/api.config.js';\nimport { Inspector } from './inspector.js';\nimport { Toolbar } from './toolbar.js';\nimport { Sidebar } from './sidebar.js';\nimport { EditorAPI } from './editor-api.js';\nimport type { ResolvedKey } from './key-resolver.js';\nimport { captureSnapshot } from './snapshot-capture.js';\nimport { activateSnapshotViewer } from './snapshot-viewer.js';\nimport { BlockExtractor } from '../block-extractor.js';\nimport { localizeUrl } from './locale-url.js';\n\n// Console diagnostic. Attached at module load time so it's available the\n// instant the editor bundle parses \u2014 before any grant exchange, before any\n// async setup. Sidebar overrides this later with a richer version that also\n// reports each block's translation status.\n//\n// Usage in DevTools: __lr_dump_keys()\nif (typeof window !== 'undefined') {\n (window as unknown as Record<string, unknown>).__lr_dump_keys = () => {\n const extractor = new BlockExtractor();\n const blocks = extractor.extract(document.body, true);\n const rows = blocks.map((b, i) => ({\n i,\n key: b.key,\n text: b.unit.text,\n stylesJson: JSON.stringify(b.unit.styles),\n }));\n console.table(rows);\n return rows;\n };\n}\n\n// ============================================\n// Session helpers\n// ============================================\n\nconst SESSION_KEY = '__lr_editor_session';\n\ninterface StoredSession {\n token: string;\n exp: number;\n}\n\n/**\n * Session-scoped cookie the edge proxy reads to decide whether to fetch\n * draft translations (bypassing the delivery gate). Set in lockstep with\n * the sessionStorage JWT so a live editor session always implies preview.\n *\n * Not sensitive on its own \u2014 it carries no auth material, just a signal.\n * The actual auth is the JWT sent via Authorization headers from this JS.\n */\nconst PREVIEW_COOKIE = '__lr_editor_active';\n\nfunction setPreviewCookie(): void {\n // No Max-Age / Expires \u2192 session cookie, clears when the browser/tab\n // closes. Matches the lifetime of sessionStorage so both signals go\n // away together.\n document.cookie = `${PREVIEW_COOKIE}=1; path=/; SameSite=Lax`;\n}\n\nfunction clearPreviewCookie(): void {\n document.cookie = `${PREVIEW_COOKIE}=; path=/; SameSite=Lax; Max-Age=0`;\n}\n\nfunction getStoredSession(): string | null {\n try {\n const raw = sessionStorage.getItem(SESSION_KEY);\n if (!raw) return null;\n const parsed: StoredSession = JSON.parse(raw);\n // Check if expired (with 30s buffer)\n if (parsed.exp * 1000 < Date.now() + 30_000) {\n sessionStorage.removeItem(SESSION_KEY);\n clearPreviewCookie();\n return null;\n }\n // Re-stamp the preview cookie on every load \u2014 covers the case where\n // the user closed and reopened the tab inside the sessionStorage TTL,\n // but the session cookie expired with the previous tab.\n setPreviewCookie();\n return parsed.token;\n } catch {\n return null;\n }\n}\n\nfunction storeSession(token: string): void {\n try {\n // Parse exp from JWT payload (es_<header>.<payload>.<sig>)\n const raw = token.startsWith('es_') ? token.slice(3) : token;\n const parts = raw.split('.');\n if (parts.length !== 3) return;\n const payload = JSON.parse(atob(parts[1]));\n const session: StoredSession = { token, exp: payload.exp || 0 };\n sessionStorage.setItem(SESSION_KEY, JSON.stringify(session));\n setPreviewCookie();\n } catch {\n // Non-critical \u2014 session just won't persist across navigations\n }\n}\n\nfunction clearSession(): void {\n sessionStorage.removeItem(SESSION_KEY);\n clearPreviewCookie();\n}\n\n/**\n * Decode the role claim from a signed editor session JWT.\n * Used for client-side UI gating (show/hide Capture button etc.) \u2014 the\n * server enforces the same check on every protected endpoint.\n */\nfunction extractSessionRole(token: string): string {\n try {\n const raw = token.startsWith('es_') ? token.slice(3) : token;\n const parts = raw.split('.');\n if (parts.length !== 3) return '';\n const payload = JSON.parse(atob(parts[1]));\n return typeof payload.role === 'string' ? payload.role : '';\n } catch {\n return '';\n }\n}\n\n// ============================================\n// User-visible notice (shown when editor can't activate)\n// ============================================\n\nfunction showEditorNotice(message: string): void {\n const el = document.createElement('div');\n el.setAttribute('data-no-translate', '');\n Object.assign(el.style, {\n position: 'fixed', bottom: '24px', right: '24px', zIndex: '2147483647',\n background: '#1a1a2e', color: '#fff', padding: '16px 20px',\n borderRadius: '8px', fontFamily: 'system-ui, sans-serif', fontSize: '14px',\n maxWidth: '360px', boxShadow: '0 4px 20px rgba(0,0,0,0.3)',\n });\n el.textContent = message;\n const close = document.createElement('button');\n Object.assign(close.style, {\n position: 'absolute', top: '4px', right: '8px',\n background: 'none', border: 'none', color: '#888', cursor: 'pointer', fontSize: '16px',\n });\n close.textContent = '\\u00d7';\n close.addEventListener('click', () => el.remove());\n el.appendChild(close);\n document.body.appendChild(el);\n setTimeout(() => el.remove(), 8000);\n}\n\n// ============================================\n// Init\n// ============================================\n\nfunction initEditor(): void {\n // Support two activation modes:\n // 1. Grant mode \u2014 script tag has data-grant (first-page activation)\n // 2. Session mode \u2014 no grant, but a valid session exists in sessionStorage\n // (subsequent page navigation after grant was consumed)\n //\n // The edge proxy injects the editor script on every page when editor.enabled\n // is true. On pages where there's no active session, the editor silently\n // exits \u2014 no FAB, no network calls.\n const scriptEl =\n (document.querySelector('script[data-grant]') as HTMLScriptElement | null) ||\n (document.querySelector('script[data-lr-editor]') as HTMLScriptElement | null);\n\n if (!scriptEl) {\n // No editor script tag at all \u2014 nothing to do\n return;\n }\n\n const grant = scriptEl.dataset.grant || '';\n const hasValidSession = getStoredSession() !== null;\n\n // Session mode requires an existing stored session; grant mode requires a grant\n if (!grant && !hasValidSession) {\n // No grant and no session \u2014 silently exit (normal visitor, not an editor user)\n return;\n }\n\n const locale = scriptEl.dataset.locale || '';\n const apiBase = scriptEl.dataset.api || DEFAULT_API_BASE_URL;\n const projectId = scriptEl.dataset.project || '';\n\n // Poll for window.LionRapid (auto-init loads first)\n let attempts = 0;\n const maxAttempts = 100; // 10s at 100ms intervals\n\n const poll = setInterval(() => {\n attempts++;\n const lr = window.LionRapid;\n\n // Duck-type: during auto-init's boot window (or under a WP/edge library\n // bundle) window.LionRapid can hold a module-exports namespace, not the\n // instance. Only the instance has getCurrentLocale.\n if (lr && typeof lr.getCurrentLocale === 'function') {\n clearInterval(poll);\n activate(lr, grant, locale, apiBase, projectId);\n } else if (attempts >= maxAttempts) {\n clearInterval(poll);\n console.warn('[LionRapid Editor] Timed out waiting for window.LionRapid');\n }\n }, 100);\n}\n\nasync function activate(\n lr: NonNullable<typeof window.LionRapid>,\n grant: string,\n locale: string,\n apiBase: string,\n projectId: string,\n): Promise<void> {\n const config = window.__lionrapidConfig;\n const languages = config?.languages || [locale];\n const namespace = config?.namespace || 'default';\n const defaultLocale = config?.defaultLocale || 'en';\n const currentLocale = locale || lr.getCurrentLocale();\n const normalizedApiBase = apiBase.replace(/\\/$/, '');\n\n // --- Session acquisition ---\n let sessionToken = getStoredSession();\n\n if (!sessionToken && grant) {\n // Exchange grant for session JWT\n try {\n const res = await fetch(`${normalizedApiBase}/api/editor/exchange`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ grant, domain: window.location.host }),\n });\n if (!res.ok) {\n console.error('[LionRapid Editor] Grant exchange failed:', res.status);\n showEditorNotice('Editor session expired. Please reopen the editor from the dashboard.');\n return;\n }\n const data = await res.json();\n sessionToken = data.session_token;\n if (sessionToken) {\n storeSession(sessionToken);\n }\n } catch (err) {\n console.error('[LionRapid Editor] Grant exchange error:', err);\n showEditorNotice('Could not connect to LionRapid. Please check your connection and try again.');\n return;\n }\n }\n\n if (!sessionToken) {\n console.error('[LionRapid Editor] No valid session. Please reopen the editor from the dashboard.');\n showEditorNotice('Editor session expired. Please reopen the editor from the dashboard.');\n return;\n }\n\n // Decode the session JWT to extract the user's role. The server signs this\n // token and enforces permissions \u2014 the client-side check here is only to\n // avoid showing actions the server would reject (defense in depth). Never\n // trust client-side role checks for security decisions.\n const role = extractSessionRole(sessionToken);\n const canCaptureSnapshots = role === 'project_manager' || role === 'admin';\n\n // Create Shadow DOM host\n const host = document.createElement('div');\n host.id = '__lionrapid-editor';\n host.setAttribute('data-no-translate', '');\n document.body.appendChild(host);\n const shadow = host.attachShadow({ mode: 'closed' });\n\n // Inject styles\n const styleEl = document.createElement('style');\n styleEl.textContent = EDITOR_CSS;\n shadow.appendChild(styleEl);\n\n // Hide the proxy/auto-init language switcher while editor is active\n const switcherEl = document.querySelector('[data-lionrapid-switcher]') as HTMLElement | null;\n if (switcherEl) switcherEl.style.display = 'none';\n\n // Create API client with session token\n const api = new EditorAPI(normalizedApiBase, sessionToken, projectId);\n api.startAutoFlush();\n\n // --- Snapshot viewer mode ---\n // If the URL has ?__lr_snapshot={uuid}, replace the page with the saved\n // snapshot HTML and scope the Inspector to the sandbox wrapper. Capture\n // button is hidden in viewer mode (a translator shouldn't re-capture).\n //\n // Separately, ?__lr_recapture={uuid} runs on the LIVE page (no viewer)\n // but switches the FAB's Capture button into re-capture mode \u2014 PUT to the\n // existing snapshot uuid instead of POST.\n const currentUrl = new URL(window.location.href);\n const snapshotUuid = currentUrl.searchParams.get('__lr_snapshot');\n const recaptureUuid = currentUrl.searchParams.get('__lr_recapture');\n let inspectorRoot: Element | undefined;\n let snapshotKeyMap: Map<number, string> | undefined;\n if (snapshotUuid) {\n const result = await activateSnapshotViewer(snapshotUuid, api);\n if (!result) {\n showEditorNotice('Snapshot not found or expired. Please check the dashboard.');\n return;\n }\n inspectorRoot = result.root;\n snapshotKeyMap = result.keyMap;\n }\n\n // Create sidebar (needs api + locale)\n const handleBlockSelect = (resolved: ResolvedKey) => {\n sidebar.open(resolved);\n inspector.clearHighlight();\n };\n\n const sidebar = new Sidebar(shadow, api, currentLocale, namespace, defaultLocale, handleBlockSelect, role);\n\n // Create inspector (hover/click \u2192 sidebar)\n const inspector = new Inspector(shadow, handleBlockSelect, inspectorRoot, snapshotKeyMap);\n\n // Create toolbar\n const toolbar = new Toolbar(\n shadow,\n languages,\n currentLocale,\n // Language change\n async (newLocale) => {\n // Edge proxy context: navigate to locale URL instead of client-side translation\n const isEdgeProxy = document.querySelector('script[data-mode=\"dynamic-only\"]') !== null;\n if (isEdgeProxy) {\n const url = new URL(window.location.href);\n // Strip existing locale prefix if present\n const stripped = url.pathname.replace(/^\\/[a-z]{2}(\\/|$)/, '/');\n // Build new URL: /{locale}/path (no locale prefix for the default).\n const prefix = newLocale === defaultLocale ? '' : `/${newLocale}`;\n url.pathname = prefix + (stripped === '/' && prefix ? '' : stripped);\n // Don't re-append ?__lr_editor \u2014 by now the session is established and\n // carried by the __lr_editor_active cookie (the server re-injects the\n // editor on every page from it). The grant is single-use and already\n // consumed; appending an empty one only confused the server's editor\n // detection and dropped the FAB after a language switch.\n window.location.href = url.toString();\n return;\n }\n sidebar.updateLocale(newLocale);\n sidebar.close();\n inspector.clearHighlight();\n await lr.changeLanguage(newLocale);\n await sidebar.hydrateStatus();\n },\n // Exit\n async () => {\n await api.stopAndFlush();\n inspector.destroy();\n toolbar.destroy();\n sidebar.destroy();\n host.remove();\n clearSession();\n },\n // Capture Snapshot \u2014 only available in live-page mode (not viewer) AND\n // only for PM/admin roles. Translators and proofreaders cannot capture;\n // the server enforces the same check on POST /api/editor/snapshots.\n //\n // If ?__lr_recapture={uuid} is present, the same button re-captures that\n // existing snapshot (PUT) instead of creating a new one (POST).\n snapshotUuid || !canCaptureSnapshots\n ? null\n : async () => {\n try {\n const snapshot = captureSnapshot();\n if (recaptureUuid) {\n const result = await api.recaptureSnapshot(recaptureUuid, snapshot);\n if (result) {\n showEditorNotice(\n `Re-captured as v${result.version} (${snapshot.textEntries.length} blocks). You can close this tab.`,\n );\n } else {\n showEditorNotice('Re-capture failed. Please try again.');\n }\n } else {\n const result = await api.createSnapshot(snapshot);\n if (result) {\n showEditorNotice(\n `Snapshot captured (${snapshot.textEntries.length} blocks). Open it from the Snapshots tab in the dashboard.`,\n );\n } else {\n showEditorNotice('Snapshot upload failed. Please try again.');\n }\n }\n } catch (err) {\n console.error('[LionRapid Editor] Snapshot capture error:', err);\n showEditorNotice('Snapshot capture failed. See console for details.');\n }\n },\n // When in recapture mode, surface it to the toolbar so the button label\n // can change from \"Capture\" to \"Save re-capture\".\n recaptureUuid ? { mode: 'recapture' } : undefined,\n // Browse-mode toggle. Suspending the inspector lets the PM click the\n // site's own links to reach another page; the session rides along via the\n // sessionStorage JWT + __lr_editor_active cookie, so the editor re-activates\n // on the destination. Disabled in snapshot viewer mode (static sandbox,\n // nothing to navigate to).\n snapshotUuid\n ? null\n : (browsing: boolean) => {\n inspector.setActive(!browsing);\n if (browsing) sidebar.close();\n },\n );\n\n // Wire progress counter to sidebar status\n sidebar.setProgressCallback((translated, total) => {\n toolbar.updateProgress(translated, total);\n });\n\n // Locale-preserving link interceptor. On /es/, internal links like\n // /about/ would naively navigate to the default-language page; this\n // rewrites them to /es/about/ so the operator stays in the locale they\n // were browsing. Runs in BUBBLE phase so the inspector (capture phase)\n // gets first say \u2014 if the inspector preventDefault'd a block click, this\n // never fires. New-tab modifiers and editor-UI clicks are left alone.\n document.addEventListener('click', (e) => {\n if (e.defaultPrevented) return;\n if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;\n if ((e as MouseEvent).button !== 0) return;\n const target = e.target as Element | null;\n if (!target?.closest) return;\n if (target.closest('#__lionrapid-editor')) return;\n const a = target.closest('a[href]') as HTMLAnchorElement | null;\n if (!a) return;\n if (a.target && a.target !== '_self') return;\n if (a.hasAttribute('download')) return;\n const localized = localizeUrl(a.href, defaultLocale);\n if (localized !== a.href) {\n e.preventDefault();\n window.location.href = localized;\n }\n });\n\n // Hydrate status from server and update progress\n sidebar.hydrateStatus().then(() => {\n toolbar.updateProgress(sidebar.getTranslatedCount(), sidebar.getTotalCount());\n });\n\n // Clean editor params from URL\n const url = new URL(window.location.href);\n let dirty = false;\n if (url.searchParams.has('__lr_editor')) {\n url.searchParams.delete('__lr_editor');\n dirty = true;\n }\n if (url.searchParams.has('__lr_snapshot')) {\n url.searchParams.delete('__lr_snapshot');\n dirty = true;\n }\n if (url.searchParams.has('__lr_recapture')) {\n url.searchParams.delete('__lr_recapture');\n dirty = true;\n }\n if (dirty) {\n history.replaceState(null, '', url.toString());\n }\n}\n\n// Self-execute\ninitEditor();\n"],
5
+ "mappings": ";;;;;;;AAKO,MAAM,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA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",
6
6
  "names": ["BLOCK_SELECTOR", "BLOCK_SELECTOR", "SKIP_TAGS", "htmlSerializer", "SKIP_TAGS", "REDACTED", "URL_ATTRS", "url"]
7
7
  }