annotask 0.0.6 → 0.0.8

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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/plugin/bridge-client.ts"],"sourcesContent":["/**\n * Client bridge script injected into the user's app.\n * Communicates with the Annotask shell via postMessage.\n * Handles all DOM interactions: hover, click, style reads/writes,\n * element resolution, layout scanning, etc.\n *\n * Returned as an inline string (same pattern as toggle-button.ts).\n */\nexport function bridgeClientScript(): string {\n return `\n(function() {\n // Don't run inside the Annotask shell\n if (window.location.pathname.startsWith('/__annotask')) return;\n // Don't run if already initialized\n if (window.__ANNOTASK_BRIDGE__) return;\n window.__ANNOTASK_BRIDGE__ = true;\n\n // ── Element Registry ──────────────────────────────────\n var eidCounter = 0;\n var eidMap = new Map(); // eid string → WeakRef<Element>\n var elToEid = new WeakMap(); // Element → eid string\n\n function getEid(el) {\n if (!el) return null;\n var existing = elToEid.get(el);\n if (existing) return existing;\n eidCounter++;\n var eid = 'e-' + eidCounter;\n eidMap.set(eid, new WeakRef(el));\n elToEid.set(el, eid);\n return eid;\n }\n\n function getEl(eid) {\n var ref = eidMap.get(eid);\n return ref ? ref.deref() || null : null;\n }\n\n // ── PostMessage Helpers ───────────────────────────────\n var shellOrigin = '*'; // Will be tightened on first shell message\n\n function sendToShell(type, payload, id) {\n var msg = { type: type, payload: payload || {}, source: 'annotask-client' };\n if (id) msg.id = id;\n window.parent.postMessage(msg, shellOrigin);\n }\n\n function respond(id, payload) {\n sendToShell(null, payload, id);\n // type is not needed for responses — shell matches by id\n }\n\n // ── Source Element Resolution ─────────────────────────\n function hasSourceAttr(el) {\n return el.hasAttribute && (el.hasAttribute('data-annotask-file') || el.hasAttribute('data-astro-source-file'));\n }\n\n function findSourceElement(el) {\n var c = el;\n while (c) {\n if (hasSourceAttr(c)) return { sourceEl: c, targetEl: el };\n c = c.parentElement;\n }\n return { sourceEl: el, targetEl: el };\n }\n\n function getSourceData(el) {\n // Prefer data-annotask-* attributes, fall back to data-astro-source-* (Astro framework)\n var file = el.getAttribute('data-annotask-file') || '';\n var line = el.getAttribute('data-annotask-line') || '';\n var component = el.getAttribute('data-annotask-component') || '';\n\n if (!file && el.getAttribute('data-astro-source-file')) {\n var astroFile = el.getAttribute('data-astro-source-file') || '';\n // Convert absolute path to project-relative by finding src/ prefix\n var srcIdx = astroFile.indexOf('/src/');\n file = srcIdx !== -1 ? astroFile.slice(srcIdx + 1) : astroFile;\n }\n if ((!line || line === '0') && el.getAttribute('data-astro-source-loc')) {\n // data-astro-source-loc format: \"line:col\"\n line = (el.getAttribute('data-astro-source-loc') || '').split(':')[0];\n }\n if (!component && file) {\n // Derive component name from file path\n var parts = file.split('/');\n var fileName = parts[parts.length - 1] || '';\n component = fileName.replace(/\\.[^.]+$/, '');\n }\n\n var mfe = el.getAttribute('data-annotask-mfe') || '';\n\n return { file: file, line: line, component: component, mfe: mfe };\n }\n\n function getRect(el) {\n var r = el.getBoundingClientRect();\n return { x: r.x, y: r.y, width: r.width, height: r.height };\n }\n\n // ── Interaction Mode ──────────────────────────────────\n var storedMode = '';\n try { storedMode = localStorage.getItem('annotask:mode') || ''; } catch(e) {}\n var currentMode = storedMode || 'select';\n var inspectModes = { select: true, pin: true };\n\n // ── Event Handlers ────────────────────────────────────\n var lastHoverEid = null;\n var rafPending = false;\n var pendingHoverData = null;\n\n function onMouseOver(e) {\n if (!inspectModes[currentMode]) return;\n var el = e.target;\n if (!el || el === document.documentElement || el === document.body) return;\n var eid = getEid(el);\n if (eid === lastHoverEid) return;\n lastHoverEid = eid;\n\n var source = findSourceElement(el);\n var data = getSourceData(source.sourceEl);\n\n pendingHoverData = {\n eid: eid,\n tag: el.tagName.toLowerCase(),\n file: data.file,\n component: data.component,\n rect: getRect(el)\n };\n\n if (!rafPending) {\n rafPending = true;\n requestAnimationFrame(function() {\n rafPending = false;\n if (pendingHoverData) sendToShell('hover:enter', pendingHoverData);\n });\n }\n }\n\n function onMouseOut(e) {\n if (!inspectModes[currentMode]) return;\n // Only fire leave when truly leaving all elements\n if (e.relatedTarget && e.relatedTarget !== document.documentElement) return;\n lastHoverEid = null;\n pendingHoverData = null;\n sendToShell('hover:leave', {});\n }\n\n function onClick(e) {\n if (!inspectModes[currentMode]) return;\n e.preventDefault();\n e.stopPropagation();\n\n var el = e.target;\n if (!el) return;\n\n var source = findSourceElement(el);\n var data = getSourceData(source.sourceEl);\n var targetEl = source.targetEl;\n var classes = typeof targetEl.className === 'string' ? targetEl.className : '';\n\n sendToShell('click:element', {\n eid: getEid(targetEl),\n sourceEid: getEid(source.sourceEl),\n file: data.file,\n line: data.line,\n component: data.component,\n mfe: data.mfe,\n tag: targetEl.tagName.toLowerCase(),\n classes: classes,\n rect: getRect(targetEl),\n shiftKey: e.shiftKey,\n clientX: e.clientX,\n clientY: e.clientY\n });\n }\n\n function onMouseDown(e) {\n if (!inspectModes[currentMode]) return;\n e.stopPropagation();\n }\n\n function onMouseUp(e) {\n if (currentMode !== 'highlight') return;\n var sel = document.getSelection();\n var text = sel ? sel.toString().trim() : '';\n if (!text || text.length < 2) return;\n var anchorEl = sel.anchorNode ? sel.anchorNode.parentElement : null;\n if (!anchorEl) return;\n var source = findSourceElement(anchorEl);\n var data = getSourceData(source.sourceEl);\n\n sendToShell('selection:text', {\n text: text,\n eid: getEid(anchorEl),\n file: data.file,\n line: parseInt(data.line) || 0,\n component: data.component,\n mfe: data.mfe,\n tag: anchorEl.tagName.toLowerCase()\n });\n }\n\n function onContextMenu(e) {\n if (currentMode === 'interact') return;\n e.preventDefault();\n var el = e.target;\n if (!el) return;\n var source = findSourceElement(el);\n var data = getSourceData(source.sourceEl);\n var targetEl = source.targetEl;\n var classes = typeof targetEl.className === 'string' ? targetEl.className : '';\n\n sendToShell('contextmenu:element', {\n eid: getEid(targetEl),\n sourceEid: getEid(source.sourceEl),\n file: data.file,\n line: data.line,\n component: data.component,\n mfe: data.mfe,\n tag: targetEl.tagName.toLowerCase(),\n classes: classes,\n rect: getRect(targetEl),\n shiftKey: false,\n clientX: e.clientX,\n clientY: e.clientY\n });\n }\n\n function onKeyDown(e) {\n if ((e.ctrlKey || e.metaKey) && e.key === 'z' && !e.shiftKey) {\n e.preventDefault();\n sendToShell('keydown', { key: e.key, ctrlKey: e.ctrlKey, metaKey: e.metaKey, shiftKey: e.shiftKey });\n }\n }\n\n // Install event listeners\n document.addEventListener('mouseover', onMouseOver, { capture: true });\n document.addEventListener('mouseout', onMouseOut, { capture: true });\n document.addEventListener('mousedown', onMouseDown, { capture: true });\n document.addEventListener('click', onClick, { capture: true });\n document.addEventListener('mouseup', onMouseUp, { capture: true });\n document.addEventListener('keydown', onKeyDown, { capture: true });\n document.addEventListener('contextmenu', onContextMenu, { capture: true });\n\n // ── User Action Tracking (interact mode) ───────────────\n // Tracks meaningful page actions (link/button clicks) so the LLM\n // can understand what the user did to reach the current state.\n\n var lastActionTs = 0;\n function onUserAction(e) {\n var el = e.target;\n if (!el || !el.closest) return;\n\n // Debounce — ignore clicks within 50ms (same gesture)\n var now = Date.now();\n if (now - lastActionTs < 50) return;\n lastActionTs = now;\n\n // Find the best description of what was clicked\n var actionEl = el.closest('a, button, [role=\"button\"], [role=\"tab\"], [role=\"menuitem\"], [role=\"link\"], [type=\"submit\"], tr[class*=\"row\"], [data-pc-section=\"bodyrow\"]');\n var descEl = actionEl || el;\n\n var tag = descEl.tagName.toLowerCase();\n // Get concise text: prefer innerText of the immediate target, fall back to closest actionable\n var text = '';\n if (el.innerText) text = el.innerText.trim();\n if (!text && descEl.innerText) text = descEl.innerText.trim();\n text = text.split(String.fromCharCode(10))[0].substring(0, 60);\n\n var href = '';\n if (tag === 'a') href = descEl.getAttribute('href') || '';\n else if (el.closest('a')) href = el.closest('a').getAttribute('href') || '';\n\n // Get component context if available\n var source = findSourceElement(descEl);\n var data = getSourceData(source.sourceEl);\n\n sendToShell('user:action', {\n tag: tag,\n text: text,\n href: href,\n component: data.component || '',\n });\n }\n\n document.addEventListener('click', onUserAction, { capture: true });\n\n // ── Route Tracking ────────────────────────────────────\n var lastRoute = window.location.pathname;\n\n function checkRoute() {\n var path = window.location.pathname;\n if (path !== lastRoute) {\n lastRoute = path;\n sendToShell('route:changed', { path: path });\n }\n }\n\n window.addEventListener('popstate', checkRoute);\n window.addEventListener('hashchange', checkRoute);\n setInterval(checkRoute, 2000); // safety net for pushState\n\n // ── Message Handler ───────────────────────────────────\n window.addEventListener('message', function(event) {\n var msg = event.data;\n if (!msg || msg.source !== 'annotask-shell') return;\n // Tighten origin on first shell message\n if (shellOrigin === '*' && event.origin) shellOrigin = event.origin;\n\n var type = msg.type;\n var payload = msg.payload || {};\n var id = msg.id;\n\n // ── Mode ──\n if (type === 'mode:set') {\n currentMode = payload.mode || 'select';\n try { localStorage.setItem('annotask:mode', currentMode); } catch(e) {}\n return;\n }\n\n // ── Ping ──\n if (type === 'bridge:ping') {\n respond(id, {});\n return;\n }\n\n // ── Element Resolution ──\n if (type === 'resolve:at-point') {\n var el = document.elementFromPoint(payload.x, payload.y);\n if (!el || el === document.documentElement || el === document.body) {\n respond(id, null);\n return;\n }\n var src = findSourceElement(el);\n var srcData = getSourceData(src.sourceEl);\n respond(id, {\n eid: getEid(src.targetEl),\n file: srcData.file,\n line: srcData.line,\n component: srcData.component,\n mfe: srcData.mfe,\n tag: src.sourceEl.tagName.toLowerCase(),\n rect: getRect(src.sourceEl),\n classes: typeof src.targetEl.className === 'string' ? src.targetEl.className : ''\n });\n return;\n }\n\n if (type === 'resolve:template-group') {\n var all = document.querySelectorAll(\n '[data-annotask-file=\"' + payload.file + '\"][data-annotask-line=\"' + payload.line + '\"]'\n );\n // Also check Astro source attributes\n if (all.length === 0 && payload.file && payload.line) {\n // Try matching by astro source attributes (absolute path ends with file, loc starts with line)\n var astroAll = document.querySelectorAll('[data-astro-source-file]');\n var matched = [];\n for (var ai = 0; ai < astroAll.length; ai++) {\n var af = astroAll[ai].getAttribute('data-astro-source-file') || '';\n var al = (astroAll[ai].getAttribute('data-astro-source-loc') || '').split(':')[0];\n if (af.endsWith('/' + payload.file) && al === payload.line) matched.push(astroAll[ai]);\n }\n if (matched.length > 0) all = matched;\n }\n var eids = [];\n var rects = [];\n for (var i = 0; i < all.length; i++) {\n if (all[i].tagName.toLowerCase() === payload.tagName) {\n eids.push(getEid(all[i]));\n rects.push(getRect(all[i]));\n }\n }\n respond(id, { eids: eids, rects: rects });\n return;\n }\n\n if (type === 'resolve:rect') {\n var rEl = getEl(payload.eid);\n respond(id, rEl ? { rect: getRect(rEl) } : null);\n return;\n }\n\n if (type === 'resolve:rects') {\n var results = [];\n for (var j = 0; j < payload.eids.length; j++) {\n var re = getEl(payload.eids[j]);\n results.push(re ? getRect(re) : null);\n }\n respond(id, { rects: results });\n return;\n }\n\n // ── Style Operations ──\n if (type === 'style:get-computed') {\n var sEl = getEl(payload.eid);\n if (!sEl) { respond(id, { styles: {} }); return; }\n var cs = window.getComputedStyle(sEl);\n var styles = {};\n for (var k = 0; k < payload.properties.length; k++) {\n styles[payload.properties[k]] = cs.getPropertyValue(payload.properties[k]);\n }\n respond(id, { styles: styles });\n return;\n }\n\n if (type === 'style:apply') {\n var aEl = getEl(payload.eid);\n if (!aEl) { respond(id, { before: '' }); return; }\n var before = window.getComputedStyle(aEl).getPropertyValue(payload.property);\n aEl.style.setProperty(payload.property, payload.value);\n respond(id, { before: before });\n return;\n }\n\n if (type === 'style:apply-batch') {\n var befores = [];\n for (var m = 0; m < payload.eids.length; m++) {\n var bEl = getEl(payload.eids[m]);\n if (bEl) {\n befores.push(window.getComputedStyle(bEl).getPropertyValue(payload.property));\n bEl.style.setProperty(payload.property, payload.value);\n } else {\n befores.push('');\n }\n }\n respond(id, { befores: befores });\n return;\n }\n\n if (type === 'style:undo') {\n var uEl = getEl(payload.eid);\n if (uEl) {\n if (payload.value) uEl.style.setProperty(payload.property, payload.value);\n else uEl.style.removeProperty(payload.property);\n }\n respond(id, {});\n return;\n }\n\n if (type === 'class:get') {\n var cEl = getEl(payload.eid);\n respond(id, { classes: cEl ? (typeof cEl.className === 'string' ? cEl.className : '') : '' });\n return;\n }\n\n if (type === 'class:set') {\n var csEl = getEl(payload.eid);\n if (!csEl) { respond(id, { before: '' }); return; }\n var classBefore = typeof csEl.className === 'string' ? csEl.className : '';\n csEl.className = payload.classes;\n respond(id, { before: classBefore });\n return;\n }\n\n if (type === 'class:set-batch') {\n var classBefores = [];\n for (var n = 0; n < payload.eids.length; n++) {\n var cbEl = getEl(payload.eids[n]);\n if (cbEl) {\n classBefores.push(typeof cbEl.className === 'string' ? cbEl.className : '');\n cbEl.className = payload.classes;\n } else {\n classBefores.push('');\n }\n }\n respond(id, { befores: classBefores });\n return;\n }\n\n if (type === 'class:undo') {\n var cuEl = getEl(payload.eid);\n if (cuEl) cuEl.className = payload.classes;\n respond(id, {});\n return;\n }\n\n // ── Classification ──\n if (type === 'classify:element') {\n var clEl = getEl(payload.eid);\n if (!clEl) { respond(id, null); return; }\n var tag = clEl.tagName.toLowerCase();\n var clCs = window.getComputedStyle(clEl);\n var display = clCs.display;\n var childCount = clEl.children.length;\n var isFlex = display.includes('flex');\n var isGrid = display.includes('grid');\n var semanticContainers = ['section','main','aside','nav','header','footer','article'];\n var role = 'content';\n if (clEl.hasAttribute('data-annotask-component')) {\n var comp = clEl.getAttribute('data-annotask-component') || '';\n if (comp && comp[0] === comp[0].toUpperCase() && comp[0] !== comp[0].toLowerCase()) {\n role = 'component';\n }\n }\n if (role !== 'component' && (isFlex || isGrid) && childCount > 0) role = 'container';\n if (role !== 'component' && role !== 'container' && semanticContainers.indexOf(tag) >= 0 && childCount > 0) role = 'container';\n\n respond(id, {\n role: role,\n display: display,\n isFlexContainer: isFlex,\n isGridContainer: isGrid,\n flexDirection: isFlex ? clCs.flexDirection : undefined,\n childCount: childCount,\n isComponentUnit: role === 'component'\n });\n return;\n }\n\n if (type === 'resolve:element-context') {\n var ctxEl = getEl(payload.eid);\n if (!ctxEl) { respond(id, null); return; }\n\n // Ancestors: walk up 3 levels, capture layout-relevant styles\n var ancestors = [];\n var cur = ctxEl.parentElement;\n for (var ai = 0; ai < 3 && cur && cur !== document.body && cur !== document.documentElement; ai++) {\n var aCs = window.getComputedStyle(cur);\n var aTag = cur.tagName.toLowerCase();\n var aClasses = typeof cur.className === 'string' ? cur.className.trim() : '';\n var aData = getSourceData(cur);\n var ancestor = { tag: aTag, display: aCs.display };\n if (aClasses) ancestor.classes = aClasses;\n if (aData.component) ancestor.component = aData.component;\n if (aCs.display.includes('flex')) {\n ancestor.flexDirection = aCs.flexDirection;\n if (aCs.gap && aCs.gap !== 'normal') ancestor.gap = aCs.gap;\n ancestor.childCount = cur.children.length;\n }\n if (aCs.display.includes('grid')) {\n ancestor.gridTemplateColumns = aCs.gridTemplateColumns;\n ancestor.gridTemplateRows = aCs.gridTemplateRows;\n if (aCs.gap && aCs.gap !== 'normal') ancestor.gap = aCs.gap;\n ancestor.childCount = cur.children.length;\n }\n if (aCs.overflow && aCs.overflow !== 'visible') ancestor.overflow = aCs.overflow;\n ancestors.push(ancestor);\n cur = cur.parentElement;\n }\n\n // Subtree: simplified HTML of the element and its children (3 levels deep)\n function describeEl(el, depth) {\n var t = el.tagName.toLowerCase();\n var cl = typeof el.className === 'string' ? el.className.trim() : '';\n var txt = '';\n // Only get direct text (not from children)\n for (var ci = 0; ci < el.childNodes.length; ci++) {\n if (el.childNodes[ci].nodeType === 3) {\n var nt = el.childNodes[ci].textContent.trim();\n if (nt) { txt = nt.substring(0, 40); break; }\n }\n }\n var node = { tag: t };\n if (cl) node.classes = cl;\n if (txt) node.text = txt;\n if (depth < 3 && el.children.length > 0) {\n var kids = [];\n for (var ki = 0; ki < el.children.length && ki < 10; ki++) {\n kids.push(describeEl(el.children[ki], depth + 1));\n }\n node.children = kids;\n if (el.children.length > 10) node.childrenTruncated = el.children.length;\n }\n return node;\n }\n\n respond(id, {\n ancestors: ancestors,\n subtree: describeEl(ctxEl, 0)\n });\n return;\n }\n\n // ── Accessibility Scan ──\n if (type === 'a11y:scan') {\n var a11yEid = payload.eid;\n var a11yEl = a11yEid ? getEl(a11yEid) : document.body;\n if (!a11yEl) { respond(id, { violations: [] }); return; }\n\n function runAxeScan(target) {\n if (!window.axe) { respond(id, { violations: [], error: 'axe not loaded' }); return; }\n window.axe.run(target, { resultTypes: ['violations'] }, function(err, results) {\n if (err) { respond(id, { violations: [], error: err.message }); return; }\n var violations = [];\n for (var vi = 0; vi < results.violations.length; vi++) {\n var v = results.violations[vi];\n var nodeDetails = [];\n for (var ni = 0; ni < v.nodes.length && ni < 5; ni++) {\n var node = v.nodes[ni];\n var detail = {\n html: (node.html || '').substring(0, 200),\n target: node.target ? node.target[0] : '',\n failureSummary: node.failureSummary || ''\n };\n // Try to resolve annotask source for this element\n var selector = node.target ? node.target[0] : null;\n if (selector) {\n try {\n var domEl = document.querySelector(selector);\n if (domEl) {\n var src = findSourceElement(domEl);\n var srcData = getSourceData(src.sourceEl);\n if (srcData.file) {\n detail.file = srcData.file;\n detail.line = srcData.line;\n detail.component = srcData.component;\n }\n }\n } catch(e) {}\n }\n nodeDetails.push(detail);\n }\n violations.push({\n id: v.id,\n impact: v.impact,\n description: v.description,\n help: v.help,\n helpUrl: v.helpUrl,\n nodes: v.nodes.length,\n elements: nodeDetails\n });\n }\n respond(id, { violations: violations });\n });\n }\n\n if (window.axe) {\n runAxeScan(a11yEl);\n } else {\n var script = document.createElement('script');\n script.src = '/__annotask/vendor/axe-core.min.js';\n // Hide AMD define so UMD bundle uses the global assignment branch (SystemJS compat)\n var savedDefine;\n if (typeof window.define === 'function' && window.define.amd) {\n savedDefine = window.define;\n window.define = undefined;\n }\n script.onload = function() {\n if (savedDefine !== undefined) window.define = savedDefine;\n runAxeScan(a11yEl);\n };\n script.onerror = function() {\n if (savedDefine !== undefined) window.define = savedDefine;\n respond(id, { violations: [], error: 'failed to load axe-core — run: pnpm build:vendor' });\n };\n document.head.appendChild(script);\n }\n return;\n }\n\n // ── Screenshot Capture ──\n if (type === 'screenshot:capture') {\n var clipRect = payload.rect;\n\n function doCapture(clip) {\n if (!window.html2canvas) {\n respond(id, { error: 'html2canvas not loaded' });\n return;\n }\n var opts = { useCORS: true, logging: false, allowTaint: true };\n if (clip) {\n opts.x = clip.x;\n opts.y = clip.y;\n opts.width = clip.width;\n opts.height = clip.height;\n }\n window.html2canvas(document.body, opts).then(function(canvas) {\n var dataUrl = canvas.toDataURL('image/png');\n respond(id, { dataUrl: dataUrl });\n }).catch(function(err) {\n respond(id, { error: err.message || 'capture failed' });\n });\n }\n\n if (window.html2canvas) {\n doCapture(clipRect);\n } else {\n var h2cScript = document.createElement('script');\n h2cScript.src = '/__annotask/vendor/html2canvas.min.js';\n // Hide AMD define so UMD bundle uses the global assignment branch (SystemJS compat)\n var savedDefine2;\n if (typeof window.define === 'function' && window.define.amd) {\n savedDefine2 = window.define;\n window.define = undefined;\n }\n h2cScript.onload = function() {\n if (savedDefine2 !== undefined) window.define = savedDefine2;\n doCapture(clipRect);\n };\n h2cScript.onerror = function() {\n if (savedDefine2 !== undefined) window.define = savedDefine2;\n respond(id, { error: 'failed to load html2canvas — run: pnpm build:vendor' });\n };\n document.head.appendChild(h2cScript);\n }\n return;\n }\n\n // ── Layout Scan ──\n if (type === 'layout:scan') {\n var layoutResults = [];\n var allEls = document.querySelectorAll('*');\n for (var li = 0; li < allEls.length; li++) {\n var lEl = allEls[li];\n if (lEl.nodeType !== 1) continue;\n var lCs = window.getComputedStyle(lEl);\n var lDisplay = lCs.display;\n if (!lDisplay.includes('flex') && !lDisplay.includes('grid')) continue;\n var lRect = lEl.getBoundingClientRect();\n if (lRect.width < 20 || lRect.height < 20) continue;\n var lIsGrid = lDisplay.includes('grid');\n var entry = {\n eid: getEid(lEl),\n display: lIsGrid ? 'grid' : 'flex',\n direction: lIsGrid ? 'grid' : lCs.flexDirection,\n rect: { x: lRect.x, y: lRect.y, width: lRect.width, height: lRect.height },\n columnGap: parseFloat(lCs.columnGap) || 0,\n rowGap: parseFloat(lCs.rowGap) || 0\n };\n if (lIsGrid) {\n var cols = lCs.gridTemplateColumns;\n var rows = lCs.gridTemplateRows;\n entry.templateColumns = cols;\n entry.templateRows = rows;\n if (cols && cols !== 'none') {\n entry.columns = cols.split(/\\\\s+/).map(function(s) { return parseFloat(s) || 0; }).filter(function(v) { return v > 0; });\n }\n if (rows && rows !== 'none') {\n entry.rows = rows.split(/\\\\s+/).map(function(s) { return parseFloat(s) || 0; }).filter(function(v) { return v > 0; });\n }\n }\n layoutResults.push(entry);\n }\n respond(id, { containers: layoutResults });\n return;\n }\n\n if (type === 'layout:add-track') {\n var ltEl = getEl(payload.eid);\n if (!ltEl) { respond(id, { property: '', before: '', after: '' }); return; }\n var ltCs = window.getComputedStyle(ltEl);\n var cssProp = payload.axis === 'col' ? 'grid-template-columns' : 'grid-template-rows';\n var ltBefore = ltCs.getPropertyValue(cssProp) || '';\n var ltAfter = ltBefore && ltBefore !== 'none' ? ltBefore + ' 1fr' : '1fr';\n ltEl.style.setProperty(cssProp, ltAfter);\n respond(id, { property: cssProp, before: ltBefore, after: ltAfter });\n return;\n }\n\n if (type === 'layout:add-child') {\n var lcEl = getEl(payload.eid);\n if (!lcEl) { respond(id, { childEid: '' }); return; }\n var child = document.createElement('div');\n child.style.minWidth = '60px';\n child.style.minHeight = '40px';\n child.style.border = '2px dashed #a855f7';\n child.style.borderRadius = '4px';\n child.style.background = 'rgba(168,85,247,0.05)';\n child.setAttribute('data-annotask-placeholder', 'true');\n lcEl.appendChild(child);\n respond(id, { childEid: getEid(child) });\n return;\n }\n\n // ── Theme ──\n if (type === 'theme:inject-css') {\n var existing = document.getElementById(payload.styleId);\n if (existing) {\n existing.textContent = payload.css;\n } else {\n var style = document.createElement('style');\n style.id = payload.styleId;\n style.textContent = payload.css;\n document.head.appendChild(style);\n }\n respond(id, {});\n return;\n }\n\n if (type === 'theme:remove-css') {\n var toRemove = document.getElementById(payload.styleId);\n if (toRemove) toRemove.remove();\n respond(id, {});\n return;\n }\n\n // ── Color Palette ──\n if (type === 'palette:scan-vars') {\n var swatches = [];\n try {\n var rootStyles = window.getComputedStyle(document.documentElement);\n var sheets = document.styleSheets;\n for (var si = 0; si < sheets.length; si++) {\n try {\n var rules = sheets[si].cssRules;\n for (var ri = 0; ri < rules.length; ri++) {\n var rule = rules[ri];\n if (rule.selectorText === ':root' || rule.selectorText === ':root, :host') {\n for (var pi = 0; pi < rule.style.length; pi++) {\n var prop = rule.style[pi];\n if (prop.startsWith('--')) {\n var val = rootStyles.getPropertyValue(prop).trim();\n if (val && isColor(val)) {\n swatches.push({ name: prop, value: val });\n }\n }\n }\n }\n }\n } catch(e) { /* cross-origin stylesheet */ }\n }\n } catch(e) {}\n respond(id, { swatches: swatches });\n return;\n }\n\n // ── Source Mapping Check ──\n if (type === 'check:source-mapping') {\n respond(id, { hasMapping: !!(document.querySelector('[data-annotask-file]') || document.querySelector('[data-astro-source-file]')) });\n return;\n }\n\n // ── Insert Placeholder ──\n if (type === 'insert:placeholder') {\n var ipTarget = getEl(payload.targetEid);\n if (!ipTarget) { respond(id, { placeholderEid: '' }); return; }\n var ipEl = createPlaceholder(payload);\n var ipRef = ipTarget;\n switch (payload.position) {\n case 'before': ipRef.parentElement && ipRef.parentElement.insertBefore(ipEl, ipRef); break;\n case 'after': ipRef.parentElement && ipRef.parentElement.insertBefore(ipEl, ipRef.nextSibling); break;\n case 'append': ipRef.appendChild(ipEl); break;\n case 'prepend': ipRef.insertBefore(ipEl, ipRef.firstChild); break;\n }\n // Match sibling sizes in flex/grid\n var insertParent = (payload.position === 'append' || payload.position === 'prepend') ? ipRef : ipRef.parentElement;\n if (insertParent) {\n var pCs = window.getComputedStyle(insertParent);\n if (pCs.display.includes('flex') || pCs.display.includes('grid')) {\n if (!ipEl.style.width) {\n var isRow = pCs.flexDirection === 'row' || pCs.flexDirection === 'row-reverse' || pCs.display.includes('grid');\n if (isRow) ipEl.style.flex = '1';\n }\n }\n }\n respond(id, { placeholderEid: getEid(ipEl) });\n return;\n }\n\n if (type === 'insert:remove') {\n var irEl = getEl(payload.eid);\n if (irEl) {\n var unmount = irEl.__annotask_unmount;\n if (unmount) unmount();\n irEl.remove();\n }\n respond(id, {});\n return;\n }\n\n if (type === 'move:element') {\n var meEl = getEl(payload.eid);\n var meTarget = getEl(payload.targetEid);\n if (meEl && meTarget) {\n switch (payload.position) {\n case 'before': meTarget.parentElement && meTarget.parentElement.insertBefore(meEl, meTarget); break;\n case 'after': meTarget.parentElement && meTarget.parentElement.insertBefore(meEl, meTarget.nextSibling); break;\n case 'append': meTarget.appendChild(meEl); break;\n case 'prepend': meTarget.insertBefore(meEl, meTarget.firstChild); break;\n }\n }\n respond(id, {});\n return;\n }\n\n if (type === 'insert:vue-component' || type === 'insert:component') {\n var vcTarget = getEl(payload.targetEid);\n if (!vcTarget) { respond(id, { eid: '', mounted: false }); return; }\n var vcContainer = document.createElement('div');\n vcContainer.setAttribute('data-annotask-placeholder', 'true');\n switch (payload.position) {\n case 'before': vcTarget.parentElement && vcTarget.parentElement.insertBefore(vcContainer, vcTarget); break;\n case 'after': vcTarget.parentElement && vcTarget.parentElement.insertBefore(vcContainer, vcTarget.nextSibling); break;\n case 'append': vcTarget.appendChild(vcContainer); break;\n case 'prepend': vcTarget.insertBefore(vcContainer, vcTarget.firstChild); break;\n }\n var mounted = tryMountComponent(vcContainer, payload.componentName, payload.props);\n respond(id, { eid: getEid(vcContainer), mounted: mounted });\n return;\n }\n\n // ── Get source data for an eid ──\n if (type === 'resolve:source') {\n var rsEl = getEl(payload.eid);\n if (!rsEl) { respond(id, null); return; }\n var rsSrc = findSourceElement(rsEl);\n var rsData = getSourceData(rsSrc.sourceEl);\n respond(id, {\n sourceEid: getEid(rsSrc.sourceEl),\n file: rsData.file,\n line: rsData.line,\n component: rsData.component,\n tag: rsSrc.sourceEl.tagName.toLowerCase()\n });\n return;\n }\n\n // ── Route ──\n if (type === 'route:current') {\n respond(id, { path: window.location.pathname });\n return;\n }\n });\n\n // ── Helpers ───────────────────────────────────────────\n function isColor(val) {\n if (val.startsWith('#') || val.startsWith('rgb') || val.startsWith('hsl')) return true;\n // Named colors — quick check with canvas\n try {\n var ctx = document.createElement('canvas').getContext('2d');\n ctx.fillStyle = '#000000';\n ctx.fillStyle = val;\n return ctx.fillStyle !== '#000000' || val === 'black' || val === '#000000';\n } catch(e) { return false; }\n }\n\n function createPlaceholder(payload) {\n var el = document.createElement(payload.tag);\n el.setAttribute('data-annotask-placeholder', 'true');\n if (payload.classes) el.className = payload.classes;\n var tag = payload.tag.toLowerCase();\n var isComponent = payload.tag.includes('-') || (payload.tag[0] === payload.tag[0].toUpperCase() && payload.tag[0] !== payload.tag[0].toLowerCase());\n\n if (tag === 'button') {\n el.textContent = payload.textContent || 'Button';\n el.style.cssText = 'padding:8px 16px;border-radius:6px;font-size:14px;font-weight:500;cursor:pointer;border:1px solid currentColor;background:var(--accent,#3b82f6);color:white;';\n } else if (tag === 'input') {\n el.type = 'text'; el.placeholder = 'Input field...';\n el.style.cssText = 'padding:8px 12px;border:1px solid #ccc;border-radius:6px;font-size:14px;width:100%;max-width:300px;background:white;color:#333;';\n } else if (tag === 'textarea') {\n el.placeholder = 'Text area...';\n el.style.cssText = 'padding:8px 12px;border:1px solid #ccc;border-radius:6px;font-size:14px;width:100%;min-height:60px;background:white;color:#333;resize:vertical;';\n } else if (tag === 'img') {\n el.style.cssText = 'width:200px;height:120px;background:#e5e7eb;border-radius:8px;display:flex;align-items:center;justify-content:center;';\n } else if (['h1','h2','h3','h4','h5','h6'].indexOf(tag) >= 0) {\n el.textContent = payload.textContent || 'Heading';\n var sizes = { h1:'2em', h2:'1.5em', h3:'1.25em', h4:'1.1em', h5:'1em', h6:'0.9em' };\n el.style.cssText = 'font-size:' + (sizes[tag]||'1em') + ';font-weight:700;margin:0.5em 0;';\n } else if (tag === 'p') {\n el.textContent = payload.textContent || 'Paragraph text goes here.';\n el.style.cssText = 'margin:0.5em 0;line-height:1.5;';\n } else if (tag === 'a') {\n el.textContent = payload.textContent || 'Link';\n el.style.cssText = 'color:var(--accent,#3b82f6);text-decoration:underline;cursor:pointer;';\n } else if (tag === 'section' || tag === 'div' || tag === 'nav' || tag === 'header' || tag === 'footer' || tag === 'aside' || tag === 'main') {\n if (!payload.classes && !payload.textContent) {\n el.style.cssText = 'min-height:40px;padding:12px;border:1.5px dashed rgba(59,130,246,0.3);border-radius:6px;background:rgba(59,130,246,0.03);';\n } else if (payload.textContent) {\n el.textContent = payload.textContent;\n }\n if (payload.category === 'layout-preset') {\n el.style.minHeight = '60px';\n el.style.padding = el.style.padding || '12px';\n el.style.border = '1.5px dashed rgba(34,197,94,0.3)';\n el.style.borderRadius = '6px';\n el.style.background = 'rgba(34,197,94,0.03)';\n }\n } else if (isComponent) {\n var vcMounted = tryMountComponent(el, payload.tag, payload.defaultProps);\n if (!vcMounted) {\n el.style.cssText = 'min-height:80px;padding:16px;border:1px solid rgba(168,85,247,0.2);border-radius:8px;background:rgba(168,85,247,0.03);display:flex;flex-direction:column;gap:8px;overflow:hidden;';\n var hdr = document.createElement('div');\n hdr.style.cssText = 'display:flex;align-items:center;gap:6px;margin-bottom:4px;';\n var dot = document.createElement('span');\n dot.style.cssText = 'width:6px;height:6px;border-radius:50%;background:#a855f7;';\n hdr.appendChild(dot);\n var tagLabel = document.createElement('span');\n tagLabel.style.cssText = 'font-size:11px;font-weight:600;color:#a855f7;';\n tagLabel.textContent = payload.tag;\n hdr.appendChild(tagLabel);\n el.appendChild(hdr);\n }\n } else {\n el.textContent = payload.textContent || '';\n }\n return el;\n }\n\n function tryMountComponent(container, componentName, props) {\n // Try Vue\n if (window.__ANNOTASK_VUE__) {\n var mounted = tryMountVueComponent(container, componentName, props);\n if (mounted) return true;\n }\n // Try React\n if (window.__ANNOTASK_REACT__) {\n var mounted = tryMountReactComponent(container, componentName, props);\n if (mounted) return true;\n }\n // Try Svelte\n if (window.__ANNOTASK_SVELTE__) {\n var mounted = tryMountSvelteComponent(container, componentName, props);\n if (mounted) return true;\n }\n return false;\n }\n\n function tryMountVueComponent(container, componentName, props) {\n try {\n var appEl = document.querySelector('#app');\n var vueApp = appEl && appEl.__vue_app__;\n if (!vueApp) return false;\n var annotaskVue = window.__ANNOTASK_VUE__;\n if (!annotaskVue || !annotaskVue.createApp || !annotaskVue.h) return false;\n var component = vueApp._context.components[componentName] || (window.__ANNOTASK_COMPONENTS__ && window.__ANNOTASK_COMPONENTS__[componentName]);\n if (!component) return false;\n var mountPoint = document.createElement('div');\n container.appendChild(mountPoint);\n var miniApp = annotaskVue.createApp({\n render: function() { return annotaskVue.h(component, props || {}); }\n });\n miniApp._context = vueApp._context;\n miniApp.mount(mountPoint);\n container.setAttribute('data-annotask-mounted', 'true');\n container.__annotask_unmount = function() { try { miniApp.unmount(); } catch(e) {} };\n return true;\n } catch(e) { return false; }\n }\n\n function tryMountReactComponent(container, componentName, props) {\n try {\n var annotaskReact = window.__ANNOTASK_REACT__;\n if (!annotaskReact || !annotaskReact.createElement || !annotaskReact.createRoot) return false;\n var component = window.__ANNOTASK_COMPONENTS__ && window.__ANNOTASK_COMPONENTS__[componentName];\n if (!component) return false;\n var mountPoint = document.createElement('div');\n container.appendChild(mountPoint);\n var root = annotaskReact.createRoot(mountPoint);\n root.render(annotaskReact.createElement(component, props || {}));\n container.setAttribute('data-annotask-mounted', 'true');\n container.__annotask_unmount = function() { try { root.unmount(); } catch(e) {} };\n return true;\n } catch(e) { return false; }\n }\n\n function tryMountSvelteComponent(container, componentName, props) {\n try {\n var annotaskSvelte = window.__ANNOTASK_SVELTE__;\n if (!annotaskSvelte || !annotaskSvelte.mount) return false;\n var component = window.__ANNOTASK_COMPONENTS__ && window.__ANNOTASK_COMPONENTS__[componentName];\n if (!component) return false;\n var mountPoint = document.createElement('div');\n container.appendChild(mountPoint);\n var instance = annotaskSvelte.mount(component, { target: mountPoint, props: props || {} });\n container.setAttribute('data-annotask-mounted', 'true');\n container.__annotask_unmount = function() {\n try {\n if (annotaskSvelte.unmount) annotaskSvelte.unmount(instance);\n } catch(e) {}\n };\n return true;\n } catch(e) { return false; }\n }\n\n // ── Ready ─────────────────────────────────────────────\n sendToShell('bridge:ready', { version: '1.0' });\n})();\n`.trim()\n}\n"],"mappings":";AAQO,SAAS,qBAA6B;AAC3C,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkiCP,KAAK;AACP;","names":[]}
@@ -0,0 +1,488 @@
1
+ // src/server/api.ts
2
+ import fsp from "fs/promises";
3
+ import nodePath from "path";
4
+ var MAX_BODY_SIZE = 4194304;
5
+ var VALID_TASK_STATUSES = /* @__PURE__ */ new Set(["pending", "applied", "review", "accepted", "denied"]);
6
+ var PATCHABLE_TASK_FIELDS = /* @__PURE__ */ new Set([
7
+ "status",
8
+ "description",
9
+ "notes",
10
+ "screenshot",
11
+ "feedback",
12
+ "intent",
13
+ "action",
14
+ "context",
15
+ "viewport",
16
+ "interaction_history",
17
+ "element_context",
18
+ "mfe"
19
+ ]);
20
+ function readBody(req) {
21
+ return new Promise((resolve, reject) => {
22
+ let body = "";
23
+ let size = 0;
24
+ req.on("data", (chunk) => {
25
+ size += chunk.length;
26
+ if (size > MAX_BODY_SIZE) {
27
+ req.destroy();
28
+ reject(new Error("Request body too large"));
29
+ return;
30
+ }
31
+ body += chunk.toString();
32
+ });
33
+ req.on("end", () => resolve(body));
34
+ req.on("error", reject);
35
+ });
36
+ }
37
+ function parseJSON(raw) {
38
+ try {
39
+ return { ok: true, data: JSON.parse(raw) };
40
+ } catch {
41
+ return { ok: false };
42
+ }
43
+ }
44
+ function sendError(res, status, message) {
45
+ res.statusCode = status;
46
+ res.end(JSON.stringify({ error: message }));
47
+ }
48
+ function isLocalOrigin(origin) {
49
+ if (!origin) return true;
50
+ try {
51
+ const url = new URL(origin);
52
+ const host = url.hostname;
53
+ return host === "localhost" || host === "127.0.0.1" || host === "[::1]" || host === "::1";
54
+ } catch {
55
+ return false;
56
+ }
57
+ }
58
+ function getCorsOrigin(req) {
59
+ const origin = req.headers.origin;
60
+ if (!origin) return null;
61
+ return isLocalOrigin(origin) ? origin : null;
62
+ }
63
+ function createAPIMiddleware(options) {
64
+ return async (req, res, next) => {
65
+ if (req.url?.startsWith("/__annotask/screenshots/") && req.method === "GET") {
66
+ const filename = req.url.replace("/__annotask/screenshots/", "").replace(/\?.*$/, "");
67
+ if (!/^[a-zA-Z0-9_-]+\.png$/.test(filename)) {
68
+ res.statusCode = 400;
69
+ res.end("Invalid filename");
70
+ return;
71
+ }
72
+ const filePath = nodePath.join(options.projectRoot, ".annotask", "screenshots", filename);
73
+ try {
74
+ const data = await fsp.readFile(filePath);
75
+ res.setHeader("Content-Type", "image/png");
76
+ res.setHeader("Cache-Control", "public, max-age=3600");
77
+ const corsOrigin2 = getCorsOrigin(req);
78
+ if (corsOrigin2) res.setHeader("Access-Control-Allow-Origin", corsOrigin2);
79
+ res.end(data);
80
+ } catch {
81
+ res.statusCode = 404;
82
+ res.end("Not found");
83
+ }
84
+ return;
85
+ }
86
+ if (!req.url?.startsWith("/__annotask/api/")) return next();
87
+ const path3 = req.url.replace("/__annotask/api/", "");
88
+ const corsOrigin = getCorsOrigin(req);
89
+ res.setHeader("Content-Type", "application/json");
90
+ if (corsOrigin) {
91
+ res.setHeader("Access-Control-Allow-Origin", corsOrigin);
92
+ res.setHeader("Vary", "Origin");
93
+ }
94
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, PATCH, OPTIONS");
95
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type");
96
+ res.setHeader("Cache-Control", "no-cache");
97
+ if (req.method === "OPTIONS") {
98
+ res.statusCode = 200;
99
+ res.end();
100
+ return;
101
+ }
102
+ if ((req.method === "POST" || req.method === "PATCH") && !isLocalOrigin(req.headers.origin)) {
103
+ return sendError(res, 403, "Forbidden: non-local origin");
104
+ }
105
+ if (path3 === "report" && req.method === "GET") {
106
+ res.end(JSON.stringify(options.getReport() ?? { version: "1.0", changes: [] }, null, 2));
107
+ return;
108
+ }
109
+ if (path3 === "config" && req.method === "GET") {
110
+ res.end(JSON.stringify(options.getConfig(), null, 2));
111
+ return;
112
+ }
113
+ if (path3 === "design-spec" && req.method === "GET") {
114
+ res.end(JSON.stringify(options.getDesignSpec(), null, 2));
115
+ return;
116
+ }
117
+ if (path3.startsWith("tasks") && !path3.startsWith("tasks/") && req.method === "GET") {
118
+ const urlObj = new URL(req.url, `http://${req.headers.host || "localhost"}`);
119
+ const mfeFilter = urlObj.searchParams.get("mfe");
120
+ const taskData = options.getTasks();
121
+ if (mfeFilter) {
122
+ const filtered = { ...taskData, tasks: taskData.tasks.filter((t) => t.mfe === mfeFilter) };
123
+ res.end(JSON.stringify(filtered, null, 2));
124
+ } else {
125
+ res.end(JSON.stringify(taskData, null, 2));
126
+ }
127
+ return;
128
+ }
129
+ if (path3 === "tasks" && req.method === "POST") {
130
+ let raw;
131
+ try {
132
+ raw = await readBody(req);
133
+ } catch {
134
+ return sendError(res, 413, "Request body too large");
135
+ }
136
+ const parsed = parseJSON(raw);
137
+ if (!parsed.ok) return sendError(res, 400, "Invalid JSON body");
138
+ const body = parsed.data;
139
+ if (!body || typeof body !== "object" || Array.isArray(body)) return sendError(res, 400, "Request body must be a JSON object");
140
+ if (typeof body.type !== "string" || !body.type) return sendError(res, 400, "Missing required field: type (string)");
141
+ if (typeof body.description !== "string") return sendError(res, 400, "Missing required field: description (string)");
142
+ res.end(JSON.stringify(options.addTask(body), null, 2));
143
+ return;
144
+ }
145
+ if (path3 === "screenshots" && req.method === "POST") {
146
+ let raw;
147
+ try {
148
+ raw = await readBody(req);
149
+ } catch {
150
+ return sendError(res, 413, "Request body too large");
151
+ }
152
+ const parsed = parseJSON(raw);
153
+ if (!parsed.ok) return sendError(res, 400, "Invalid JSON body");
154
+ const body = parsed.data;
155
+ if (!body.data || typeof body.data !== "string") return sendError(res, 400, "Missing data field");
156
+ const match = body.data.match(/^data:image\/png;base64,(.+)$/);
157
+ if (!match) return sendError(res, 400, "Invalid PNG data URL");
158
+ const buffer = Buffer.from(match[1], "base64");
159
+ if (buffer.length > 4 * 1024 * 1024) return sendError(res, 413, "Screenshot too large (max 4MB)");
160
+ const filename = `screenshot-${Date.now()}-${Math.random().toString(36).slice(2, 7)}.png`;
161
+ const dir = nodePath.join(options.projectRoot, ".annotask", "screenshots");
162
+ await fsp.mkdir(dir, { recursive: true });
163
+ await fsp.writeFile(nodePath.join(dir, filename), buffer);
164
+ res.end(JSON.stringify({ filename }));
165
+ return;
166
+ }
167
+ if (path3.startsWith("tasks/") && req.method === "PATCH") {
168
+ const id = path3.replace("tasks/", "");
169
+ let raw;
170
+ try {
171
+ raw = await readBody(req);
172
+ } catch {
173
+ return sendError(res, 413, "Request body too large");
174
+ }
175
+ const parsed = parseJSON(raw);
176
+ if (!parsed.ok) return sendError(res, 400, "Invalid JSON body");
177
+ const body = parsed.data;
178
+ if (!body || typeof body !== "object" || Array.isArray(body)) return sendError(res, 400, "Request body must be a JSON object");
179
+ if (body.status !== void 0 && !VALID_TASK_STATUSES.has(body.status)) {
180
+ return sendError(res, 400, `Invalid status. Must be one of: ${[...VALID_TASK_STATUSES].join(", ")}`);
181
+ }
182
+ const sanitized = {};
183
+ for (const key of Object.keys(body)) {
184
+ if (PATCHABLE_TASK_FIELDS.has(key)) {
185
+ sanitized[key] = body[key];
186
+ }
187
+ }
188
+ res.end(JSON.stringify(options.updateTask(id, sanitized), null, 2));
189
+ return;
190
+ }
191
+ if (path3 === "status" && req.method === "GET") {
192
+ res.end(JSON.stringify({ status: "ok", tool: "annotask" }));
193
+ return;
194
+ }
195
+ res.statusCode = 404;
196
+ res.end(JSON.stringify({ error: "Not found" }));
197
+ };
198
+ }
199
+
200
+ // src/server/ws-server.ts
201
+ import { WebSocketServer, WebSocket } from "ws";
202
+ function createWSServer() {
203
+ let currentReport = null;
204
+ const clients = /* @__PURE__ */ new Set();
205
+ const wss = new WebSocketServer({ noServer: true });
206
+ wss.on("connection", (ws) => {
207
+ clients.add(ws);
208
+ if (currentReport) {
209
+ ws.send(JSON.stringify({ event: "report:current", data: currentReport, timestamp: Date.now() }));
210
+ }
211
+ ws.on("message", (raw) => {
212
+ try {
213
+ const msg = JSON.parse(raw.toString());
214
+ if (msg.event === "report:updated") {
215
+ currentReport = msg.data;
216
+ for (const client of clients) {
217
+ if (client !== ws && client.readyState === WebSocket.OPEN) {
218
+ client.send(JSON.stringify({ event: "report:updated", data: msg.data, timestamp: Date.now() }));
219
+ }
220
+ }
221
+ }
222
+ if (msg.event === "changes:cleared") {
223
+ currentReport = null;
224
+ for (const client of clients) {
225
+ if (client !== ws && client.readyState === WebSocket.OPEN) {
226
+ client.send(JSON.stringify({ event: "changes:cleared", data: null, timestamp: Date.now() }));
227
+ }
228
+ }
229
+ }
230
+ if (msg.event === "get:report") {
231
+ ws.send(JSON.stringify({ event: "report:current", data: currentReport, timestamp: Date.now() }));
232
+ }
233
+ } catch {
234
+ }
235
+ });
236
+ ws.on("close", () => {
237
+ clients.delete(ws);
238
+ });
239
+ });
240
+ return {
241
+ handleUpgrade(req, socket, head) {
242
+ wss.handleUpgrade(req, socket, head, (ws) => {
243
+ wss.emit("connection", ws, req);
244
+ });
245
+ },
246
+ broadcast(event, data) {
247
+ const msg = JSON.stringify({ event, data, timestamp: Date.now() });
248
+ for (const client of clients) {
249
+ if (client.readyState === WebSocket.OPEN) client.send(msg);
250
+ }
251
+ },
252
+ getReport() {
253
+ return currentReport;
254
+ },
255
+ clients
256
+ };
257
+ }
258
+
259
+ // src/server/serve-shell.ts
260
+ import fsp2 from "fs/promises";
261
+ import path from "path";
262
+ import { fileURLToPath } from "url";
263
+ var __dirname = path.dirname(fileURLToPath(import.meta.url));
264
+ function findShellDist() {
265
+ return path.resolve(__dirname, "shell");
266
+ }
267
+ function findVendorDist() {
268
+ return path.resolve(__dirname, "vendor");
269
+ }
270
+ function createShellMiddleware() {
271
+ const shellDist = findShellDist();
272
+ const vendorDist = findVendorDist();
273
+ return async (req, res, next) => {
274
+ if (!req.url?.startsWith("/__annotask")) return next();
275
+ const origin = req.headers.origin;
276
+ if (origin) {
277
+ try {
278
+ const host = new URL(origin).hostname;
279
+ if (host === "localhost" || host === "127.0.0.1" || host === "[::1]" || host === "::1") {
280
+ res.setHeader("Access-Control-Allow-Origin", origin);
281
+ res.setHeader("Vary", "Origin");
282
+ }
283
+ } catch {
284
+ }
285
+ }
286
+ let filePath = req.url.replace("/__annotask", "") || "/";
287
+ const queryIndex = filePath.indexOf("?");
288
+ if (queryIndex !== -1) filePath = filePath.slice(0, queryIndex);
289
+ if (filePath === "/" || filePath === "") filePath = "/index.html";
290
+ if (filePath.startsWith("/api/") || filePath === "/ws") return next();
291
+ if (filePath.startsWith("/vendor/")) {
292
+ const vendorFile = path.join(vendorDist, filePath.replace("/vendor/", ""));
293
+ if (!vendorFile.startsWith(vendorDist)) {
294
+ res.statusCode = 403;
295
+ res.end("Forbidden");
296
+ return;
297
+ }
298
+ try {
299
+ const data = await fsp2.readFile(vendorFile);
300
+ res.setHeader("Content-Type", "application/javascript");
301
+ res.setHeader("Cache-Control", "public, max-age=86400");
302
+ res.end(data);
303
+ } catch {
304
+ res.statusCode = 404;
305
+ res.end("Vendor file not found");
306
+ return;
307
+ }
308
+ return;
309
+ }
310
+ const fullPath = path.join(shellDist, filePath);
311
+ if (!fullPath.startsWith(shellDist)) {
312
+ res.statusCode = 403;
313
+ res.end("Forbidden");
314
+ return;
315
+ }
316
+ try {
317
+ const stat = await fsp2.stat(fullPath);
318
+ if (!stat.isFile()) throw new Error("not a file");
319
+ const ext = path.extname(fullPath);
320
+ const contentTypes = {
321
+ ".html": "text/html",
322
+ ".js": "application/javascript",
323
+ ".css": "text/css",
324
+ ".json": "application/json",
325
+ ".svg": "image/svg+xml",
326
+ ".png": "image/png",
327
+ ".ico": "image/x-icon"
328
+ };
329
+ res.setHeader("Content-Type", contentTypes[ext] || "application/octet-stream");
330
+ const data = await fsp2.readFile(fullPath);
331
+ res.end(data);
332
+ } catch {
333
+ const indexPath = path.join(shellDist, "index.html");
334
+ try {
335
+ const html = await fsp2.readFile(indexPath, "utf-8");
336
+ res.setHeader("Content-Type", "text/html");
337
+ res.end(html);
338
+ } catch {
339
+ res.statusCode = 404;
340
+ res.end("Annotask shell not built. Run: pnpm build:shell");
341
+ }
342
+ }
343
+ };
344
+ }
345
+
346
+ // src/server/state.ts
347
+ import fs from "fs";
348
+ import fsp3 from "fs/promises";
349
+ import path2 from "path";
350
+ var DEFAULT_DESIGN_SPEC = {
351
+ initialized: false,
352
+ version: "1.0",
353
+ framework: null,
354
+ colors: [],
355
+ typography: { families: [], scale: [], weights: [] },
356
+ spacing: [],
357
+ borders: { radius: [] },
358
+ icons: null,
359
+ components: null
360
+ };
361
+ async function atomicWrite(filePath, data) {
362
+ const dir = path2.dirname(filePath);
363
+ await fsp3.mkdir(dir, { recursive: true });
364
+ const tmpPath = filePath + `.tmp.${process.pid}.${Date.now()}`;
365
+ await fsp3.writeFile(tmpPath, data, "utf-8");
366
+ await fsp3.rename(tmpPath, filePath);
367
+ }
368
+ function createProjectState(projectRoot, broadcast) {
369
+ let cachedDesignSpec = null;
370
+ let specWatcher = null;
371
+ const tasksPath = path2.join(projectRoot, ".annotask", "tasks.json");
372
+ let taskCache = null;
373
+ let writeQueue = Promise.resolve();
374
+ function loadTasksSync() {
375
+ if (taskCache) return taskCache;
376
+ try {
377
+ taskCache = JSON.parse(fs.readFileSync(tasksPath, "utf-8"));
378
+ } catch {
379
+ taskCache = { version: "1.0", tasks: [] };
380
+ }
381
+ return taskCache;
382
+ }
383
+ function flushTasks() {
384
+ const data = taskCache;
385
+ if (!data) return;
386
+ writeQueue = writeQueue.then(() => atomicWrite(tasksPath, JSON.stringify(data, null, 2))).catch(() => {
387
+ });
388
+ }
389
+ function getDesignSpec() {
390
+ if (cachedDesignSpec !== null) return cachedDesignSpec;
391
+ const specPath = path2.join(projectRoot, ".annotask", "design-spec.json");
392
+ try {
393
+ cachedDesignSpec = { initialized: true, ...JSON.parse(fs.readFileSync(specPath, "utf-8")) };
394
+ } catch {
395
+ cachedDesignSpec = DEFAULT_DESIGN_SPEC;
396
+ }
397
+ if (!specWatcher) {
398
+ const configDir = path2.join(projectRoot, ".annotask");
399
+ try {
400
+ if (!fs.existsSync(configDir)) fs.mkdirSync(configDir, { recursive: true });
401
+ specWatcher = fs.watch(configDir, (_, filename) => {
402
+ cachedDesignSpec = null;
403
+ if (filename === "design-spec.json") broadcast("designspec:updated", null);
404
+ if (filename === "tasks.json") {
405
+ taskCache = null;
406
+ }
407
+ });
408
+ } catch {
409
+ cachedDesignSpec = null;
410
+ }
411
+ }
412
+ return cachedDesignSpec ?? DEFAULT_DESIGN_SPEC;
413
+ }
414
+ function getConfig() {
415
+ const spec = getDesignSpec();
416
+ return { initialized: !!spec?.initialized, ...spec };
417
+ }
418
+ function addTask(task) {
419
+ const data = loadTasksSync();
420
+ const id = `task-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
421
+ const newTask = { id, status: "pending", createdAt: Date.now(), updatedAt: Date.now(), ...task };
422
+ data.tasks.push(newTask);
423
+ flushTasks();
424
+ broadcast("tasks:updated", data);
425
+ return newTask;
426
+ }
427
+ function updateTask(id, updates) {
428
+ const data = loadTasksSync();
429
+ const task = data.tasks.find((t) => t.id === id);
430
+ if (!task) return { error: "Task not found" };
431
+ Object.assign(task, updates, { updatedAt: Date.now() });
432
+ if (updates.status === "accepted") {
433
+ if (task.screenshot) {
434
+ const screenshotPath = path2.join(projectRoot, ".annotask", "screenshots", task.screenshot);
435
+ fsp3.unlink(screenshotPath).catch(() => {
436
+ });
437
+ }
438
+ data.tasks = data.tasks.filter((t) => t.id !== id);
439
+ }
440
+ flushTasks();
441
+ broadcast("tasks:updated", data);
442
+ return task;
443
+ }
444
+ function dispose() {
445
+ if (specWatcher) {
446
+ specWatcher.close();
447
+ specWatcher = null;
448
+ }
449
+ }
450
+ return { getDesignSpec, getConfig, getTasks: loadTasksSync, addTask, updateTask, dispose };
451
+ }
452
+
453
+ // src/server/index.ts
454
+ function createAnnotaskServer(options) {
455
+ const wsServer = createWSServer();
456
+ const state = createProjectState(options.projectRoot, wsServer.broadcast);
457
+ const apiMiddleware = createAPIMiddleware({
458
+ projectRoot: options.projectRoot,
459
+ getReport: () => wsServer.getReport(),
460
+ getConfig: () => state.getConfig(),
461
+ getDesignSpec: () => state.getDesignSpec(),
462
+ getTasks: () => state.getTasks(),
463
+ addTask: (task) => state.addTask(task),
464
+ updateTask: (id, updates) => state.updateTask(id, updates)
465
+ });
466
+ const shellMiddleware = createShellMiddleware();
467
+ const middleware = (req, res, next) => {
468
+ apiMiddleware(req, res, () => {
469
+ shellMiddleware(req, res, next);
470
+ });
471
+ };
472
+ return {
473
+ middleware,
474
+ handleUpgrade: (req, socket, head) => wsServer.handleUpgrade(req, socket, head),
475
+ broadcast: (event, data) => wsServer.broadcast(event, data),
476
+ getReport: () => wsServer.getReport(),
477
+ dispose: () => state.dispose()
478
+ };
479
+ }
480
+
481
+ export {
482
+ createAPIMiddleware,
483
+ createWSServer,
484
+ createShellMiddleware,
485
+ createProjectState,
486
+ createAnnotaskServer
487
+ };
488
+ //# sourceMappingURL=chunk-LGNV6UH3.js.map