@sentientui/core 0.21.3 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/chunk-2RH7I6AP.mjs +2 -0
  2. package/dist/chunk-2RH7I6AP.mjs.map +1 -0
  3. package/dist/chunk-EWP6FTHE.mjs +2 -0
  4. package/dist/chunk-EWP6FTHE.mjs.map +1 -0
  5. package/dist/chunk-KIP52DUJ.mjs +2 -0
  6. package/dist/chunk-KIP52DUJ.mjs.map +1 -0
  7. package/dist/classify-DYlSjkFP.d.cts +47 -0
  8. package/dist/classify-DYlSjkFP.d.ts +47 -0
  9. package/dist/{index-BcRtGYVH.d.ts → index-BbrtAtrY.d.ts} +11 -4
  10. package/dist/{index-D3_vlZ3z.d.cts → index-CXxvWxCB.d.cts} +11 -4
  11. package/dist/index-engagement.d.cts +3 -28
  12. package/dist/index-engagement.d.ts +3 -28
  13. package/dist/index-engagement.js +1 -1
  14. package/dist/index-engagement.js.map +1 -1
  15. package/dist/index-engagement.mjs +1 -1
  16. package/dist/index-engagement.mjs.map +1 -1
  17. package/dist/index-graph.d.cts +15 -5
  18. package/dist/index-graph.d.ts +15 -5
  19. package/dist/index-graph.js +1 -1
  20. package/dist/index-graph.js.map +1 -1
  21. package/dist/index-graph.mjs +1 -1
  22. package/dist/index-graph.mjs.map +1 -1
  23. package/dist/index-local.d.cts +1 -1
  24. package/dist/index-local.d.ts +1 -1
  25. package/dist/index-topics.d.cts +21 -0
  26. package/dist/index-topics.d.ts +21 -0
  27. package/dist/index-topics.js +2 -0
  28. package/dist/index-topics.js.map +1 -0
  29. package/dist/index-topics.mjs +2 -0
  30. package/dist/index-topics.mjs.map +1 -0
  31. package/dist/index.d.cts +1 -1
  32. package/dist/index.d.ts +1 -1
  33. package/dist/index.js +1 -1
  34. package/dist/index.js.map +1 -1
  35. package/dist/index.mjs +1 -1
  36. package/package.json +7 -2
  37. package/dist/chunk-CD2A55US.mjs +0 -2
  38. package/dist/chunk-CD2A55US.mjs.map +0 -1
  39. package/dist/chunk-Q252FDO2.mjs +0 -2
  40. package/dist/chunk-Q252FDO2.mjs.map +0 -1
@@ -1,2 +1,2 @@
1
- import{a as b,d as v}from"./chunk-CD2A55US.mjs";import{A as _,y as x}from"./chunk-Q252FDO2.mjs";import{a as T,b as N,c as A,j as L,k as U,l as j,m as K,n as z}from"./chunk-QNEYUJJJ.mjs";import{a as y,b as I}from"./chunk-TMCGHANO.mjs";var O="_snt_graph_edges",F={pricing:["features","faq"],features:["pricing"],faq:["pricing"],social_proof:["cta"],cta:["social_proof","hero","trust"],hero:["cta"],comparison:["pricing"],trust:["cta"]};function q(e){var t;return(t=F[e])!=null?t:[]}function B(e,t){try{let n=localStorage.getItem(e);return n?JSON.parse(n):t}catch(n){return t}}function H(e,t){try{localStorage.setItem(e,JSON.stringify(t))}catch(n){}}var J=new Set(["pricing","hero","social_proof","cta","features","faq","comparison","trust","navigation","generic"]);function V(e){return J.has(e)?e:"generic"}function M(e){try{let t=new URL(e);return`${t.origin}${t.pathname}`}catch(t){return"/"}}function Y(e,t,n){let i=`${e}:${t}:${n.join(",")}`,p=5381;for(let l=0;l<i.length;l++)p=(p<<5)+p+i.charCodeAt(l)&4294967295;return(p>>>0).toString(16).padStart(8,"0")}function R(e){let t=new Map,n=new Map,i=`_snt_graph_nodes${T(e==null?void 0:e.apiKey)}`,p=()=>{typeof window!="undefined"&&H(i,[...t.values()])},l=o=>{var a;try{let d=JSON.parse(o);t.clear();for(let c of(a=d.pageNodes)!=null?a:[])t.set(c.componentId,c)}catch(d){}};if(typeof window!="undefined"){let o=B(i,[]);for(let a of o)t.set(a.componentId,a);try{localStorage.removeItem(O)}catch(a){}}return{addPageNode(o){t.set(o.componentId,o),p()},addStructuralEdge(o){let a=`${o.fromComponentId}->${o.toComponentId}`;n.set(a,o)},syncOnce(){var a,d;if(!(e!=null&&e.syncUrl)||typeof window=="undefined")return;let o=[...t.values()];if(o.length!==0)try{let c=new Map;for(let s of o){let f=(a=c.get(s.semanticType))!=null?a:[];f.push(s),c.set(s.semanticType,f)}let u=[],m=new Set;for(let s of o)for(let f of q(s.semanticType)){let S=(d=c.get(f))!=null?d:[];for(let h of S){if(h.componentId===s.componentId)continue;let w=`semantic:${s.componentId}->${h.componentId}`;m.has(w)||(m.add(w),u.push({fromComponentId:s.componentId,toComponentId:h.componentId,type:"semantic",weight:.4,confidence:.9}))}}let g=new Set(o.map(s=>s.componentId));for(let s of n.values()){if(!g.has(s.fromComponentId)||!g.has(s.toComponentId))continue;let f=`structural:${s.fromComponentId}->${s.toComponentId}`;m.has(f)||(m.add(f),u.push({fromComponentId:s.fromComponentId,toComponentId:s.toComponentId,type:"structural",weight:s.weight,confidence:1}))}let r=I(y(y({pageUrl:M(window.location.href)},e.sessionId?{sessionId:e.sessionId}:{}),e.projectId?{projectId:e.projectId}:{}),{nodes:o.map(s=>{let f=V(s.semanticType);return{componentId:s.componentId,semanticType:f,answers:s.answers,contentHash:Y(s.componentId,f,s.answers),prominenceScore:s.prominenceScore,depthInPage:s.depth}}),edges:u});fetch(e.syncUrl,{method:"POST",keepalive:!0,headers:y({"Content-Type":"application/json"},e.apiKey?{Authorization:`Bearer ${e.apiKey}`}:{}),body:JSON.stringify(r)}).catch(()=>{})}catch(c){}},snapshot(){return{pageNodes:[...t.values()],capturedAt:Date.now()}},serialize(){return JSON.stringify({pageNodes:[...t.values()]})},restore:l,destroy(){if(typeof window!="undefined")try{localStorage.removeItem(i),localStorage.removeItem(O)}catch(o){}}}}var W=new Set(["SECTION","ARTICLE","MAIN","DIV"]),Q="h1, h2, h3",P=30,X=1500,Z={scan:async()=>({nodes:[],edges:[],scannedAt:0}),observe:()=>{},getProminenceScore:()=>0,destroy:()=>{}};function D(e,t,n){return n<=t?0:Math.max(0,Math.min(1,(e-t)/(n-t)))}function ee(e){var t,n,i;try{let p=e;for(let l of Object.keys(p)){if(!l.startsWith("__reactFiber")&&!l.startsWith("__reactInternalInstance"))continue;let o=p[l],a=(i=(t=o==null?void 0:o.type)==null?void 0:t.displayName)!=null?i:(n=o==null?void 0:o.type)==null?void 0:n.name;if(a&&a.length>1)return a}}catch(p){}}function te(e){let t={};for(let n of Array.from(e.attributes))n.name.startsWith("data-")&&(t[n.name]=n.value);return t}var G=new Set(b);function ne(e){let t=e.getAttribute("data-sentient-type");if(t&&G.has(t))return t;let n=e.getAttribute("role");return n&&G.has(n)?n:v(e)}function oe(e){let t=5381;for(let n=0;n<e.length;n++)t=(t<<5)+t+e.charCodeAt(n)&4294967295;return(t>>>0).toString(16).padStart(8,"0")}function re(e){let t=[],n=e;for(;n;){let i=n.parentElement;if(!i){t.push(n.tagName.toLowerCase());break}let p=Array.prototype.indexOf.call(i.children,n);t.push(`${n.tagName.toLowerCase()}[${p}]`),n=i}return t.reverse().join("/")}function se(e){var n;let t=(n=e.getAttribute("data-sentient-id"))!=null?n:e.getAttribute("id");return t||`${e.tagName.toLowerCase()}-${oe(re(e))}`}function ae(e){let t=0,n=e.parentElement;for(;n;)t++,n=n.parentElement;return t}function C(e,t){var i,p,l;let n=e.querySelector(Q);return{componentId:se(e),semanticType:ne(e),ariaLabel:(i=e.getAttribute("aria-label"))!=null?i:void 0,headingText:(l=(p=n==null?void 0:n.textContent)==null?void 0:p.trim())!=null?l:void 0,isAboveFold:e.getBoundingClientRect().top<window.innerHeight,prominenceScore:t(e),depth:ae(e),reactComponentName:ee(e),dataAttributes:te(e)}}function $(e){var l;let t=[],n=new Set,i="__root__",p=new Map;for(let[o,a]of e){let d=o.parentElement,c=i;for(;d;){if(e.has(d)){c=e.get(d);let m=e.get(o),g=`${c}->${m}`;!n.has(g)&&c!==m&&(n.add(g),t.push({fromComponentId:c,toComponentId:m,weight:.6}));break}d=d.parentElement}let u=(l=p.get(c))!=null?l:[];u.push(o),p.set(c,u)}for(let o of p.values()){if(o.length<2)continue;let a=o.length>P?o.slice(0,P):o;for(let d=0;d<a.length;d++)for(let c=d+1;c<a.length;c++){if(t.length>=X)return t;let u=e.get(a[d]),m=e.get(a[c]);if(u===m)continue;let g=`${u}->${m}::sib`,r=`${m}->${u}::sib`;n.has(g)||(n.add(g),t.push({fromComponentId:u,toComponentId:m,weight:.3})),n.has(r)||(n.add(r),t.push({fromComponentId:m,toComponentId:u,weight:.3}))}}return t}function ie(e){let t=[],n=new Set,i=new Map;return document.querySelectorAll("[data-sentient-id]").forEach(o=>{if(o instanceof Element&&!n.has(o)){n.add(o);let a=C(o,e);t.push(a),i.set(o,a.componentId)}}),document.querySelectorAll("section, article, main, aside").forEach(o=>{if(!(o instanceof Element)||n.has(o))return;let a=o.hasAttribute("aria-label"),d=o.hasAttribute("data-sentient-id");if(!a&&!d)return;n.add(o);let c=C(o,e);t.push(c),i.set(o,c.componentId)}),{nodes:t,edges:$(i),elementToId:i}}function k(){if(typeof window=="undefined")return Z;let e=null,t=0,n=null,i=new Map,p=d=>{try{let c=window.getComputedStyle(d),u=parseFloat(c.fontSize)||12,m=parseFloat(c.zIndex)||0,g=d.getBoundingClientRect(),r=Math.max(g.top,0),s=window.innerHeight||1,f=1/(r/s+1),S=D(u,12,48)*.4+f*.4+D(m,0,100)*.2;return Math.max(0,Math.min(1,S))}catch(c){return .5}};return{scan:()=>new Promise(d=>{let c=()=>{let{nodes:u,edges:m,elementToId:g}=ie(p);i.clear();for(let[r,s]of g)i.set(r,s);d({nodes:u,edges:m,scannedAt:Date.now()})};try{typeof requestIdleCallback=="function"?t=requestIdleCallback(c,{timeout:100}):c()}catch(u){c()}}),observe:d=>{n=d;try{e=new MutationObserver(c=>{let u=[],m=new Set;for(let r of c)r.type==="childList"&&r.addedNodes.forEach(s=>{if(!(s instanceof Element)||!W.has(s.tagName))return;let f=s.hasAttribute("data-sentient-id"),S=s.hasAttribute("aria-label");if(!f&&!S)return;let h=C(s,p);u.push(h),m.add(h.componentId),i.set(s,h.componentId)});if(u.length===0||!n)return;for(let r of[...i.keys()])r.isConnected||i.delete(r);let g=$(i).filter(r=>m.has(r.fromComponentId)||m.has(r.toComponentId));n({nodes:u,edges:g,addedAt:Date.now()})}),e.observe(document.body,{childList:!0,subtree:!0})}catch(c){}},getProminenceScore:p,destroy:()=>{if(e&&(e.disconnect(),e=null),t&&typeof cancelIdleCallback=="function")try{cancelIdleCallback(t)}catch(d){}t=0,n=null,i.clear()}}}var de="https://api.sentient-ui.com/v1/events";function ce(e){var n;let t=i=>{try{let p=document.cookie.match(new RegExp(`(?:^|; )${i}=([^;]*)`));return p?decodeURIComponent(p[1]):void 0}catch(p){return}};return(n=e?t(A(e)):void 0)!=null?n:t(N)}var E=new Map;function Ie(e){var m;let t=_(e),n=e.respectDoNotTrack!==!1&&x(),i=e.consent===!1||n;if(!e.graph||!e.apiKey||i||typeof window=="undefined")return t;let p=E.get(e.apiKey);if(p)try{p()}catch(g){}let l=k(),o=(m=e.ingestUrl)!=null?m:de,a=R({syncUrl:o.replace(/\/events\/?$/,"/graph/sync"),apiKey:e.apiKey,projectId:e.apiKey,sessionId:ce(e.apiKey)});l.scan().then(g=>{for(let r of g.nodes)a.addPageNode({id:r.componentId,componentId:r.componentId,semanticType:r.semanticType,answers:e.captureDomText&&r.headingText?[r.headingText]:[],prominenceScore:r.prominenceScore,depth:r.depth});for(let r of g.edges)a.addStructuralEdge(r);a.syncOnce()});let d=null,c=()=>{d!==null&&clearTimeout(d),d=setTimeout(()=>{d=null,a.syncOnce()},500)};l.observe(g=>{for(let r of g.nodes)a.addPageNode({id:r.componentId,componentId:r.componentId,semanticType:r.semanticType,answers:e.captureDomText&&r.headingText?[r.headingText]:[],prominenceScore:r.prominenceScore,depth:r.depth});for(let r of g.edges)a.addStructuralEdge(r);c()});let u=()=>{d!==null&&(clearTimeout(d),d=null),l.destroy(),a.destroy(),E.get(e.apiKey)===u&&E.delete(e.apiKey)};return E.set(e.apiKey,u),I(y({},t),{getGraph:()=>a.snapshot(),dispose:()=>{u(),t.dispose()},destroy:()=>{u(),t.destroy()}})}export{z as deriveSessionSegment,L as detectDeviceClass,K as detectTimeOfDay,U as detectTrafficSource,Ie as init,j as referrerDomainFromReferer,M as sanitizePageUrl};
1
+ import{a as w}from"./chunk-KIP52DUJ.mjs";import{a as v,f as A}from"./chunk-EWP6FTHE.mjs";import{A as Z,B as O,a as V,b as W,c as J,d as X,g as we,h as Te,i as ve,j as Ae,k as Ne,l as xe,m as _e,n as Oe,o as Re,p as Me,q as De,r as Pe,s as ke,t as Ge,u as $e,v as Le,w as je,x as Q,y as x,z as _}from"./chunk-2RH7I6AP.mjs";import{a as N,b as C,c as b,j,k as K,l as U,m as F,n as q,p as z,q as B,r as H,s as Y}from"./chunk-QNEYUJJJ.mjs";import{a as y,b as E}from"./chunk-TMCGHANO.mjs";var R="_snt_graph_edges",ee={pricing:["features","faq"],features:["pricing"],faq:["pricing"],social_proof:["cta"],cta:["social_proof","hero","trust"],hero:["cta"],comparison:["pricing"],trust:["cta"]};function te(e){var t;return(t=ee[e])!=null?t:[]}function ne(e,t){try{let n=localStorage.getItem(e);return n?JSON.parse(n):t}catch(n){return t}}function oe(e,t){try{localStorage.setItem(e,JSON.stringify(t))}catch(n){}}var re=new Set(["pricing","hero","social_proof","cta","features","faq","comparison","trust","navigation","generic"]);function se(e){return re.has(e)?e:"generic"}function M(e){try{let t=new URL(e);return`${t.origin}${t.pathname}`}catch(t){return"/"}}function ae(e,t,n){let a=`${e}:${t}:${n.join(",")}`,c=5381;for(let i=0;i<a.length;i++)c=(c<<5)+c+a.charCodeAt(i)&4294967295;return(c>>>0).toString(16).padStart(8,"0")}function D(e){let t=new Map,n=new Map,a=`_snt_graph_nodes${N(e==null?void 0:e.apiKey)}`,c=()=>{typeof window!="undefined"&&oe(a,[...t.values()])};if(typeof window!="undefined"){let i=ne(a,[]);for(let o of i)t.set(o.componentId,o);try{localStorage.removeItem(R)}catch(o){}}return{addPageNode(i){t.set(i.componentId,i),c()},addStructuralEdge(i){let o=`${i.fromComponentId}->${i.toComponentId}`;n.set(o,i)},syncOnce(){var o,m;if(!(e!=null&&e.syncUrl)||typeof window=="undefined")return;let i=[...t.values()];if(i.length!==0)try{let u=new Map;for(let r of i){let p=(o=u.get(r.semanticType))!=null?o:[];p.push(r),u.set(r.semanticType,p)}let s=[],d=new Set;for(let r of i)for(let p of te(r.semanticType)){let l=(m=u.get(p))!=null?m:[];for(let h of l){if(h.componentId===r.componentId)continue;let S=`semantic:${r.componentId}->${h.componentId}`;d.has(S)||(d.add(S),s.push({fromComponentId:r.componentId,toComponentId:h.componentId,type:"semantic",weight:.4,confidence:.9}))}}let f=new Set(i.map(r=>r.componentId));for(let r of n.values()){if(!f.has(r.fromComponentId)||!f.has(r.toComponentId))continue;let p=`structural:${r.fromComponentId}->${r.toComponentId}`;d.has(p)||(d.add(p),s.push({fromComponentId:r.fromComponentId,toComponentId:r.toComponentId,type:"structural",weight:r.weight,confidence:1}))}let g=E(y(y({pageUrl:M(window.location.href)},e.sessionId?{sessionId:e.sessionId}:{}),e.projectId?{projectId:e.projectId}:{}),{nodes:i.map(r=>{let p=se(r.semanticType);return{componentId:r.componentId,semanticType:p,answers:r.answers,contentHash:ae(r.componentId,p,r.answers),prominenceScore:r.prominenceScore,depthInPage:r.depth}}),edges:s});fetch(e.syncUrl,{method:"POST",keepalive:!0,headers:y({"Content-Type":"application/json"},e.apiKey?{Authorization:`Bearer ${e.apiKey}`}:{}),body:JSON.stringify(g)}).catch(()=>{})}catch(u){}},snapshot(){return{pageNodes:[...t.values()],capturedAt:Date.now()}},destroy(){if(typeof window!="undefined")try{localStorage.removeItem(a),localStorage.removeItem(R)}catch(i){}}}}var ie=new Set(["SECTION","ARTICLE","MAIN","DIV","ASIDE"]),ce="h1, h2, h3",de="[data-sentient-id], section[aria-label], article[aria-label], main[aria-label], aside[aria-label]",P=30,pe=1500,ue={scan:async()=>({nodes:[],edges:[],scannedAt:0}),observe:()=>{},getProminenceScore:()=>0,destroy:()=>{}};function k(e,t,n){return n<=t?0:Math.max(0,Math.min(1,(e-t)/(n-t)))}function le(e){var t,n,a;try{let c=e;for(let i of Object.keys(c)){if(!i.startsWith("__reactFiber")&&!i.startsWith("__reactInternalInstance"))continue;let o=c[i],m=(a=(t=o==null?void 0:o.type)==null?void 0:t.displayName)!=null?a:(n=o==null?void 0:o.type)==null?void 0:n.name;if(m&&m.length>1)return m}}catch(c){}}function me(e){let t={};for(let n of Array.from(e.attributes))n.name.startsWith("data-")&&(t[n.name]=n.value);return t}var G=new Set(v);function fe(e){let t=e.getAttribute("data-sentient-type");if(t&&G.has(t))return t;let n=e.getAttribute("role");return n&&G.has(n)?n:A(e)}function ge(e){let t=5381;for(let n=0;n<e.length;n++)t=(t<<5)+t+e.charCodeAt(n)&4294967295;return(t>>>0).toString(16).padStart(8,"0")}function he(e){let t=[],n=e;for(;n;){let a=n.parentElement;if(!a){t.push(n.tagName.toLowerCase());break}let c=Array.prototype.indexOf.call(a.children,n);t.push(`${n.tagName.toLowerCase()}[${c}]`),n=a}return t.reverse().join("/")}function Se(e){var n;let t=(n=e.getAttribute("data-sentient-id"))!=null?n:e.getAttribute("id");return t||`${e.tagName.toLowerCase()}-${ge(he(e))}`}function ye(e){let t=0,n=e.parentElement;for(;n;)t++,n=n.parentElement;return t}function T(e,t){var a,c,i,o;let n=e.querySelector(ce);return{componentId:Se(e),semanticType:fe(e),ariaLabel:(a=e.getAttribute("aria-label"))!=null?a:void 0,headingText:(i=(c=n==null?void 0:n.textContent)==null?void 0:c.trim())!=null?i:void 0,isAboveFold:e.getBoundingClientRect().top<window.innerHeight,prominenceScore:t(e),depth:ye(e),reactComponentName:le(e),dataAttributes:me(e),locator:(o=w(e,e.ownerDocument))!=null?o:void 0}}function $(e){var i;let t=[],n=new Set,a="__root__",c=new Map;for(let[o,m]of e){let u=o.parentElement,s=a;for(;u;){if(e.has(u)){s=e.get(u);let f=e.get(o),g=`${s}->${f}`;!n.has(g)&&s!==f&&(n.add(g),t.push({fromComponentId:s,toComponentId:f,weight:.6}));break}u=u.parentElement}let d=(i=c.get(s))!=null?i:[];d.push(o),c.set(s,d)}for(let o of c.values()){if(o.length<2)continue;let m=o.length>P?o.slice(0,P):o;for(let u=0;u<m.length;u++)for(let s=u+1;s<m.length;s++){if(t.length>=pe)return t;let d=e.get(m[u]),f=e.get(m[s]);if(d===f)continue;let g=`${d}->${f}::sib`,r=`${f}->${d}::sib`;n.has(g)||(n.add(g),t.push({fromComponentId:d,toComponentId:f,weight:.3})),n.has(r)||(n.add(r),t.push({fromComponentId:f,toComponentId:d,weight:.3}))}}return t}function Ee(e){let t=[],n=new Set,a=new Map;return document.querySelectorAll("[data-sentient-id]").forEach(o=>{if(o instanceof Element&&!n.has(o)){n.add(o);let m=T(o,e);t.push(m),a.set(o,m.componentId)}}),document.querySelectorAll("section, article, main, aside").forEach(o=>{if(!(o instanceof Element)||n.has(o))return;let m=o.hasAttribute("aria-label"),u=o.hasAttribute("data-sentient-id");if(!m&&!u)return;n.add(o);let s=T(o,e);t.push(s),a.set(o,s.componentId)}),{nodes:t,edges:$(a),elementToId:a}}function L(){if(typeof window=="undefined")return ue;let e=null,t=0,n=null,a=new Map,c=u=>{try{let s=window.getComputedStyle(u),d=parseFloat(s.fontSize)||12,f=parseFloat(s.zIndex)||0,g=u.getBoundingClientRect(),r=Math.max(g.top,0),p=window.innerHeight||1,l=1/(r/p+1),h=k(d,12,48)*.4+l*.4+k(f,0,100)*.2;return Math.max(0,Math.min(1,h))}catch(s){return .5}};return{scan:()=>new Promise(u=>{let s=()=>{let{nodes:d,edges:f,elementToId:g}=Ee(c);a.clear();for(let[r,p]of g)a.set(r,p);u({nodes:d,edges:f,scannedAt:Date.now()})};try{typeof requestIdleCallback=="function"?t=requestIdleCallback(s,{timeout:100}):s()}catch(d){s()}}),observe:u=>{n=u;try{e=new MutationObserver(s=>{let d=[],f=new Set;for(let r of s)r.type==="childList"&&r.addedNodes.forEach(p=>{if(!(p instanceof Element))return;let l=[];ie.has(p.tagName)&&(p.hasAttribute("data-sentient-id")||p.hasAttribute("aria-label"))&&l.push(p);try{p.querySelectorAll(de).forEach(h=>l.push(h))}catch(h){}for(let h of l){if(a.has(h))continue;let S=T(h,c);d.push(S),f.add(S.componentId),a.set(h,S.componentId)}});if(d.length===0||!n)return;for(let r of[...a.keys()])r.isConnected||a.delete(r);let g=$(a).filter(r=>f.has(r.fromComponentId)||f.has(r.toComponentId));n({nodes:d,edges:g,addedAt:Date.now()})}),e.observe(document.body,{childList:!0,subtree:!0})}catch(s){}},getProminenceScore:c,destroy:()=>{if(e&&(e.disconnect(),e=null),t&&typeof cancelIdleCallback=="function")try{cancelIdleCallback(t)}catch(u){}t=0,n=null,a.clear()}}}var Ie="https://api.sentient-ui.com/v1/events";function Ce(e){var n;let t=a=>{try{let c=document.cookie.match(new RegExp(`(?:^|; )${a}=([^;]*)`));return c?decodeURIComponent(c[1]):void 0}catch(c){return}};return(n=e?t(b(e)):void 0)!=null?n:t(C)}var I=new Map;function be(e){var r;let t=O(e),n=e.respectDoNotTrack!==!1&&_(),a=e.consent===!1||n,c=typeof e.apiKey=="string"&&e.apiKey.startsWith("pk_"),i=e.localMode===!0||!c;if(!e.graph||i||typeof window=="undefined")return t;if(a)return n||x(e.apiKey,p=>be(p)),t;let o=I.get(e.apiKey);if(o)try{o()}catch(p){}let m=L(),u=(r=e.ingestUrl)!=null?r:Ie,s=D({syncUrl:u.replace(/\/events\/?$/,"/graph/sync"),apiKey:e.apiKey,projectId:e.apiKey,sessionId:Ce(e.apiKey)});m.scan().then(p=>{for(let l of p.nodes)s.addPageNode({id:l.componentId,componentId:l.componentId,semanticType:l.semanticType,answers:e.captureDomText&&l.headingText?[l.headingText]:[],prominenceScore:l.prominenceScore,depth:l.depth});for(let l of p.edges)s.addStructuralEdge(l);s.syncOnce()});let d=null,f=()=>{d!==null&&clearTimeout(d),d=setTimeout(()=>{d=null,s.syncOnce()},500)};m.observe(p=>{for(let l of p.nodes)s.addPageNode({id:l.componentId,componentId:l.componentId,semanticType:l.semanticType,answers:e.captureDomText&&l.headingText?[l.headingText]:[],prominenceScore:l.prominenceScore,depth:l.depth});for(let l of p.edges)s.addStructuralEdge(l);f()});let g=()=>{d!==null&&(clearTimeout(d),d=null),m.destroy(),s.destroy(),I.get(e.apiKey)===g&&I.delete(e.apiKey)};return I.set(e.apiKey,g),E(y({},t),{getGraph:()=>s.snapshot(),dispose:()=>{g(),t.dispose()},destroy:()=>{g(),t.destroy()}})}export{Te as BLOCK_ALIGNS,_e as BLOCK_EMPHASES,Me as BLOCK_FITS,we as BLOCK_GAPS,De as BLOCK_GRID_COLUMNS,Pe as BLOCK_HEADING_LEVELS,ve as BLOCK_JUSTIFIES,Re as BLOCK_RATIOS,Ae as BLOCK_SIZES,Oe as BLOCK_TEXT_ALIGNS,xe as BLOCK_TONES,Ne as BLOCK_WEIGHTS,C as LEGACY_SESSION_COOKIE_NAME,Le as MAX_BLOCK_ARMS,$e as MAX_BLOCK_CHILDREN,Ge as MAX_BLOCK_DEPTH,ke as MAX_BLOCK_NODES,je as MAX_BLOCK_TEXT_LEN,V as SNAPSHOT_STORAGE_KEY_PREFIX,Y as armOfResult,Q as attachMicroSignalDetectors,B as baselineResultFor,H as baselineSlots,q as deriveSessionSegment,j as detectDeviceClass,F as detectTimeOfDay,K as detectTrafficSource,Z as grantConsent,be as init,_ as isDoNotTrackEnabled,w as locatorFromElement,W as readSnapshot,U as referrerDomainFromReferer,X as renderPrePaintScript,M as sanitizePageUrl,b as sessionCookieName,z as toWireSlot,J as writeSnapshot};
2
2
  //# sourceMappingURL=index-graph.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/graph.ts","../src/scanner.ts","../src/index-graph.ts"],"sourcesContent":["/** In-memory context graph with persistence and backend sync. */\n\nimport { storageSuffix } from './storage-key.js';\n\nexport type PageNode = {\n id: string;\n componentId: string;\n semanticType: string;\n answers: string[];\n prominenceScore: number;\n depth: number;\n};\n\nexport type GraphSnapshot = {\n pageNodes: PageNode[];\n capturedAt: number;\n};\n\nexport type GraphConfig = {\n syncUrl?: string;\n apiKey?: string;\n projectId?: string;\n sessionId?: string;\n};\n\nexport type StructuralEdge = {\n fromComponentId: string;\n toComponentId: string;\n weight: number;\n};\n\nexport type GraphClient = {\n addPageNode(node: PageNode): void;\n /** Record a DOM-derived parent/child or sibling relationship between two components. */\n addStructuralEdge(edge: StructuralEdge): void;\n /** One-shot batch sync of all current page nodes to the backend. */\n syncOnce(): void;\n snapshot(): GraphSnapshot;\n serialize(): string;\n restore(data: string): void;\n destroy(): void;\n};\n\n// _snt_graph_edges was written by earlier builds but never synced to the backend.\n// We still read and clear any leftover key on init/destroy so old clients don't\n// accumulate stale data, but we no longer write it.\nconst STALE_EDGES_KEY = '_snt_graph_edges';\n\nconst SEMANTIC_NEIGHBOURS: Record<string, string[]> = {\n pricing: ['features', 'faq'],\n features: ['pricing'],\n faq: ['pricing'],\n social_proof: ['cta'],\n cta: ['social_proof', 'hero', 'trust'],\n hero: ['cta'],\n comparison: ['pricing'],\n trust: ['cta'],\n};\n\nfunction neighboursFor(semanticType: string): string[] {\n return SEMANTIC_NEIGHBOURS[semanticType] ?? [];\n}\n\nfunction readStorage<T>(key: string, fallback: T): T {\n try {\n const raw = localStorage.getItem(key);\n if (!raw) return fallback;\n return JSON.parse(raw) as T;\n } catch {\n return fallback;\n }\n}\n\nfunction writeStorage(key: string, value: unknown): void {\n try {\n localStorage.setItem(key, JSON.stringify(value));\n } catch {\n /* ignore */\n }\n}\n\nconst VALID_SEMANTIC_TYPES = new Set([\n 'pricing', 'hero', 'social_proof', 'cta', 'features',\n 'faq', 'comparison', 'trust', 'navigation', 'generic',\n]);\n\nfunction toValidSemanticType(type: string): string {\n return VALID_SEMANTIC_TYPES.has(type) ? type : 'generic';\n}\n\n/**\n * Path-only page URL for graph sync — strips query + fragment so tokens,\n * emails, and other sensitive URL params never leave the browser by default.\n */\nexport function sanitizePageUrl(href: string): string {\n try {\n const u = new URL(href);\n return `${u.origin}${u.pathname}`;\n } catch {\n return '/';\n }\n}\n\nfunction contentHashOf(componentId: string, semanticType: string, answers: string[]): string {\n const input = `${componentId}:${semanticType}:${answers.join(',')}`;\n let h = 5381;\n for (let i = 0; i < input.length; i++) {\n h = ((h << 5) + h + input.charCodeAt(i)) & 0xffffffff;\n }\n return (h >>> 0).toString(16).padStart(8, '0');\n}\n\n/**\n * Creates an in-memory graph client with optional persistence and sync.\n */\nexport function createGraphClient(config?: GraphConfig): GraphClient {\n const pageNodes = new Map<string, PageNode>();\n const structuralEdges = new Map<string, StructuralEdge>();\n\n // Per-project localStorage key so two projects on one origin don't share a\n // graph-node cache (see storage-key.ts). Legacy `_snt_graph_nodes` with no key.\n const nodesKey = `_snt_graph_nodes${storageSuffix(config?.apiKey)}`;\n\n const persist = (): void => {\n if (typeof window === 'undefined') return;\n writeStorage(nodesKey, [...pageNodes.values()]);\n };\n\n const restore = (data: string): void => {\n try {\n const parsed = JSON.parse(data) as { pageNodes?: PageNode[] };\n pageNodes.clear();\n for (const node of parsed.pageNodes ?? []) {\n pageNodes.set(node.componentId, node);\n }\n } catch {\n /* ignore corrupt state */\n }\n };\n\n if (typeof window !== 'undefined') {\n const storedNodes = readStorage<PageNode[]>(nodesKey, []);\n for (const node of storedNodes) {\n pageNodes.set(node.componentId, node);\n }\n // Clear any stale edge data written by older SDK versions.\n try { localStorage.removeItem(STALE_EDGES_KEY); } catch { /* ignore */ }\n }\n\n return {\n addPageNode(node: PageNode): void {\n pageNodes.set(node.componentId, node);\n persist();\n },\n\n addStructuralEdge(edge: StructuralEdge): void {\n const key = `${edge.fromComponentId}->${edge.toComponentId}`;\n structuralEdges.set(key, edge);\n },\n\n syncOnce(): void {\n if (!config?.syncUrl || typeof window === 'undefined') return;\n const nodes = [...pageNodes.values()];\n if (nodes.length === 0) return;\n try {\n // Build semantic edges from the SEMANTIC_NEIGHBOURS map. Each node emits\n // an edge to every present sibling that matches one of its neighbour types.\n // Weight matches propagate()'s 0.4 attention factor; confidence is high\n // because the mapping is curated, not inferred.\n const nodesByType = new Map<string, PageNode[]>();\n for (const n of nodes) {\n const list = nodesByType.get(n.semanticType) ?? [];\n list.push(n);\n nodesByType.set(n.semanticType, list);\n }\n const edges: Array<{\n fromComponentId: string;\n toComponentId: string;\n type: 'semantic' | 'structural';\n weight: number;\n confidence: number;\n }> = [];\n const seen = new Set<string>();\n for (const source of nodes) {\n for (const neighbourType of neighboursFor(source.semanticType)) {\n const targets = nodesByType.get(neighbourType) ?? [];\n for (const target of targets) {\n if (target.componentId === source.componentId) continue;\n const key = `semantic:${source.componentId}->${target.componentId}`;\n if (seen.has(key)) continue;\n seen.add(key);\n edges.push({\n fromComponentId: source.componentId,\n toComponentId: target.componentId,\n type: 'semantic',\n weight: 0.4,\n confidence: 0.9,\n });\n }\n }\n }\n\n // Structural edges from DOM relationships, recorded during scan.\n // Only emit when both endpoints are present in this page's node set —\n // a structural edge to/from a node that no longer exists would dangle.\n const componentIds = new Set(nodes.map((n) => n.componentId));\n for (const edge of structuralEdges.values()) {\n if (!componentIds.has(edge.fromComponentId) || !componentIds.has(edge.toComponentId)) continue;\n const key = `structural:${edge.fromComponentId}->${edge.toComponentId}`;\n if (seen.has(key)) continue;\n seen.add(key);\n edges.push({\n fromComponentId: edge.fromComponentId,\n toComponentId: edge.toComponentId,\n type: 'structural',\n weight: edge.weight,\n confidence: 1.0,\n });\n }\n\n const payload = {\n pageUrl: sanitizePageUrl(window.location.href),\n // Visitor/project attribution. The /graph/sync handler ignores unknown\n // top-level fields (Fastify additionalProperties + Zod strips extras),\n // so these ride along harmlessly and let beacons carry attribution.\n ...(config.sessionId ? { sessionId: config.sessionId } : {}),\n ...(config.projectId ? { projectId: config.projectId } : {}),\n nodes: nodes.map((n) => {\n const semanticType = toValidSemanticType(n.semanticType);\n return {\n componentId: n.componentId,\n semanticType,\n answers: n.answers,\n contentHash: contentHashOf(n.componentId, semanticType, n.answers),\n prominenceScore: n.prominenceScore,\n depthInPage: n.depth,\n };\n }),\n edges,\n };\n fetch(config.syncUrl, {\n method: 'POST',\n keepalive: true,\n headers: {\n 'Content-Type': 'application/json',\n ...(config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}),\n },\n body: JSON.stringify(payload),\n }).catch(() => undefined);\n } catch {\n /* ignore */\n }\n },\n\n snapshot(): GraphSnapshot {\n return {\n pageNodes: [...pageNodes.values()],\n capturedAt: Date.now(),\n };\n },\n\n serialize(): string {\n return JSON.stringify({ pageNodes: [...pageNodes.values()] });\n },\n\n restore,\n\n destroy(): void {\n if (typeof window === 'undefined') return;\n try {\n localStorage.removeItem(nodesKey);\n localStorage.removeItem(STALE_EDGES_KEY);\n } catch {\n /* ignore */\n }\n },\n };\n}\n","/** Reads the rendered DOM to build the page-side context graph. */\n\nimport { classifySection, SEMANTIC_TYPES, type SemanticType } from './engagement/classify';\n\nexport type ScannedNode = {\n componentId: string;\n semanticType: string;\n ariaLabel?: string;\n headingText?: string;\n isAboveFold: boolean;\n prominenceScore: number;\n depth: number;\n reactComponentName?: string;\n dataAttributes: Record<string, string>;\n};\n\nexport type StructuralEdge = {\n fromComponentId: string;\n toComponentId: string;\n /** 0.6 for direct parent → child, 0.3 for sibling (both directions emitted). */\n weight: number;\n};\n\nexport type ScanResult = {\n nodes: ScannedNode[];\n edges: StructuralEdge[];\n scannedAt: number;\n};\n\nexport type ContentAddedEvent = {\n nodes: ScannedNode[];\n edges: StructuralEdge[];\n addedAt: number;\n};\n\nexport type DOMScanner = {\n scan(): Promise<ScanResult>;\n observe(onContentAdded: (event: ContentAddedEvent) => void): void;\n getProminenceScore(element: Element): number;\n destroy(): void;\n};\n\nconst OBSERVE_TAGS = new Set(['SECTION', 'ARTICLE', 'MAIN', 'DIV']);\nconst HEADING_SELECTOR = 'h1, h2, h3';\n\n// Sibling detection is O(n²) per group and emits two edges per pair, so a page\n// with hundreds of co-located components (e.g. a 200-cell product grid) would\n// otherwise emit tens of thousands of low-signal sibling edges. We cap the\n// sibling fan-out per group and the total structural edges per detection pass.\n// The total stays well under the server's 2000-edge-per-sync limit, leaving room\n// for semantic edges. Parent→child edges (higher signal) are emitted first, so\n// the cap sheds sibling edges before it ever touches them.\nconst MAX_SIBLINGS_PER_GROUP = 30;\nconst MAX_STRUCTURAL_EDGES = 1500;\n\nconst SSR_SCANNER: DOMScanner = {\n scan: async () => ({ nodes: [], edges: [], scannedAt: 0 }),\n observe: () => undefined,\n getProminenceScore: () => 0,\n destroy: () => undefined,\n};\n\nfunction normalize(value: number, min: number, max: number): number {\n if (max <= min) return 0;\n return Math.max(0, Math.min(1, (value - min) / (max - min)));\n}\n\nfunction readReactComponentName(element: Element): string | undefined {\n try {\n const record = element as unknown as Record<string, unknown>;\n for (const key of Object.keys(record)) {\n if (!key.startsWith('__reactFiber') && !key.startsWith('__reactInternalInstance')) {\n continue;\n }\n const fiber = record[key] as { type?: { name?: string; displayName?: string } };\n const name = fiber?.type?.displayName ?? fiber?.type?.name;\n if (name && name.length > 1) {\n return name;\n }\n }\n } catch {\n /* ignore */\n }\n return undefined;\n}\n\nfunction extractDataAttributes(element: Element): Record<string, string> {\n const attrs: Record<string, string> = {};\n for (const attr of Array.from(element.attributes)) {\n if (attr.name.startsWith('data-')) {\n attrs[attr.name] = attr.value;\n }\n }\n return attrs;\n}\n\nconst SEMANTIC_SET: ReadonlySet<string> = new Set(SEMANTIC_TYPES);\n\n// Explicit tag wins → role → heuristic → generic. Every output is normalized to\n// the graph_nodes enum: an out-of-vocabulary data-sentient-type/role falls\n// through to the heuristic instead of being emitted raw (raw values violated\n// the graph_nodes CHECK constraint on insert).\nfunction inferSemanticType(element: Element): SemanticType {\n const explicit = element.getAttribute('data-sentient-type');\n if (explicit && SEMANTIC_SET.has(explicit)) return explicit as SemanticType;\n const role = element.getAttribute('role');\n if (role && SEMANTIC_SET.has(role)) return role as SemanticType;\n return classifySection(element);\n}\n\n// djb2 over a string → 8-hex-char digest. Stable and collision-resistant enough\n// to disambiguate co-located components that declare no id of their own.\nfunction shortHash(input: string): string {\n let h = 5381;\n for (let i = 0; i < input.length; i++) {\n h = ((h << 5) + h + input.charCodeAt(i)) & 0xffffffff;\n }\n return (h >>> 0).toString(16).padStart(8, '0');\n}\n\n// A structural path (tag + sibling index at each level up to the root) uniquely\n// and stably identifies an element's DOM position, so two id-less sections don't\n// hash to the same value the way a bare tagName would.\nfunction domPathSignature(element: Element): string {\n const parts: string[] = [];\n let current: Element | null = element;\n while (current) {\n const parent: Element | null = current.parentElement;\n if (!parent) {\n parts.push(current.tagName.toLowerCase());\n break;\n }\n const index = Array.prototype.indexOf.call(parent.children, current);\n parts.push(`${current.tagName.toLowerCase()}[${index}]`);\n current = parent;\n }\n return parts.reverse().join('/');\n}\n\n// Prefer an author-declared id. When none exists, synthesize a stable, unique id\n// from the element's DOM path — the old `tagName.toLowerCase()` fallback gave\n// every id-less <section> the same \"section\" id, so a second such node silently\n// overwrote the first in the componentId-keyed graph Map (node loss).\nfunction componentIdFor(element: Element): string {\n const declared =\n element.getAttribute('data-sentient-id') ?? element.getAttribute('id');\n if (declared) return declared;\n return `${element.tagName.toLowerCase()}-${shortHash(domPathSignature(element))}`;\n}\n\nfunction depthOf(element: Element): number {\n let depth = 0;\n let current: Element | null = element.parentElement;\n while (current) {\n depth++;\n current = current.parentElement;\n }\n return depth;\n}\n\nfunction scanElement(\n element: Element,\n getProminenceScore: (el: Element) => number,\n): ScannedNode {\n const heading = element.querySelector(HEADING_SELECTOR);\n return {\n componentId: componentIdFor(element),\n semanticType: inferSemanticType(element),\n ariaLabel: element.getAttribute('aria-label') ?? undefined,\n headingText: heading?.textContent?.trim() ?? undefined,\n isAboveFold: element.getBoundingClientRect().top < window.innerHeight,\n prominenceScore: getProminenceScore(element),\n depth: depthOf(element),\n reactComponentName: readReactComponentName(element),\n dataAttributes: extractDataAttributes(element),\n };\n}\n\n/**\n * Detects structural relationships between scanned component elements:\n * - parent → child (direct ancestor link, weight 0.6)\n * - sibling ↔ sibling (share nearest component ancestor, weight 0.3, bidirectional)\n * A pair appears at most once per direction.\n */\nfunction detectStructuralEdges(elementToId: Map<Element, string>): StructuralEdge[] {\n const edges: StructuralEdge[] = [];\n const seen = new Set<string>();\n const ROOT = '__root__';\n\n // Parent → child: nearest component ancestor only.\n const groups = new Map<string, Element[]>();\n for (const [el, _id] of elementToId) {\n let ancestor: Element | null = el.parentElement;\n let groupKey = ROOT;\n while (ancestor) {\n if (elementToId.has(ancestor)) {\n groupKey = elementToId.get(ancestor)!;\n const childId = elementToId.get(el)!;\n const key = `${groupKey}->${childId}`;\n if (!seen.has(key) && groupKey !== childId) {\n seen.add(key);\n edges.push({ fromComponentId: groupKey, toComponentId: childId, weight: 0.6 });\n }\n break;\n }\n ancestor = ancestor.parentElement;\n }\n const siblings = groups.get(groupKey) ?? [];\n siblings.push(el);\n groups.set(groupKey, siblings);\n }\n\n // Sibling ↔ sibling: same ancestor group.\n for (const sibs of groups.values()) {\n if (sibs.length < 2) continue;\n // Cap the fan-out of any single group before the O(n²) pairing.\n const capped = sibs.length > MAX_SIBLINGS_PER_GROUP ? sibs.slice(0, MAX_SIBLINGS_PER_GROUP) : sibs;\n for (let i = 0; i < capped.length; i++) {\n for (let j = i + 1; j < capped.length; j++) {\n if (edges.length >= MAX_STRUCTURAL_EDGES) return edges;\n const aId = elementToId.get(capped[i]!)!;\n const bId = elementToId.get(capped[j]!)!;\n if (aId === bId) continue;\n const fwd = `${aId}->${bId}::sib`;\n const rev = `${bId}->${aId}::sib`;\n if (!seen.has(fwd)) {\n seen.add(fwd);\n edges.push({ fromComponentId: aId, toComponentId: bId, weight: 0.3 });\n }\n if (!seen.has(rev)) {\n seen.add(rev);\n edges.push({ fromComponentId: bId, toComponentId: aId, weight: 0.3 });\n }\n }\n }\n }\n\n return edges;\n}\n\nfunction collectNodesAndEdges(\n getProminenceScore: (el: Element) => number,\n): { nodes: ScannedNode[]; edges: StructuralEdge[]; elementToId: Map<Element, string> } {\n const nodes: ScannedNode[] = [];\n const seen = new Set<Element>();\n const elementToId = new Map<Element, string>();\n\n const registered = document.querySelectorAll('[data-sentient-id]');\n registered.forEach((el) => {\n if (el instanceof Element && !seen.has(el)) {\n seen.add(el);\n const node = scanElement(el, getProminenceScore);\n nodes.push(node);\n elementToId.set(el, node.componentId);\n }\n });\n\n const structural = document.querySelectorAll('section, article, main, aside');\n structural.forEach((el) => {\n if (!(el instanceof Element) || seen.has(el)) return;\n const hasAria = el.hasAttribute('aria-label');\n const hasSentientId = el.hasAttribute('data-sentient-id');\n if (!hasAria && !hasSentientId) return;\n seen.add(el);\n const node = scanElement(el, getProminenceScore);\n nodes.push(node);\n elementToId.set(el, node.componentId);\n });\n\n return { nodes, edges: detectStructuralEdges(elementToId), elementToId };\n}\n\n/**\n * Creates a DOM scanner that uses idle callbacks and mutation observation.\n */\nexport function createDOMScanner(): DOMScanner {\n if (typeof window === 'undefined') {\n return SSR_SCANNER;\n }\n\n let observer: MutationObserver | null = null;\n let idleCallbackId = 0;\n let contentCallback: ((event: ContentAddedEvent) => void) | null = null;\n // Element→componentId for every node registered so far (initial scan + prior\n // mutations). The MutationObserver detects edges against this whole set — not\n // just the nodes in the current mutation — so a child inserted under an\n // already-scanned parent still gets its parent→child edge.\n const knownElementToId = new Map<Element, string>();\n\n const getProminenceScore = (element: Element): number => {\n try {\n const styles = window.getComputedStyle(element);\n const fontSize = parseFloat(styles.fontSize) || 12;\n const zIndex = parseFloat(styles.zIndex) || 0;\n const rect = element.getBoundingClientRect();\n const distanceFromTop = Math.max(rect.top, 0);\n const viewportHeight = window.innerHeight || 1;\n const inverseDistance = 1 / (distanceFromTop / viewportHeight + 1);\n\n // inverseDistance is already in (0, 1] by construction, so it needs no\n // further normalization — only fontSize and zIndex are rescaled.\n const score =\n normalize(fontSize, 12, 48) * 0.4 +\n inverseDistance * 0.4 +\n normalize(zIndex, 0, 100) * 0.2;\n\n return Math.max(0, Math.min(1, score));\n } catch {\n return 0.5;\n }\n };\n\n const scan = (): Promise<ScanResult> =>\n new Promise((resolve) => {\n const run = (): void => {\n const { nodes, edges, elementToId } = collectNodesAndEdges(getProminenceScore);\n // Seed the known-element registry so the observer can resolve parents that\n // were registered in this scan when later children are inserted.\n knownElementToId.clear();\n for (const [el, id] of elementToId) knownElementToId.set(el, id);\n resolve({ nodes, edges, scannedAt: Date.now() });\n };\n\n try {\n if (typeof requestIdleCallback === 'function') {\n idleCallbackId = requestIdleCallback(run, { timeout: 100 });\n } else {\n run();\n }\n } catch {\n run();\n }\n });\n\n const observe = (onContentAdded: (event: ContentAddedEvent) => void): void => {\n contentCallback = onContentAdded;\n try {\n observer = new MutationObserver((mutations) => {\n const added: ScannedNode[] = [];\n const addedIds = new Set<string>();\n for (const mutation of mutations) {\n if (mutation.type !== 'childList') continue;\n mutation.addedNodes.forEach((node) => {\n if (!(node instanceof Element)) return;\n if (!OBSERVE_TAGS.has(node.tagName)) return;\n const hasId = node.hasAttribute('data-sentient-id');\n const hasAria = node.hasAttribute('aria-label');\n if (!hasId && !hasAria) return;\n const scanned = scanElement(node, getProminenceScore);\n added.push(scanned);\n addedIds.add(scanned.componentId);\n knownElementToId.set(node, scanned.componentId);\n });\n }\n if (added.length === 0 || !contentCallback) return;\n // Drop elements no longer in the document so removed nodes don't dangle\n // and the map stays bounded.\n for (const el of [...knownElementToId.keys()]) {\n if (!el.isConnected) knownElementToId.delete(el);\n }\n // Detect over the FULL known set so a child inserted under an already-\n // scanned parent still gets its parent→child edge, then surface only the\n // edges that touch a newly-added node (existing edges were already emitted).\n const edges = detectStructuralEdges(knownElementToId).filter(\n (e) => addedIds.has(e.fromComponentId) || addedIds.has(e.toComponentId),\n );\n contentCallback({ nodes: added, edges, addedAt: Date.now() });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n } catch {\n /* ignore */\n }\n };\n\n const destroy = (): void => {\n if (observer) {\n observer.disconnect();\n observer = null;\n }\n if (idleCallbackId && typeof cancelIdleCallback === 'function') {\n try {\n cancelIdleCallback(idleCallbackId);\n } catch {\n /* ignore */\n }\n }\n idleCallbackId = 0;\n contentCallback = null;\n knownElementToId.clear();\n };\n\n return {\n scan,\n observe,\n getProminenceScore,\n destroy,\n };\n}\n","/**\n * Graph-capable entry point for @sentientui/core.\n *\n * Import from `@sentientui/core/graph` when you need DOM graph scanning and\n * page-structure sync. This entry pulls in `scanner.ts` and `graph.ts` at\n * build time, giving bundlers a real tree-shaking boundary. Standard A/B tests\n * should use `@sentientui/core` (the lean entry) instead.\n */\n\n// Re-export everything from the lean entry except `init`, which we override below.\nexport {\n detectDeviceClass,\n detectTrafficSource,\n detectTimeOfDay,\n deriveSessionSegment,\n referrerDomainFromReferer,\n} from './index.js';\n// SSR preload helpers moved to `@sentientui/core/server` in 0.6.0.\nexport type {\n SentientConfig,\n AssignResult,\n SentientClient,\n} from './index.js';\nexport type {\n SessionConfig,\n SessionManager,\n EventType,\n SentientEvent,\n QueueConfig,\n Assignment,\n} from './index.js';\n// EventQueue/AssignmentCache are no longer on the lean barrel — source them from\n// their own modules so the `/graph` subpath keeps exposing them unchanged.\nexport type { EventQueue } from './queue.js';\nexport type { AssignmentCache } from './cache.js';\n// Scanner + graph types (live in this entry only).\nexport type {\n ScannedNode,\n ScanResult,\n ContentAddedEvent,\n DOMScanner,\n} from './scanner.js';\nexport type {\n PageNode,\n GraphSnapshot,\n GraphConfig,\n GraphClient,\n} from './graph.js';\nexport { sanitizePageUrl } from './graph.js';\n\nimport { init as initLean, isDoNotTrackEnabled, type SentientConfig, type SentientClient } from './index.js';\nimport { LEGACY_SESSION_COOKIE_NAME, sessionCookieName } from './storage-key.js';\n\nconst DEFAULT_INGEST_URL = 'https://api.sentient-ui.com/v1/events';\nimport { createDOMScanner } from './scanner.js';\nimport { createGraphClient } from './graph.js';\n\nexport type GraphSentientConfig = SentientConfig & {\n /**\n * Enable DOM graph scanning and page-structure sync. Wires a MutationObserver\n * and a localStorage-backed graph. When `false` (default) this entry behaves\n * identically to `@sentientui/core`.\n */\n graph?: boolean;\n /**\n * Include captured heading / DOM text in graph sync payloads. OFF by default —\n * headings can contain account names or user-generated content. Structure\n * (component ids, semantic types, prominence) still syncs when `graph: true`.\n */\n captureDomText?: boolean;\n};\n\n// The client writes the per-project SUFFIXED cookie (sessionCookieName in\n// storage-key.ts). This reader kept the bare `_snt_uid` after namespacing\n// landed, so graph sync sent `sessionId: undefined` for every keyed project.\n// The bare name stays as a fallback for pre-namespacing identities.\nfunction readSntUid(apiKey?: string): string | undefined {\n const read = (name: string): string | undefined => {\n try {\n // Cookie names are `_snt_uid` + `_` + a pk_ key prefix — no regex\n // metacharacters, so interpolation is safe (same pattern as session.ts).\n const m = document.cookie.match(new RegExp(`(?:^|; )${name}=([^;]*)`));\n return m ? decodeURIComponent(m[1]!) : undefined;\n } catch {\n return undefined;\n }\n };\n return (apiKey ? read(sessionCookieName(apiKey)) : undefined) ?? read(LEGACY_SESSION_COOKIE_NAME);\n}\n\n/**\n * Graph-capable variant of `init()`. Identical to the lean `init` when\n * `config.graph` is `false` (or omitted). When `config.graph: true`, mounts\n * a DOM scanner + graph client and enables `client.getGraph()`.\n *\n * Import from `@sentientui/core/graph` — do not call the lean `init` alongside\n * this function as that would initialise two clients.\n */\n// Teardown for the graph resources (scanner + MutationObserver + debounce\n// timer + graph client) bound to each apiKey. initLean already disposes the\n// lean client's own timers/listeners on re-init, but it knows nothing about\n// these graph resources, so this entry tracks and tears them down itself —\n// otherwise an HMR / consent-toggle / provider-remount re-init would leak a\n// live MutationObserver and a pending sync timer per mount.\nconst _graphTeardowns = new Map<string, () => void>();\n\nexport function init(config: GraphSentientConfig): SentientClient {\n const client = initLean(config);\n\n // Mirror initLean's opt-out gate: a DNT/GPC visitor (or consent:false) must\n // not get the DOM scanner + graph sync, which POST page-structure beacons and\n // read `_snt_uid` — tracking that bypasses the lean client's own gating, even\n // under `preConsentBehavior: 'statistical_winner'` (audit P1).\n const dntBlocked = config.respectDoNotTrack !== false && isDoNotTrackEnabled();\n const gated = config.consent === false || dntBlocked;\n\n // Keyless zero-network contract: with no api key there is nothing to feed —\n // the scanner must never mount and /v1/graph/sync must never fire.\n if (!config.graph || !config.apiKey || gated || typeof window === 'undefined') return client;\n\n // A prior graph mount for this key is now superseded — tear it down first so\n // its observer/timer don't leak alongside the new mount's.\n const prevTeardown = _graphTeardowns.get(config.apiKey);\n if (prevTeardown) {\n try {\n prevTeardown();\n } catch {\n /* teardown must never throw on re-init */\n }\n }\n\n const domScanner = createDOMScanner();\n const resolvedIngestUrl = config.ingestUrl ?? DEFAULT_INGEST_URL;\n const graphClient = createGraphClient({\n syncUrl: resolvedIngestUrl.replace(/\\/events\\/?$/, '/graph/sync'),\n apiKey: config.apiKey,\n projectId: config.apiKey,\n sessionId: readSntUid(config.apiKey),\n });\n\n // Persisted page-node state from a previous page load is restored by the graph\n // client's own constructor (it reads `_snt_graph_nodes` on creation). We no\n // longer re-read the key and call restore() here — that was a redundant second\n // load path that cleared and reloaded identical data.\n\n void domScanner.scan().then((result) => {\n for (const node of result.nodes) {\n graphClient.addPageNode({\n id: node.componentId,\n componentId: node.componentId,\n semanticType: node.semanticType,\n answers: config.captureDomText && node.headingText ? [node.headingText] : [],\n prominenceScore: node.prominenceScore,\n depth: node.depth,\n });\n }\n for (const edge of result.edges) {\n graphClient.addStructuralEdge(edge);\n }\n graphClient.syncOnce();\n });\n\n let syncDebounceTimer: ReturnType<typeof setTimeout> | null = null;\n const debouncedSync = (): void => {\n if (syncDebounceTimer !== null) clearTimeout(syncDebounceTimer);\n syncDebounceTimer = setTimeout(() => {\n syncDebounceTimer = null;\n graphClient.syncOnce();\n }, 500);\n };\n\n domScanner.observe((event) => {\n for (const node of event.nodes) {\n graphClient.addPageNode({\n id: node.componentId,\n componentId: node.componentId,\n semanticType: node.semanticType,\n answers: config.captureDomText && node.headingText ? [node.headingText] : [],\n prominenceScore: node.prominenceScore,\n depth: node.depth,\n });\n }\n for (const edge of event.edges) {\n graphClient.addStructuralEdge(edge);\n }\n debouncedSync();\n });\n\n // Tears down only the graph resources; the caller pairs it with the lean\n // client's own dispose/destroy. Idempotent, and de-registers itself so a\n // later re-init or teardown can't run it twice on already-freed resources.\n const teardownGraph = (): void => {\n if (syncDebounceTimer !== null) {\n clearTimeout(syncDebounceTimer);\n syncDebounceTimer = null;\n }\n domScanner.destroy();\n graphClient.destroy();\n if (_graphTeardowns.get(config.apiKey) === teardownGraph) {\n _graphTeardowns.delete(config.apiKey);\n }\n };\n _graphTeardowns.set(config.apiKey, teardownGraph);\n\n return {\n ...client,\n getGraph: () => graphClient.snapshot(),\n dispose: () => {\n teardownGraph();\n client.dispose();\n },\n destroy: () => {\n teardownGraph();\n client.destroy();\n },\n };\n}\n"],"mappings":"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","names":["STALE_EDGES_KEY","SEMANTIC_NEIGHBOURS","neighboursFor","semanticType","_a","readStorage","key","fallback","raw","e","writeStorage","value","VALID_SEMANTIC_TYPES","toValidSemanticType","type","sanitizePageUrl","href","u","contentHashOf","componentId","answers","input","h","i","createGraphClient","config","pageNodes","structuralEdges","nodesKey","storageSuffix","persist","restore","data","parsed","node","storedNodes","edge","_b","nodes","nodesByType","n","list","edges","seen","source","neighbourType","targets","target","componentIds","payload","__spreadProps","__spreadValues","OBSERVE_TAGS","HEADING_SELECTOR","MAX_SIBLINGS_PER_GROUP","MAX_STRUCTURAL_EDGES","SSR_SCANNER","normalize","value","min","max","readReactComponentName","element","_a","_b","_c","record","key","fiber","name","e","extractDataAttributes","attrs","attr","SEMANTIC_SET","SEMANTIC_TYPES","inferSemanticType","explicit","role","classifySection","shortHash","input","h","i","domPathSignature","parts","current","parent","index","componentIdFor","declared","depthOf","depth","scanElement","getProminenceScore","heading","detectStructuralEdges","elementToId","edges","seen","ROOT","groups","el","_id","ancestor","groupKey","childId","siblings","sibs","capped","j","aId","bId","fwd","rev","collectNodesAndEdges","nodes","node","hasAria","hasSentientId","createDOMScanner","observer","idleCallbackId","contentCallback","knownElementToId","styles","fontSize","zIndex","rect","distanceFromTop","viewportHeight","inverseDistance","score","resolve","run","id","onContentAdded","mutations","added","addedIds","mutation","hasId","scanned","DEFAULT_INGEST_URL","readSntUid","apiKey","_a","read","name","m","e","sessionCookieName","LEGACY_SESSION_COOKIE_NAME","_graphTeardowns","init","config","client","dntBlocked","isDoNotTrackEnabled","gated","prevTeardown","domScanner","createDOMScanner","resolvedIngestUrl","graphClient","createGraphClient","result","node","edge","syncDebounceTimer","debouncedSync","event","teardownGraph","__spreadProps","__spreadValues"]}
1
+ {"version":3,"sources":["../src/graph.ts","../src/scanner.ts","../src/index-graph.ts"],"sourcesContent":["/** In-memory context graph with persistence and backend sync. */\n\nimport { storageSuffix } from './storage-key.js';\n\nexport type PageNode = {\n id: string;\n componentId: string;\n semanticType: string;\n answers: string[];\n prominenceScore: number;\n depth: number;\n};\n\nexport type GraphSnapshot = {\n pageNodes: PageNode[];\n capturedAt: number;\n};\n\nexport type GraphConfig = {\n syncUrl?: string;\n apiKey?: string;\n projectId?: string;\n sessionId?: string;\n};\n\nexport type StructuralEdge = {\n fromComponentId: string;\n toComponentId: string;\n weight: number;\n};\n\nexport type GraphClient = {\n addPageNode(node: PageNode): void;\n /** Record a DOM-derived parent/child or sibling relationship between two components. */\n addStructuralEdge(edge: StructuralEdge): void;\n /** One-shot batch sync of all current page nodes to the backend. */\n syncOnce(): void;\n snapshot(): GraphSnapshot;\n destroy(): void;\n};\n\n// _snt_graph_edges was written by earlier builds but never synced to the backend.\n// We still read and clear any leftover key on init/destroy so old clients don't\n// accumulate stale data, but we no longer write it.\nconst STALE_EDGES_KEY = '_snt_graph_edges';\n\nconst SEMANTIC_NEIGHBOURS: Record<string, string[]> = {\n pricing: ['features', 'faq'],\n features: ['pricing'],\n faq: ['pricing'],\n social_proof: ['cta'],\n cta: ['social_proof', 'hero', 'trust'],\n hero: ['cta'],\n comparison: ['pricing'],\n trust: ['cta'],\n};\n\nfunction neighboursFor(semanticType: string): string[] {\n return SEMANTIC_NEIGHBOURS[semanticType] ?? [];\n}\n\nfunction readStorage<T>(key: string, fallback: T): T {\n try {\n const raw = localStorage.getItem(key);\n if (!raw) return fallback;\n return JSON.parse(raw) as T;\n } catch {\n return fallback;\n }\n}\n\nfunction writeStorage(key: string, value: unknown): void {\n try {\n localStorage.setItem(key, JSON.stringify(value));\n } catch {\n /* ignore */\n }\n}\n\nconst VALID_SEMANTIC_TYPES = new Set([\n 'pricing', 'hero', 'social_proof', 'cta', 'features',\n 'faq', 'comparison', 'trust', 'navigation', 'generic',\n]);\n\nfunction toValidSemanticType(type: string): string {\n return VALID_SEMANTIC_TYPES.has(type) ? type : 'generic';\n}\n\n/**\n * Path-only page URL for graph sync — strips query + fragment so tokens,\n * emails, and other sensitive URL params never leave the browser by default.\n */\nexport function sanitizePageUrl(href: string): string {\n try {\n const u = new URL(href);\n return `${u.origin}${u.pathname}`;\n } catch {\n return '/';\n }\n}\n\nfunction contentHashOf(componentId: string, semanticType: string, answers: string[]): string {\n const input = `${componentId}:${semanticType}:${answers.join(',')}`;\n let h = 5381;\n for (let i = 0; i < input.length; i++) {\n h = ((h << 5) + h + input.charCodeAt(i)) & 0xffffffff;\n }\n return (h >>> 0).toString(16).padStart(8, '0');\n}\n\n/**\n * Creates an in-memory graph client with optional persistence and sync.\n */\nexport function createGraphClient(config?: GraphConfig): GraphClient {\n const pageNodes = new Map<string, PageNode>();\n const structuralEdges = new Map<string, StructuralEdge>();\n\n // Per-project localStorage key so two projects on one origin don't share a\n // graph-node cache (see storage-key.ts). Legacy `_snt_graph_nodes` with no key.\n const nodesKey = `_snt_graph_nodes${storageSuffix(config?.apiKey)}`;\n\n const persist = (): void => {\n if (typeof window === 'undefined') return;\n writeStorage(nodesKey, [...pageNodes.values()]);\n };\n\n // serialize()/restore() were removed here: persisted-node restoration moved\n // into this constructor (below), leaving both dead — yet still shipping in\n // the graph bundle.\n if (typeof window !== 'undefined') {\n const storedNodes = readStorage<PageNode[]>(nodesKey, []);\n for (const node of storedNodes) {\n pageNodes.set(node.componentId, node);\n }\n // Clear any stale edge data written by older SDK versions.\n try { localStorage.removeItem(STALE_EDGES_KEY); } catch { /* ignore */ }\n }\n\n return {\n addPageNode(node: PageNode): void {\n pageNodes.set(node.componentId, node);\n persist();\n },\n\n addStructuralEdge(edge: StructuralEdge): void {\n const key = `${edge.fromComponentId}->${edge.toComponentId}`;\n structuralEdges.set(key, edge);\n },\n\n syncOnce(): void {\n if (!config?.syncUrl || typeof window === 'undefined') return;\n const nodes = [...pageNodes.values()];\n if (nodes.length === 0) return;\n try {\n // Build semantic edges from the SEMANTIC_NEIGHBOURS map. Each node emits\n // an edge to every present sibling that matches one of its neighbour types.\n // Weight matches propagate()'s 0.4 attention factor; confidence is high\n // because the mapping is curated, not inferred.\n const nodesByType = new Map<string, PageNode[]>();\n for (const n of nodes) {\n const list = nodesByType.get(n.semanticType) ?? [];\n list.push(n);\n nodesByType.set(n.semanticType, list);\n }\n const edges: Array<{\n fromComponentId: string;\n toComponentId: string;\n type: 'semantic' | 'structural';\n weight: number;\n confidence: number;\n }> = [];\n const seen = new Set<string>();\n for (const source of nodes) {\n for (const neighbourType of neighboursFor(source.semanticType)) {\n const targets = nodesByType.get(neighbourType) ?? [];\n for (const target of targets) {\n if (target.componentId === source.componentId) continue;\n const key = `semantic:${source.componentId}->${target.componentId}`;\n if (seen.has(key)) continue;\n seen.add(key);\n edges.push({\n fromComponentId: source.componentId,\n toComponentId: target.componentId,\n type: 'semantic',\n weight: 0.4,\n confidence: 0.9,\n });\n }\n }\n }\n\n // Structural edges from DOM relationships, recorded during scan.\n // Only emit when both endpoints are present in this page's node set —\n // a structural edge to/from a node that no longer exists would dangle.\n const componentIds = new Set(nodes.map((n) => n.componentId));\n for (const edge of structuralEdges.values()) {\n if (!componentIds.has(edge.fromComponentId) || !componentIds.has(edge.toComponentId)) continue;\n const key = `structural:${edge.fromComponentId}->${edge.toComponentId}`;\n if (seen.has(key)) continue;\n seen.add(key);\n edges.push({\n fromComponentId: edge.fromComponentId,\n toComponentId: edge.toComponentId,\n type: 'structural',\n weight: edge.weight,\n confidence: 1.0,\n });\n }\n\n const payload = {\n pageUrl: sanitizePageUrl(window.location.href),\n // Visitor/project attribution. The /graph/sync handler ignores unknown\n // top-level fields (Fastify additionalProperties + Zod strips extras),\n // so these ride along harmlessly and let beacons carry attribution.\n ...(config.sessionId ? { sessionId: config.sessionId } : {}),\n ...(config.projectId ? { projectId: config.projectId } : {}),\n nodes: nodes.map((n) => {\n const semanticType = toValidSemanticType(n.semanticType);\n return {\n componentId: n.componentId,\n semanticType,\n answers: n.answers,\n contentHash: contentHashOf(n.componentId, semanticType, n.answers),\n prominenceScore: n.prominenceScore,\n depthInPage: n.depth,\n };\n }),\n edges,\n };\n fetch(config.syncUrl, {\n method: 'POST',\n keepalive: true,\n headers: {\n 'Content-Type': 'application/json',\n ...(config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}),\n },\n body: JSON.stringify(payload),\n }).catch(() => undefined);\n } catch {\n /* ignore */\n }\n },\n\n snapshot(): GraphSnapshot {\n return {\n pageNodes: [...pageNodes.values()],\n capturedAt: Date.now(),\n };\n },\n\n destroy(): void {\n if (typeof window === 'undefined') return;\n try {\n localStorage.removeItem(nodesKey);\n localStorage.removeItem(STALE_EDGES_KEY);\n } catch {\n /* ignore */\n }\n },\n };\n}\n","/** Reads the rendered DOM to build the page-side context graph. */\n\nimport { classifySection, SEMANTIC_TYPES, type SemanticType } from './engagement/classify';\nimport { locatorFromElement } from './locator-from-dom.js';\nimport type { CompoundLocator } from './snapshot.js';\n\nexport type ScannedNode = {\n componentId: string;\n semanticType: string;\n ariaLabel?: string;\n headingText?: string;\n isAboveFold: boolean;\n prominenceScore: number;\n depth: number;\n reactComponentName?: string;\n dataAttributes: Record<string, string>;\n /** Compound locator for this element. The server hashes it into section_key —\n * the identity the crawler and the snippet resolve to for the same physical\n * section. Undefined when nothing resolves uniquely: the runtime never\n * guesses an identity it could not verify later. */\n locator?: CompoundLocator;\n};\n\nexport type StructuralEdge = {\n fromComponentId: string;\n toComponentId: string;\n /** 0.6 for direct parent → child, 0.3 for sibling (both directions emitted). */\n weight: number;\n};\n\nexport type ScanResult = {\n nodes: ScannedNode[];\n edges: StructuralEdge[];\n scannedAt: number;\n};\n\nexport type ContentAddedEvent = {\n nodes: ScannedNode[];\n edges: StructuralEdge[];\n addedAt: number;\n};\n\nexport type DOMScanner = {\n scan(): Promise<ScanResult>;\n observe(onContentAdded: (event: ContentAddedEvent) => void): void;\n getProminenceScore(element: Element): number;\n destroy(): void;\n};\n\n// ASIDE included to match the initial scan (which queries `section, article,\n// main, aside`) — this set omitted it, so a server-rendered aside was captured\n// while an identical dynamically-inserted one was silently ignored.\nconst OBSERVE_TAGS = new Set(['SECTION', 'ARTICLE', 'MAIN', 'DIV', 'ASIDE']);\nconst HEADING_SELECTOR = 'h1, h2, h3';\n// Selector mirror of the initial scan's criteria (collectNodesAndEdges): any\n// element carrying a declared id, plus structural tags with an aria-label.\n// Used to walk INTO inserted subtrees — a SPA mounts ONE root node whose\n// interesting sections are all descendants, and inspecting only the root made\n// every framework-mounted component permanently invisible to graph capture.\nconst SUBTREE_SELECTOR =\n '[data-sentient-id], section[aria-label], article[aria-label], main[aria-label], aside[aria-label]';\n\n// Sibling detection is O(n²) per group and emits two edges per pair, so a page\n// with hundreds of co-located components (e.g. a 200-cell product grid) would\n// otherwise emit tens of thousands of low-signal sibling edges. We cap the\n// sibling fan-out per group and the total structural edges per detection pass.\n// The total stays well under the server's 2000-edge-per-sync limit, leaving room\n// for semantic edges. Parent→child edges (higher signal) are emitted first, so\n// the cap sheds sibling edges before it ever touches them.\nconst MAX_SIBLINGS_PER_GROUP = 30;\nconst MAX_STRUCTURAL_EDGES = 1500;\n\nconst SSR_SCANNER: DOMScanner = {\n scan: async () => ({ nodes: [], edges: [], scannedAt: 0 }),\n observe: () => undefined,\n getProminenceScore: () => 0,\n destroy: () => undefined,\n};\n\nfunction normalize(value: number, min: number, max: number): number {\n if (max <= min) return 0;\n return Math.max(0, Math.min(1, (value - min) / (max - min)));\n}\n\nfunction readReactComponentName(element: Element): string | undefined {\n try {\n const record = element as unknown as Record<string, unknown>;\n for (const key of Object.keys(record)) {\n if (!key.startsWith('__reactFiber') && !key.startsWith('__reactInternalInstance')) {\n continue;\n }\n const fiber = record[key] as { type?: { name?: string; displayName?: string } };\n const name = fiber?.type?.displayName ?? fiber?.type?.name;\n if (name && name.length > 1) {\n return name;\n }\n }\n } catch {\n /* ignore */\n }\n return undefined;\n}\n\nfunction extractDataAttributes(element: Element): Record<string, string> {\n const attrs: Record<string, string> = {};\n for (const attr of Array.from(element.attributes)) {\n if (attr.name.startsWith('data-')) {\n attrs[attr.name] = attr.value;\n }\n }\n return attrs;\n}\n\nconst SEMANTIC_SET: ReadonlySet<string> = new Set(SEMANTIC_TYPES);\n\n// Explicit tag wins → role → heuristic → generic. Every output is normalized to\n// the graph_nodes enum: an out-of-vocabulary data-sentient-type/role falls\n// through to the heuristic instead of being emitted raw (raw values violated\n// the graph_nodes CHECK constraint on insert).\nfunction inferSemanticType(element: Element): SemanticType {\n const explicit = element.getAttribute('data-sentient-type');\n if (explicit && SEMANTIC_SET.has(explicit)) return explicit as SemanticType;\n const role = element.getAttribute('role');\n if (role && SEMANTIC_SET.has(role)) return role as SemanticType;\n return classifySection(element);\n}\n\n// djb2 over a string → 8-hex-char digest. Stable and collision-resistant enough\n// to disambiguate co-located components that declare no id of their own.\nfunction shortHash(input: string): string {\n let h = 5381;\n for (let i = 0; i < input.length; i++) {\n h = ((h << 5) + h + input.charCodeAt(i)) & 0xffffffff;\n }\n return (h >>> 0).toString(16).padStart(8, '0');\n}\n\n// A structural path (tag + sibling index at each level up to the root) uniquely\n// and stably identifies an element's DOM position, so two id-less sections don't\n// hash to the same value the way a bare tagName would.\nfunction domPathSignature(element: Element): string {\n const parts: string[] = [];\n let current: Element | null = element;\n while (current) {\n const parent: Element | null = current.parentElement;\n if (!parent) {\n parts.push(current.tagName.toLowerCase());\n break;\n }\n const index = Array.prototype.indexOf.call(parent.children, current);\n parts.push(`${current.tagName.toLowerCase()}[${index}]`);\n current = parent;\n }\n return parts.reverse().join('/');\n}\n\n// Prefer an author-declared id. When none exists, synthesize a stable, unique id\n// from the element's DOM path — the old `tagName.toLowerCase()` fallback gave\n// every id-less <section> the same \"section\" id, so a second such node silently\n// overwrote the first in the componentId-keyed graph Map (node loss).\nfunction componentIdFor(element: Element): string {\n const declared =\n element.getAttribute('data-sentient-id') ?? element.getAttribute('id');\n if (declared) return declared;\n return `${element.tagName.toLowerCase()}-${shortHash(domPathSignature(element))}`;\n}\n\nfunction depthOf(element: Element): number {\n let depth = 0;\n let current: Element | null = element.parentElement;\n while (current) {\n depth++;\n current = current.parentElement;\n }\n return depth;\n}\n\nfunction scanElement(\n element: Element,\n getProminenceScore: (el: Element) => number,\n): ScannedNode {\n const heading = element.querySelector(HEADING_SELECTOR);\n return {\n componentId: componentIdFor(element),\n semanticType: inferSemanticType(element),\n ariaLabel: element.getAttribute('aria-label') ?? undefined,\n headingText: heading?.textContent?.trim() ?? undefined,\n isAboveFold: element.getBoundingClientRect().top < window.innerHeight,\n prominenceScore: getProminenceScore(element),\n depth: depthOf(element),\n reactComponentName: readReactComponentName(element),\n dataAttributes: extractDataAttributes(element),\n locator: locatorFromElement(element, element.ownerDocument) ?? undefined,\n };\n}\n\n/**\n * Detects structural relationships between scanned component elements:\n * - parent → child (direct ancestor link, weight 0.6)\n * - sibling ↔ sibling (share nearest component ancestor, weight 0.3, bidirectional)\n * A pair appears at most once per direction.\n */\nfunction detectStructuralEdges(elementToId: Map<Element, string>): StructuralEdge[] {\n const edges: StructuralEdge[] = [];\n const seen = new Set<string>();\n const ROOT = '__root__';\n\n // Parent → child: nearest component ancestor only.\n const groups = new Map<string, Element[]>();\n for (const [el, _id] of elementToId) {\n let ancestor: Element | null = el.parentElement;\n let groupKey = ROOT;\n while (ancestor) {\n if (elementToId.has(ancestor)) {\n groupKey = elementToId.get(ancestor)!;\n const childId = elementToId.get(el)!;\n const key = `${groupKey}->${childId}`;\n if (!seen.has(key) && groupKey !== childId) {\n seen.add(key);\n edges.push({ fromComponentId: groupKey, toComponentId: childId, weight: 0.6 });\n }\n break;\n }\n ancestor = ancestor.parentElement;\n }\n const siblings = groups.get(groupKey) ?? [];\n siblings.push(el);\n groups.set(groupKey, siblings);\n }\n\n // Sibling ↔ sibling: same ancestor group.\n for (const sibs of groups.values()) {\n if (sibs.length < 2) continue;\n // Cap the fan-out of any single group before the O(n²) pairing.\n const capped = sibs.length > MAX_SIBLINGS_PER_GROUP ? sibs.slice(0, MAX_SIBLINGS_PER_GROUP) : sibs;\n for (let i = 0; i < capped.length; i++) {\n for (let j = i + 1; j < capped.length; j++) {\n if (edges.length >= MAX_STRUCTURAL_EDGES) return edges;\n const aId = elementToId.get(capped[i]!)!;\n const bId = elementToId.get(capped[j]!)!;\n if (aId === bId) continue;\n const fwd = `${aId}->${bId}::sib`;\n const rev = `${bId}->${aId}::sib`;\n if (!seen.has(fwd)) {\n seen.add(fwd);\n edges.push({ fromComponentId: aId, toComponentId: bId, weight: 0.3 });\n }\n if (!seen.has(rev)) {\n seen.add(rev);\n edges.push({ fromComponentId: bId, toComponentId: aId, weight: 0.3 });\n }\n }\n }\n }\n\n return edges;\n}\n\nfunction collectNodesAndEdges(\n getProminenceScore: (el: Element) => number,\n): { nodes: ScannedNode[]; edges: StructuralEdge[]; elementToId: Map<Element, string> } {\n const nodes: ScannedNode[] = [];\n const seen = new Set<Element>();\n const elementToId = new Map<Element, string>();\n\n const registered = document.querySelectorAll('[data-sentient-id]');\n registered.forEach((el) => {\n if (el instanceof Element && !seen.has(el)) {\n seen.add(el);\n const node = scanElement(el, getProminenceScore);\n nodes.push(node);\n elementToId.set(el, node.componentId);\n }\n });\n\n const structural = document.querySelectorAll('section, article, main, aside');\n structural.forEach((el) => {\n if (!(el instanceof Element) || seen.has(el)) return;\n const hasAria = el.hasAttribute('aria-label');\n const hasSentientId = el.hasAttribute('data-sentient-id');\n if (!hasAria && !hasSentientId) return;\n seen.add(el);\n const node = scanElement(el, getProminenceScore);\n nodes.push(node);\n elementToId.set(el, node.componentId);\n });\n\n return { nodes, edges: detectStructuralEdges(elementToId), elementToId };\n}\n\n/**\n * Creates a DOM scanner that uses idle callbacks and mutation observation.\n */\nexport function createDOMScanner(): DOMScanner {\n if (typeof window === 'undefined') {\n return SSR_SCANNER;\n }\n\n let observer: MutationObserver | null = null;\n let idleCallbackId = 0;\n let contentCallback: ((event: ContentAddedEvent) => void) | null = null;\n // Element→componentId for every node registered so far (initial scan + prior\n // mutations). The MutationObserver detects edges against this whole set — not\n // just the nodes in the current mutation — so a child inserted under an\n // already-scanned parent still gets its parent→child edge.\n const knownElementToId = new Map<Element, string>();\n\n const getProminenceScore = (element: Element): number => {\n try {\n const styles = window.getComputedStyle(element);\n const fontSize = parseFloat(styles.fontSize) || 12;\n const zIndex = parseFloat(styles.zIndex) || 0;\n const rect = element.getBoundingClientRect();\n const distanceFromTop = Math.max(rect.top, 0);\n const viewportHeight = window.innerHeight || 1;\n const inverseDistance = 1 / (distanceFromTop / viewportHeight + 1);\n\n // inverseDistance is already in (0, 1] by construction, so it needs no\n // further normalization — only fontSize and zIndex are rescaled.\n const score =\n normalize(fontSize, 12, 48) * 0.4 +\n inverseDistance * 0.4 +\n normalize(zIndex, 0, 100) * 0.2;\n\n return Math.max(0, Math.min(1, score));\n } catch {\n return 0.5;\n }\n };\n\n const scan = (): Promise<ScanResult> =>\n new Promise((resolve) => {\n const run = (): void => {\n const { nodes, edges, elementToId } = collectNodesAndEdges(getProminenceScore);\n // Seed the known-element registry so the observer can resolve parents that\n // were registered in this scan when later children are inserted.\n knownElementToId.clear();\n for (const [el, id] of elementToId) knownElementToId.set(el, id);\n resolve({ nodes, edges, scannedAt: Date.now() });\n };\n\n try {\n if (typeof requestIdleCallback === 'function') {\n idleCallbackId = requestIdleCallback(run, { timeout: 100 });\n } else {\n run();\n }\n } catch {\n run();\n }\n });\n\n const observe = (onContentAdded: (event: ContentAddedEvent) => void): void => {\n contentCallback = onContentAdded;\n try {\n observer = new MutationObserver((mutations) => {\n const added: ScannedNode[] = [];\n const addedIds = new Set<string>();\n for (const mutation of mutations) {\n if (mutation.type !== 'childList') continue;\n mutation.addedNodes.forEach((node) => {\n if (!(node instanceof Element)) return;\n const candidates: Element[] = [];\n if (\n OBSERVE_TAGS.has(node.tagName) &&\n (node.hasAttribute('data-sentient-id') || node.hasAttribute('aria-label'))\n ) {\n candidates.push(node);\n }\n // Also scan the inserted SUBTREE with the initial scan's criteria\n // (see SUBTREE_SELECTOR): the root of a SPA/framework mount is\n // usually a plain wrapper, and its sections arrive as descendants\n // of ONE childList mutation — inspecting only the root meant they\n // were never captured at all.\n try {\n node.querySelectorAll(SUBTREE_SELECTOR).forEach((el) => candidates.push(el));\n } catch {\n /* exotic host without querySelectorAll — root-only scan stands */\n }\n for (const el of candidates) {\n // Skip elements already registered: a parent and its child can\n // both appear in addedNodes (the child once via the parent's\n // subtree, once directly), which would emit duplicate nodes.\n if (knownElementToId.has(el)) continue;\n const scanned = scanElement(el, getProminenceScore);\n added.push(scanned);\n addedIds.add(scanned.componentId);\n knownElementToId.set(el, scanned.componentId);\n }\n });\n }\n if (added.length === 0 || !contentCallback) return;\n // Drop elements no longer in the document so removed nodes don't dangle\n // and the map stays bounded.\n for (const el of [...knownElementToId.keys()]) {\n if (!el.isConnected) knownElementToId.delete(el);\n }\n // Detect over the FULL known set so a child inserted under an already-\n // scanned parent still gets its parent→child edge, then surface only the\n // edges that touch a newly-added node (existing edges were already emitted).\n const edges = detectStructuralEdges(knownElementToId).filter(\n (e) => addedIds.has(e.fromComponentId) || addedIds.has(e.toComponentId),\n );\n contentCallback({ nodes: added, edges, addedAt: Date.now() });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n } catch {\n /* ignore */\n }\n };\n\n const destroy = (): void => {\n if (observer) {\n observer.disconnect();\n observer = null;\n }\n if (idleCallbackId && typeof cancelIdleCallback === 'function') {\n try {\n cancelIdleCallback(idleCallbackId);\n } catch {\n /* ignore */\n }\n }\n idleCallbackId = 0;\n contentCallback = null;\n knownElementToId.clear();\n };\n\n return {\n scan,\n observe,\n getProminenceScore,\n destroy,\n };\n}\n","/**\n * Graph-capable entry point for @sentientui/core.\n *\n * Import from `@sentientui/core/graph` when you need DOM graph scanning and\n * page-structure sync. This entry pulls in `scanner.ts` and `graph.ts` at\n * build time, giving bundlers a real tree-shaking boundary. Standard A/B tests\n * should use `@sentientui/core` (the lean entry) instead.\n */\n\n// Re-export everything from the lean entry except `init`, which we override\n// below — so a graph consumer never needs dual-entry imports (importing the\n// lean entry alongside this one risks initialising two clients).\nexport {\n detectDeviceClass,\n detectTrafficSource,\n detectTimeOfDay,\n deriveSessionSegment,\n referrerDomainFromReferer,\n grantConsent,\n} from './index.js';\n// Sourced from their own modules (identical bindings to the lean barrel's):\n// snapshot/pre-paint helpers, slot helpers, blocks, micro-signals, and the\n// session cookie name — all were missing here, which made the comment above\n// a lie and forced consumers into dual-entry imports.\nexport {\n SNAPSHOT_STORAGE_KEY_PREFIX,\n readSnapshot,\n writeSnapshot,\n renderPrePaintScript,\n} from './snapshot.js';\nexport type { DecisionSnapshot, SlotConfigEntry, SlotOps, CompoundLocator } from './snapshot.js';\nexport { armOfResult, baselineResultFor, baselineSlots, toWireSlot } from './slots.js';\nexport type { SlotDeclInput, SlotResult } from './slots.js';\nexport * from './blocks.js';\nexport { attachMicroSignalDetectors } from './micro-signals.js';\nexport type { MicroSignalEmitter, MicroSignalType } from './micro-signals.js';\nexport { sessionCookieName, LEGACY_SESSION_COOKIE_NAME } from './storage-key.js';\n// SSR preload helpers moved to `@sentientui/core/server` in 0.6.0.\nexport type {\n SentientConfig,\n AssignResult,\n SentientClient,\n} from './index.js';\nexport type {\n SessionConfig,\n SessionManager,\n EventType,\n SentientEvent,\n QueueConfig,\n Assignment,\n} from './index.js';\n// EventQueue/AssignmentCache are no longer on the lean barrel — source them from\n// their own modules so the `/graph` subpath keeps exposing them unchanged.\nexport type { EventQueue } from './queue.js';\nexport type { AssignmentCache } from './cache.js';\n// Scanner + graph types (live in this entry only).\nexport type {\n ScannedNode,\n ScanResult,\n ContentAddedEvent,\n DOMScanner,\n} from './scanner.js';\nexport type {\n PageNode,\n GraphSnapshot,\n GraphConfig,\n GraphClient,\n} from './graph.js';\nexport { sanitizePageUrl } from './graph.js';\nexport { locatorFromElement } from './locator-from-dom.js';\n\nimport {\n init as initLean,\n isDoNotTrackEnabled,\n _registerConsentUpgradeInit,\n type SentientConfig,\n type SentientClient,\n} from './index.js';\nexport { isDoNotTrackEnabled };\nimport { LEGACY_SESSION_COOKIE_NAME, sessionCookieName } from './storage-key.js';\n\nconst DEFAULT_INGEST_URL = 'https://api.sentient-ui.com/v1/events';\nimport { createDOMScanner } from './scanner.js';\nimport { createGraphClient } from './graph.js';\n\nexport type GraphSentientConfig = SentientConfig & {\n /**\n * Enable DOM graph scanning and page-structure sync. Wires a MutationObserver\n * and a localStorage-backed graph. When `false` (default) this entry behaves\n * identically to `@sentientui/core`.\n */\n graph?: boolean;\n /**\n * Include captured heading / DOM text in graph sync payloads. OFF by default —\n * headings can contain account names or user-generated content. Structure\n * (component ids, semantic types, prominence) still syncs when `graph: true`.\n */\n captureDomText?: boolean;\n};\n\n// The client writes the per-project SUFFIXED cookie (sessionCookieName in\n// storage-key.ts). This reader kept the bare `_snt_uid` after namespacing\n// landed, so graph sync sent `sessionId: undefined` for every keyed project.\n// The bare name stays as a fallback for pre-namespacing identities.\nfunction readSntUid(apiKey?: string): string | undefined {\n const read = (name: string): string | undefined => {\n try {\n // Cookie names are `_snt_uid` + `_` + a pk_ key prefix — no regex\n // metacharacters, so interpolation is safe (same pattern as session.ts).\n const m = document.cookie.match(new RegExp(`(?:^|; )${name}=([^;]*)`));\n return m ? decodeURIComponent(m[1]!) : undefined;\n } catch {\n return undefined;\n }\n };\n return (apiKey ? read(sessionCookieName(apiKey)) : undefined) ?? read(LEGACY_SESSION_COOKIE_NAME);\n}\n\n/**\n * Graph-capable variant of `init()`. Identical to the lean `init` when\n * `config.graph` is `false` (or omitted). When `config.graph: true`, mounts\n * a DOM scanner + graph client and enables `client.getGraph()`.\n *\n * Import from `@sentientui/core/graph` — do not call the lean `init` alongside\n * this function as that would initialise two clients.\n */\n// Teardown for the graph resources (scanner + MutationObserver + debounce\n// timer + graph client) bound to each apiKey. initLean already disposes the\n// lean client's own timers/listeners on re-init, but it knows nothing about\n// these graph resources, so this entry tracks and tears them down itself —\n// otherwise an HMR / consent-toggle / provider-remount re-init would leak a\n// live MutationObserver and a pending sync timer per mount.\nconst _graphTeardowns = new Map<string, () => void>();\n\nexport function init(config: GraphSentientConfig): SentientClient {\n const client = initLean(config);\n\n // Mirror initLean's opt-out gate: a DNT/GPC visitor (or consent:false) must\n // not get the DOM scanner + graph sync, which POST page-structure beacons and\n // read `_snt_uid` — tracking that bypasses the lean client's own gating, even\n // under `preConsentBehavior: 'statistical_winner'` (audit P1).\n const dntBlocked = config.respectDoNotTrack !== false && isDoNotTrackEnabled();\n const gated = config.consent === false || dntBlocked;\n\n // Zero-network contract, mirroring the lean init's own gate: `localMode:\n // true` forces the on-device engine, and a missing OR INVALID (non-`pk_`)\n // key means the lean client is keyless-local or disabled. This used to check\n // only `!apiKey`, so a typo'd key — which the React provider's default\n // `graph: true` reaches — still mounted the scanner and POSTed\n // /v1/graph/sync into a client that discards everything.\n const keyValid = typeof config.apiKey === 'string' && config.apiKey.startsWith('pk_');\n const zeroNetwork = config.localMode === true || !keyValid;\n if (!config.graph || zeroNetwork || typeof window === 'undefined') return client;\n\n if (gated) {\n // Consent may still be granted later: grantConsent() must re-init through\n // THIS entry so the post-consent client mounts the scanner — the lean init\n // it upgraded through before knows nothing about graph resources, so a\n // consent grant used to lose graph capture for the session. DNT-blocked\n // clients register nothing: consent cannot override a global opt-out.\n if (!dntBlocked) {\n _registerConsentUpgradeInit(config.apiKey, (c) => init(c as GraphSentientConfig));\n }\n return client;\n }\n\n // A prior graph mount for this key is now superseded — tear it down first so\n // its observer/timer don't leak alongside the new mount's.\n const prevTeardown = _graphTeardowns.get(config.apiKey);\n if (prevTeardown) {\n try {\n prevTeardown();\n } catch {\n /* teardown must never throw on re-init */\n }\n }\n\n const domScanner = createDOMScanner();\n const resolvedIngestUrl = config.ingestUrl ?? DEFAULT_INGEST_URL;\n const graphClient = createGraphClient({\n syncUrl: resolvedIngestUrl.replace(/\\/events\\/?$/, '/graph/sync'),\n apiKey: config.apiKey,\n projectId: config.apiKey,\n sessionId: readSntUid(config.apiKey),\n });\n\n // Persisted page-node state from a previous page load is restored by the graph\n // client's own constructor (it reads `_snt_graph_nodes` on creation). We no\n // longer re-read the key and call restore() here — that was a redundant second\n // load path that cleared and reloaded identical data.\n\n void domScanner.scan().then((result) => {\n for (const node of result.nodes) {\n graphClient.addPageNode({\n id: node.componentId,\n componentId: node.componentId,\n semanticType: node.semanticType,\n answers: config.captureDomText && node.headingText ? [node.headingText] : [],\n prominenceScore: node.prominenceScore,\n depth: node.depth,\n });\n }\n for (const edge of result.edges) {\n graphClient.addStructuralEdge(edge);\n }\n graphClient.syncOnce();\n });\n\n let syncDebounceTimer: ReturnType<typeof setTimeout> | null = null;\n const debouncedSync = (): void => {\n if (syncDebounceTimer !== null) clearTimeout(syncDebounceTimer);\n syncDebounceTimer = setTimeout(() => {\n syncDebounceTimer = null;\n graphClient.syncOnce();\n }, 500);\n };\n\n domScanner.observe((event) => {\n for (const node of event.nodes) {\n graphClient.addPageNode({\n id: node.componentId,\n componentId: node.componentId,\n semanticType: node.semanticType,\n answers: config.captureDomText && node.headingText ? [node.headingText] : [],\n prominenceScore: node.prominenceScore,\n depth: node.depth,\n });\n }\n for (const edge of event.edges) {\n graphClient.addStructuralEdge(edge);\n }\n debouncedSync();\n });\n\n // Tears down only the graph resources; the caller pairs it with the lean\n // client's own dispose/destroy. Idempotent, and de-registers itself so a\n // later re-init or teardown can't run it twice on already-freed resources.\n const teardownGraph = (): void => {\n if (syncDebounceTimer !== null) {\n clearTimeout(syncDebounceTimer);\n syncDebounceTimer = null;\n }\n domScanner.destroy();\n graphClient.destroy();\n if (_graphTeardowns.get(config.apiKey) === teardownGraph) {\n _graphTeardowns.delete(config.apiKey);\n }\n };\n _graphTeardowns.set(config.apiKey, teardownGraph);\n\n return {\n ...client,\n getGraph: () => graphClient.snapshot(),\n dispose: () => {\n teardownGraph();\n client.dispose();\n },\n destroy: () => {\n teardownGraph();\n client.destroy();\n },\n };\n}\n"],"mappings":"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","names":["STALE_EDGES_KEY","SEMANTIC_NEIGHBOURS","neighboursFor","semanticType","_a","readStorage","key","fallback","raw","e","writeStorage","value","VALID_SEMANTIC_TYPES","toValidSemanticType","type","sanitizePageUrl","href","u","contentHashOf","componentId","answers","input","h","createGraphClient","config","pageNodes","structuralEdges","nodesKey","storageSuffix","persist","storedNodes","node","edge","_b","nodes","nodesByType","n","list","edges","seen","source","neighbourType","targets","target","componentIds","payload","__spreadProps","__spreadValues","OBSERVE_TAGS","HEADING_SELECTOR","SUBTREE_SELECTOR","MAX_SIBLINGS_PER_GROUP","MAX_STRUCTURAL_EDGES","SSR_SCANNER","normalize","value","min","max","readReactComponentName","element","_a","_b","_c","record","key","fiber","name","e","extractDataAttributes","attrs","attr","SEMANTIC_SET","SEMANTIC_TYPES","inferSemanticType","explicit","role","classifySection","shortHash","input","h","i","domPathSignature","parts","current","parent","index","componentIdFor","declared","depthOf","depth","scanElement","getProminenceScore","_d","heading","locatorFromElement","detectStructuralEdges","elementToId","edges","seen","ROOT","groups","el","_id","ancestor","groupKey","childId","siblings","sibs","capped","j","aId","bId","fwd","rev","collectNodesAndEdges","nodes","node","hasAria","hasSentientId","createDOMScanner","observer","idleCallbackId","contentCallback","knownElementToId","styles","fontSize","zIndex","rect","distanceFromTop","viewportHeight","inverseDistance","score","resolve","run","id","onContentAdded","mutations","added","addedIds","mutation","candidates","scanned","DEFAULT_INGEST_URL","readSntUid","apiKey","_a","read","name","m","e","sessionCookieName","LEGACY_SESSION_COOKIE_NAME","_graphTeardowns","init","config","client","dntBlocked","isDoNotTrackEnabled","gated","keyValid","zeroNetwork","_registerConsentUpgradeInit","c","prevTeardown","domScanner","createDOMScanner","resolvedIngestUrl","graphClient","createGraphClient","result","node","edge","syncDebounceTimer","debouncedSync","event","teardownGraph","__spreadProps","__spreadValues"]}
@@ -1,4 +1,4 @@
1
- import { G as DecideOutcome } from './index-D3_vlZ3z.cjs';
1
+ import { G as DecideOutcome } from './index-CXxvWxCB.cjs';
2
2
  import { b as SlotDeclInput } from './session-meta-BVvq5RBB.cjs';
3
3
  import '@sentientui/policy';
4
4
 
@@ -1,4 +1,4 @@
1
- import { G as DecideOutcome } from './index-BcRtGYVH.js';
1
+ import { G as DecideOutcome } from './index-BbrtAtrY.js';
2
2
  import { b as SlotDeclInput } from './session-meta-BVvq5RBB.js';
3
3
  import '@sentientui/policy';
4
4
 
@@ -0,0 +1,21 @@
1
+ import { T as TopicRule, a as SectionFeatures } from './classify-DYlSjkFP.cjs';
2
+ export { b as SemanticType } from './classify-DYlSjkFP.cjs';
3
+
4
+ declare const CLASSIFIER_TOPICS: readonly TopicRule[];
5
+ /** Which classifier stage produced the label. Persisted as provenance (the
6
+ * `page_semantics.source` column distinguishes structured_data from
7
+ * heuristic), so it must name the stage that actually MATCHED — a section
8
+ * that merely CONTAINS a JSON-LD blob but was classified by its `<nav>` tag
9
+ * is heuristic evidence, not the site telling us what it is. */
10
+ type ClassifierStage = 'structural' | 'structured_data' | 'keyword' | 'content' | 'fallback';
11
+ /**
12
+ * Classify a section into a topic, its parent, and its functional role.
13
+ * Structure and fallbacks are the shared implementation from ./classify.ts, so
14
+ * the browser cannot disagree with the server about what a `<nav>` is.
15
+ */
16
+ declare function classifyTopic(f: SectionFeatures): TopicRule & {
17
+ strength: 'strong' | 'weak';
18
+ stage: ClassifierStage;
19
+ };
20
+
21
+ export { CLASSIFIER_TOPICS, SectionFeatures, TopicRule, classifyTopic };
@@ -0,0 +1,21 @@
1
+ import { T as TopicRule, a as SectionFeatures } from './classify-DYlSjkFP.js';
2
+ export { b as SemanticType } from './classify-DYlSjkFP.js';
3
+
4
+ declare const CLASSIFIER_TOPICS: readonly TopicRule[];
5
+ /** Which classifier stage produced the label. Persisted as provenance (the
6
+ * `page_semantics.source` column distinguishes structured_data from
7
+ * heuristic), so it must name the stage that actually MATCHED — a section
8
+ * that merely CONTAINS a JSON-LD blob but was classified by its `<nav>` tag
9
+ * is heuristic evidence, not the site telling us what it is. */
10
+ type ClassifierStage = 'structural' | 'structured_data' | 'keyword' | 'content' | 'fallback';
11
+ /**
12
+ * Classify a section into a topic, its parent, and its functional role.
13
+ * Structure and fallbacks are the shared implementation from ./classify.ts, so
14
+ * the browser cannot disagree with the server about what a `<nav>` is.
15
+ */
16
+ declare function classifyTopic(f: SectionFeatures): TopicRule & {
17
+ strength: 'strong' | 'weak';
18
+ stage: ClassifierStage;
19
+ };
20
+
21
+ export { CLASSIFIER_TOPICS, SectionFeatures, TopicRule, classifyTopic };
@@ -0,0 +1,2 @@
1
+ "use strict";var p=Object.defineProperty,y=Object.defineProperties,h=Object.getOwnPropertyDescriptor,w=Object.getOwnPropertyDescriptors,v=Object.getOwnPropertyNames,l=Object.getOwnPropertySymbols;var g=Object.prototype.hasOwnProperty,T=Object.prototype.propertyIsEnumerable;var b=(e,r,t)=>r in e?p(e,r,{enumerable:!0,configurable:!0,writable:!0,value:t}):e[r]=t,n=(e,r)=>{for(var t in r||(r={}))g.call(r,t)&&b(e,t,r[t]);if(l)for(var t of l(r))T.call(r,t)&&b(e,t,r[t]);return e},i=(e,r)=>y(e,w(r));var S=(e,r)=>{for(var t in r)p(e,t,{get:r[t],enumerable:!0})},x=(e,r,t,s)=>{if(r&&typeof r=="object"||typeof r=="function")for(let o of v(r))!g.call(e,o)&&o!==t&&p(e,o,{get:()=>r[o],enumerable:!(s=h(r,o))||s.enumerable});return e};var _=e=>x(p({},"__esModule",{value:!0}),e);var E={};S(E,{CLASSIFIER_TOPICS:()=>u,classifyTopic:()=>m});module.exports=_(E);var k={banner:"hero",navigation:"navigation",contentinfo:"footer"};function d(e){let r=e.ariaRole?k[e.ariaRole.toLowerCase()]:void 0;if(r)return r;if(e.tag==="nav")return"navigation";if(e.tag==="footer")return"footer";if(e.tag==="header")return"hero";let t=`${e.idClass} ${e.headingText}`.toLowerCase();return/\b(navbar|nav-bar|navigation|site-nav|main-nav|topbar|footer)\b/.test(t)?/footer/.test(t)?"footer":"navigation":/\b(hero|masthead|jumbotron)\b/i.test(e.idClass)?"hero":null}function f(e){return e.actionCount>=1&&e.textLength>0&&e.textLength<200?"cta":"generic"}var u=[{topic:"navigation",parent:"navigation",role:"structural"},{topic:"footer",parent:"navigation",role:"structural"},{topic:"breadcrumb",parent:"navigation",role:"structural"},{topic:"cookie_banner",parent:"navigation",role:"structural"},{topic:"hero",parent:"hero",role:"converter"},{topic:"pricing_plans",parent:"pricing",role:"converter"},{topic:"quote",parent:"pricing",role:"converter"},{topic:"financing",parent:"pricing",role:"persuader"},{topic:"faq",parent:"faq",role:"persuader"},{topic:"comparison",parent:"comparison",role:"persuader"},{topic:"reviews",parent:"social_proof",role:"persuader"},{topic:"testimonials",parent:"social_proof",role:"persuader"},{topic:"case_study",parent:"social_proof",role:"persuader"},{topic:"brands",parent:"social_proof",role:"persuader"},{topic:"awards",parent:"social_proof",role:"persuader"},{topic:"gallery",parent:"social_proof",role:"persuader"},{topic:"stats",parent:"social_proof",role:"persuader"},{topic:"insurance",parent:"trust",role:"persuader"},{topic:"warranty",parent:"trust",role:"persuader"},{topic:"certifications",parent:"trust",role:"persuader"},{topic:"security",parent:"trust",role:"persuader"},{topic:"guarantee",parent:"trust",role:"persuader"},{topic:"about",parent:"trust",role:"persuader"},{topic:"team",parent:"trust",role:"persuader"},{topic:"services",parent:"features",role:"persuader"},{topic:"features",parent:"features",role:"persuader"},{topic:"process",parent:"features",role:"persuader"},{topic:"menu",parent:"features",role:"persuader"},{topic:"inventory",parent:"features",role:"converter"},{topic:"integrations",parent:"features",role:"persuader"},{topic:"booking",parent:"cta",role:"converter"},{topic:"contact_form",parent:"cta",role:"converter"},{topic:"newsletter",parent:"cta",role:"converter"},{topic:"hours",parent:"cta",role:"persuader"},{topic:"location",parent:"cta",role:"persuader"},{topic:"cta",parent:"cta",role:"converter"},{topic:"blog",parent:"generic",role:"persuader"},{topic:"careers",parent:"generic",role:"persuader"},{topic:"legal",parent:"generic",role:"persuader"},{topic:"generic",parent:"generic",role:"persuader"}],C=new Map(u.map(e=>[e.topic,e]));function c(e){var r;return(r=C.get(e))!=null?r:{topic:"generic",parent:"generic",role:"persuader"}}var R=[["cookie_banner",/\bcookies?\b[^.]{0,20}\b(banner|notice|consent|policy)\b|consent banner/i],["breadcrumb",/\bbreadcrumbs?\b/i],["insurance",/\b(insurance|insurer|insured)\b/i],["warranty",/\b(warrant(y|ies)|guarantees?|guaranteed)\b/i],["certifications",/\b(certifi|accredit|licen[cs]ed)\b|approved by|iso ?\d/i],["security",/\b(security|privacy|compliance|gdpr|encrypt)\b|soc ?2/i],["about",/why choose|why us|about us|our story|who we are|years? (of )?(experience|serving)/i],["team",/our team|meet the team|our people|our staff/i],["reviews",/\b(reviews?|ratings?|testimonial)\b|what our customers say/i],["brands",/\b(brands?|partners?|manufacturers?|logos)\b|we work with|trusted by|loved by/i],["gallery",/\b(galler(y|ies)|photos?)\b|before and after/i],["case_study",/case stud|selected work|portfolio|our projects?|recent work/i],["awards",/\b(awards?|accolades?)\b/i],["pricing_plans",/\b(pricing|price list|per month|\/mo|subscriptions?)\b|\bplans?\b(?=[^.]{0,40}(from|start|month|year|[$€£]))/i],["quote",/get a quote|request a quote|free estimate|get an estimate/i],["financing",/\b(financ(e|ing)|instal?ments?)\b|payment plan|pay monthly/i],["booking",/\b(book|booking|reserve|reservation|appointments?)\b|schedule a/i],["newsletter",/\b(newsletter|subscribe)\b|join our (list|mailing)|mailing list/i],["contact_form",/contact us|get in touch|start a project|\benquir|\binquir|send us/i],["hours",/opening hours|\bhours\b|open (mon|tue|wed|thu|fri|sat|sun)/i],["location",/find us|our location|directions|where we are|visit us/i],["menu",/\b(menus?|dishes)\b|our food/i],["inventory",/\bbest ?sellers?\b|\b(shop|products?|collection)\b|our range|in stock/i],["process",/our process|how it works|the process|what to expect|\bsteps?\b/i],["services",/\b(services?|repairs?|treatments?|specialit|specialt)\b|what we (do|offer)/i],["integrations",/\bintegrations?\b|works with|connects? to/i],["features",/\b(features?|benefits?|capabilit)\b|what you get/i],["faq",/\bfaq\b|frequently asked|common questions?/i],["comparison",/\b(compare|comparison|versus)\b|\bvs\./i],["blog",/\b(blog|news|articles?|insights?)\b/i],["careers",/\b(careers?|jobs?)\b|join (the|our) team|we.re hiring/i],["legal",/\bterms\b|privacy policy|cookie policy|\blegal\b/i]],q={FAQPage:"faq",Question:"faq",Review:"reviews",AggregateRating:"reviews",Offer:"pricing_plans",AggregateOffer:"pricing_plans",PriceSpecification:"pricing_plans",Service:"services",Product:"inventory",ItemList:"inventory",Menu:"menu",MenuSection:"menu",LocalBusiness:"location",PostalAddress:"location",OpeningHoursSpecification:"hours",BreadcrumbList:"breadcrumb",ImageGallery:"gallery",JobPosting:"careers",BlogPosting:"blog",Article:"blog",Organization:"about"},A=[["pricing_plans",/(?:[$€£]\s?\d[\d,.]*\s*(?:\/|per\s)\s*(?:mo|month|yr|year|seat|user))|(?:\b(?:starter|basic|pro|growth|premium|enterprise)\b[^.]{0,60}[$€£]\s?\d)/i],["reviews",/(?:★{2,})|(?:\b\d(?:\.\d)?\s*(?:out of|\/)\s*5\b)|(?:\brated\b)|(?:["“][^"”]{20,160}["”]\s*[—–-]\s*[A-Z][a-z]+)/],["guarantee",/\b(?:money[- ]back guarantee|free returns?|returns? within|refund(?:s)? within|cancel anytime)\b/i],["security",/\b(?:encrypted|soc ?2|iso ?27001|gdpr[- ]compliant)\b/i],["comparison",/\b(?:vs|versus)\b\.?[^.!?]{0,80}\b(?:compare|comparison|plans?|features?|alternative)\b|\bhow (?:we|it) compares?\b/i]];function m(e){var s;let r=d(e);if(r)return i(n({},c(r)),{strength:"strong",stage:"structural"});for(let o of(s=e.structuredTypes)!=null?s:[]){let a=q[o];if(a)return i(n({},c(a)),{strength:"strong",stage:"structured_data"})}let t=`${e.idClass} ${e.headingText}`.toLowerCase();for(let[o,a]of R)if(a.test(t))return i(n({},c(o)),{strength:"strong",stage:"keyword"});for(let[o,a]of A)if(a.test(e.bodyText))return i(n({},c(o)),{strength:"strong",stage:"content"});return i(n({},c(f(e))),{strength:"weak",stage:"fallback"})}0&&(module.exports={CLASSIFIER_TOPICS,classifyTopic});
2
+ //# sourceMappingURL=index-topics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index-topics.ts","../src/engagement/classify.ts","../src/engagement/topics.ts"],"sourcesContent":["// SERVER-ONLY entry for the rich section-topic vocabulary.\n//\n// Separate from ./engagement on purpose: the topic tables cost ~1.5 KB gzip,\n// which is 7% of the always-on snippet, on every page view of every customer\n// site — and the browser never reads them (classifySection returns only the\n// parent). Only the site-audit classification job imports this.\nexport {\n classifyTopic,\n CLASSIFIER_TOPICS,\n type TopicRule,\n} from './engagement/topics';\nexport type { SectionFeatures, SemanticType } from './engagement/classify';\n","// Semantic section classification for no-code section capture (Phase 3 §2.4).\n// Pure heuristic: element → one of the graph_nodes semantic_type enum. Mirrors\n// the SDK graph scanner's vocabulary so the persona × section matrix consumes\n// snippet-captured sections unchanged.\n//\n// SPLIT (Phase 2b, spec 2026-09-04). This module is the BROWSER-SAFE core:\n// structural rules, the small parent-level keyword table, and the fallbacks.\n// The rich ~40-topic vocabulary lives in ./topics.ts, which only the server\n// imports — shipping it here cost 1.5 KB gzip in every bundle (7% of the\n// always-on snippet) for a layer the browser never reads, since classifySection\n// returns only the parent.\n//\n// Both paths share structuralTopicOf/fallbackTopicOf, so the three fixes below\n// apply identically on client and server; only vocabulary richness differs.\n//\n// Fixes, each reproduced against the shipped classifier before being changed:\n// 1. structural before the converter fallback — `<div class=\"navbar\">` used\n// to become `cta`, because `navigation` was reachable only via tag\n// nav/footer.\n// 2. hero before the converter fallback — the cta rule sat above both hero\n// rules, so a `<header>` with a button and <200 chars could never be hero.\n// 3. tightened pricing/social keywords — `plans?` and `customers?` fired on\n// unrelated copy AND were `strong`, so they auto-applied at confidence 0.9\n// and were never sent to the LLM fallback.\n\nexport type SemanticType =\n | 'pricing' | 'hero' | 'social_proof' | 'cta' | 'features'\n | 'faq' | 'comparison' | 'trust' | 'navigation' | 'generic';\n\nexport type SectionRole = 'converter' | 'persuader' | 'structural';\n\n/** Canonical vocabulary — mirrors the graph_nodes semantic_type CHECK enum. */\nexport const SEMANTIC_TYPES: readonly SemanticType[] = [\n 'pricing', 'hero', 'social_proof', 'cta', 'features',\n 'faq', 'comparison', 'trust', 'navigation', 'generic',\n] as const;\n\nexport type TopicRule = { topic: string; parent: SemanticType; role: SectionRole };\n\n/** Environment-agnostic section features — buildable from a browser Element or\n * a server-parsed node (node-html-parser). */\nexport type SectionFeatures = {\n tag: string; // lowercase tag name\n idClass: string; // `${id} ${className}`\n headingText: string;\n bodyText: string; // normalized text content, first 2000 chars\n actionCount: number;\n textLength: number;\n /** ARIA landmark role, when the element declares one. Optional so every\n * existing caller keeps compiling; a page that uses landmarks gives a\n * high-precision signal for free, which the old classifier ignored — it\n * tested membership of SEMANTIC_TYPES, so only role=\"navigation\" ever hit\n * and role=\"banner\"/\"contentinfo\" were discarded. */\n ariaRole?: string;\n /** schema.org `@type` values found in `<script type=\"application/ld+json\">`\n * INSIDE this section. Highest-precision signal available and free to\n * collect — the crawler already has the HTML. Type-only here; the\n * `@type` → topic table is server-side in ./topics.ts, since the browser\n * never reads the topic layer and the snippet has ~200 bytes of margin. */\n structuredTypes?: string[];\n};\n\n// ARIA landmark → topic. Structural facts, not guesses.\nconst ARIA_TOPIC: Record<string, string> = {\n banner: 'hero',\n navigation: 'navigation',\n contentinfo: 'footer',\n};\n\n/**\n * Structural identification, shared by the browser and server classifiers.\n * Returns a topic name, or null when the section is not structural furniture.\n * Everything matched here is definitive (tag or authored marker), so callers\n * treat it as `strong`.\n */\nexport function structuralTopicOf(f: SectionFeatures): string | null {\n const aria = f.ariaRole ? ARIA_TOPIC[f.ariaRole.toLowerCase()] : undefined;\n if (aria) return aria;\n if (f.tag === 'nav') return 'navigation';\n if (f.tag === 'footer') return 'footer';\n // A page-level <header> IS the banner landmark (HTML-AAM maps it to\n // role=\"banner\", which this function already treats as hero), so it is a\n // structural fact rather than a keyword guess. Without this, a header whose\n // headline happens to contain a content word loses to the keyword table:\n // \"We build brands that move\" scored social_proof and \"Winter collection\"\n // scored features, purely on words inside the hero copy.\n if (f.tag === 'header') return 'hero';\n const hay = `${f.idClass} ${f.headingText}`.toLowerCase();\n if (/\\b(navbar|nav-bar|navigation|site-nav|main-nav|topbar|footer)\\b/.test(hay)) {\n return /footer/.test(hay) ? 'footer' : 'navigation';\n }\n // Explicit authoring marker beats any inferred keyword, and is matched on\n // idClass ONLY: a `<header class=\"hero\">` headlined \"Expert Car Repair\" is a\n // hero, not a services section — but matching \"hero\" in heading text would\n // also catch \"Hero of the story\".\n if (/\\b(hero|masthead|jumbotron)\\b/i.test(f.idClass)) return 'hero';\n return null;\n}\n\n/**\n * Last-resort classification once no keyword or content evidence matched.\n * Hero is checked BEFORE the converter fallback — fix 2 above.\n */\nexport function fallbackTopicOf(f: SectionFeatures): string {\n // `tag === 'header'` is handled in structuralTopicOf, which runs first.\n if (f.actionCount >= 1 && f.textLength > 0 && f.textLength < 200) return 'cta';\n return 'generic';\n}\n\n// Parent-level keyword table for the BROWSER path. Deliberately close to the\n// original size — the rich topic vocabulary is in ./topics.ts. `plans?` now\n// requires adjacent pricing context, and bare `customers?` is gone (it fired on\n// navigation furniture like a \"Customer Service\" footer block).\nconst KEYWORDS: Array<[SemanticType, RegExp]> = [\n ['pricing', /\\b(pricing|price list|per month|\\/mo|subscriptions?)\\b|\\bplans?\\b(?=[^.]{0,40}(from|start|month|year|[$€£]))/i],\n ['faq', /\\bfaq\\b|frequently asked|common questions?/i],\n ['comparison', /\\b(compare|comparison|versus)\\b|\\bvs\\./i],\n ['social_proof', /\\b(reviews?|ratings?|testimonial|brands?|galler(y|ies)|logos)\\b|trusted by|loved by|case stud|what our customers say/i],\n ['trust', /\\b(insurance|warrant(y|ies)|guarantees?|certifi|accredit|security|privacy|compliance|gdpr|encrypt)\\b|why choose|about us|our team/i],\n ['cta', /\\b(book|booking|reserve|appointments?|newsletter|subscribe)\\b|contact us|get in touch|opening hours/i],\n ['features', /\\b(features?|benefits?|capabilit|services?|repairs?|menus?|products?)\\b|how it works|our process|what we (do|offer)|what you get/i],\n];\n\n// Content-evidence patterns — run against bodyText when the heading gave us\n// nothing. Deliberately conservative: pricing needs per-period/plan context next\n// to money so an article mentioning \"$5 million\" stays generic.\nexport const CONTENT_PATTERNS: Array<[SemanticType, RegExp]> = [\n ['pricing', /(?:[$€£]\\s?\\d[\\d,.]*\\s*(?:\\/|per\\s)\\s*(?:mo|month|yr|year|seat|user))|(?:\\b(?:starter|basic|pro|growth|premium|enterprise)\\b[^.]{0,60}[$€£]\\s?\\d)/i],\n ['social_proof', /(?:★{2,})|(?:\\b\\d(?:\\.\\d)?\\s*(?:out of|\\/)\\s*5\\b)|(?:\\brated\\b)|(?:[\"“][^\"”]{20,160}[\"”]\\s*[—–-]\\s*[A-Z][a-z]+)/],\n ['trust', /\\b(?:money[- ]back guarantee|free returns?|returns? within|refund(?:s)? within|encrypted|soc ?2|iso ?27001|gdpr[- ]compliant|cancel anytime)\\b/i],\n ['comparison', /\\b(?:vs|versus)\\b\\.?[^.!?]{0,80}\\b(?:compare|comparison|plans?|features?|alternative)\\b|\\bhow (?:we|it) compares?\\b/i],\n];\n\n/** Parent for the handful of topics the shared structural/fallback helpers\n * emit. The server maps the full vocabulary via CLASSIFIER_TOPICS instead. */\nconst SHARED_TOPIC_PARENT: Record<string, SemanticType> = {\n navigation: 'navigation',\n footer: 'navigation',\n hero: 'hero',\n cta: 'cta',\n generic: 'generic',\n};\n\nfunction headingText(el: Element): string {\n const h = el.querySelector('h1, h2, h3');\n return (h?.textContent ?? '').slice(0, 160);\n}\n\n/**\n * Pure classification over extracted features. `strong` = keyword, content, or\n * structural evidence (trustable enough to auto-apply); `weak` = a fallback\n * guess (hero/cta/generic — capture-worthy but not persona evidence).\n */\nexport function classifyFeatures(f: SectionFeatures): { type: SemanticType; strength: 'strong' | 'weak' } {\n const structural = structuralTopicOf(f);\n if (structural) return { type: SHARED_TOPIC_PARENT[structural] ?? 'generic', strength: 'strong' };\n\n const hay = `${f.idClass} ${f.headingText}`.toLowerCase();\n for (const [type, re] of KEYWORDS) if (re.test(hay)) return { type, strength: 'strong' };\n for (const [type, re] of CONTENT_PATTERNS) if (re.test(f.bodyText)) return { type, strength: 'strong' };\n\n const fb = fallbackTopicOf(f);\n return { type: SHARED_TOPIC_PARENT[fb] ?? 'generic', strength: 'weak' };\n}\n\n/** Feature extraction from a live DOM element (browser paths). */\nexport function featuresFromElement(el: Element): SectionFeatures {\n const text = (el.textContent ?? '').replace(/\\s+/g, ' ').trim();\n const role = el.getAttribute('role');\n return {\n tag: el.tagName.toLowerCase(),\n idClass: `${el.id} ${String(el.className ?? '')}`,\n headingText: headingText(el),\n bodyText: text.slice(0, 2000),\n actionCount: el.querySelectorAll('a, button, [role=\"button\"]').length,\n textLength: text.length,\n ...(role ? { ariaRole: role } : {}),\n };\n}\n\n/** Classify a page section into a semantic type (never null — falls back to\n * 'generic' so the caller can still capture attention on it). */\nexport function classifySection(el: Element): SemanticType {\n return classifyFeatures(featuresFromElement(el)).type;\n}\n","// Rich topic vocabulary for section classification (Phase 2b, spec 2026-09-04).\n//\n// SERVER-ONLY. This module is deliberately NOT imported by ./classify.ts, which\n// the browser bundles: the tables below cost ~1.5 KB gzip, which is 7% of the\n// always-on snippet, on every page view of every customer site — for a layer\n// the browser never reads (classifySection returns only the parent).\n//\n// It shares structuralTopicOf/fallbackTopicOf with ./classify.ts, so client and\n// server agree on structure and fallbacks; only vocabulary richness differs.\n//\n// The topic→parent mapping is DUPLICATED from packages/policy/src/taxonomy.ts,\n// for the same bundle reason — core must not import the policy taxonomy.\n// apps/api/src/domain/classifier-taxonomy-sync.test.ts fails if the two drift.\n\nimport {\n structuralTopicOf,\n fallbackTopicOf,\n type SectionFeatures,\n type SemanticType,\n type TopicRule,\n} from './classify.js';\n\nexport const CLASSIFIER_TOPICS: readonly TopicRule[] = [\n { topic: 'navigation', parent: 'navigation', role: 'structural' },\n { topic: 'footer', parent: 'navigation', role: 'structural' },\n { topic: 'breadcrumb', parent: 'navigation', role: 'structural' },\n { topic: 'cookie_banner', parent: 'navigation', role: 'structural' },\n { topic: 'hero', parent: 'hero', role: 'converter' },\n { topic: 'pricing_plans', parent: 'pricing', role: 'converter' },\n { topic: 'quote', parent: 'pricing', role: 'converter' },\n { topic: 'financing', parent: 'pricing', role: 'persuader' },\n { topic: 'faq', parent: 'faq', role: 'persuader' },\n { topic: 'comparison', parent: 'comparison', role: 'persuader' },\n { topic: 'reviews', parent: 'social_proof', role: 'persuader' },\n { topic: 'testimonials', parent: 'social_proof', role: 'persuader' },\n { topic: 'case_study', parent: 'social_proof', role: 'persuader' },\n { topic: 'brands', parent: 'social_proof', role: 'persuader' },\n { topic: 'awards', parent: 'social_proof', role: 'persuader' },\n { topic: 'gallery', parent: 'social_proof', role: 'persuader' },\n { topic: 'stats', parent: 'social_proof', role: 'persuader' },\n { topic: 'insurance', parent: 'trust', role: 'persuader' },\n { topic: 'warranty', parent: 'trust', role: 'persuader' },\n { topic: 'certifications', parent: 'trust', role: 'persuader' },\n { topic: 'security', parent: 'trust', role: 'persuader' },\n { topic: 'guarantee', parent: 'trust', role: 'persuader' },\n { topic: 'about', parent: 'trust', role: 'persuader' },\n { topic: 'team', parent: 'trust', role: 'persuader' },\n { topic: 'services', parent: 'features', role: 'persuader' },\n { topic: 'features', parent: 'features', role: 'persuader' },\n { topic: 'process', parent: 'features', role: 'persuader' },\n { topic: 'menu', parent: 'features', role: 'persuader' },\n { topic: 'inventory', parent: 'features', role: 'converter' },\n { topic: 'integrations', parent: 'features', role: 'persuader' },\n { topic: 'booking', parent: 'cta', role: 'converter' },\n { topic: 'contact_form', parent: 'cta', role: 'converter' },\n { topic: 'newsletter', parent: 'cta', role: 'converter' },\n { topic: 'hours', parent: 'cta', role: 'persuader' },\n { topic: 'location', parent: 'cta', role: 'persuader' },\n { topic: 'cta', parent: 'cta', role: 'converter' },\n { topic: 'blog', parent: 'generic', role: 'persuader' },\n { topic: 'careers', parent: 'generic', role: 'persuader' },\n { topic: 'legal', parent: 'generic', role: 'persuader' },\n { topic: 'generic', parent: 'generic', role: 'persuader' },\n];\n\nconst RULE = new Map(CLASSIFIER_TOPICS.map((t) => [t.topic, t]));\n\nfunction ruleFor(topic: string): TopicRule {\n return RULE.get(topic) ?? { topic: 'generic', parent: 'generic', role: 'persuader' };\n}\n\n// ORDER IS LOAD-BEARING — first hit wins, so specific must precede general:\n// * `insurance` precedes `services`, or \"Insurance Approved Repairs\" becomes\n// a services section on the strength of \"repairs\".\n// * `brands` precedes `case_study`, or \"Brands We Work With\" matches \"work\".\n// * `pricing_plans` requires real pricing context — see fix 3 in ./classify.ts.\nconst TOPIC_KEYWORDS: Array<[string, RegExp]> = [\n ['cookie_banner', /\\bcookies?\\b[^.]{0,20}\\b(banner|notice|consent|policy)\\b|consent banner/i],\n ['breadcrumb', /\\bbreadcrumbs?\\b/i],\n ['insurance', /\\b(insurance|insurer|insured)\\b/i],\n ['warranty', /\\b(warrant(y|ies)|guarantees?|guaranteed)\\b/i],\n ['certifications', /\\b(certifi|accredit|licen[cs]ed)\\b|approved by|iso ?\\d/i],\n ['security', /\\b(security|privacy|compliance|gdpr|encrypt)\\b|soc ?2/i],\n ['about', /why choose|why us|about us|our story|who we are|years? (of )?(experience|serving)/i],\n ['team', /our team|meet the team|our people|our staff/i],\n ['reviews', /\\b(reviews?|ratings?|testimonial)\\b|what our customers say/i],\n ['brands', /\\b(brands?|partners?|manufacturers?|logos)\\b|we work with|trusted by|loved by/i],\n ['gallery', /\\b(galler(y|ies)|photos?)\\b|before and after/i],\n ['case_study', /case stud|selected work|portfolio|our projects?|recent work/i],\n ['awards', /\\b(awards?|accolades?)\\b/i],\n ['pricing_plans', /\\b(pricing|price list|per month|\\/mo|subscriptions?)\\b|\\bplans?\\b(?=[^.]{0,40}(from|start|month|year|[$€£]))/i],\n ['quote', /get a quote|request a quote|free estimate|get an estimate/i],\n ['financing', /\\b(financ(e|ing)|instal?ments?)\\b|payment plan|pay monthly/i],\n ['booking', /\\b(book|booking|reserve|reservation|appointments?)\\b|schedule a/i],\n ['newsletter', /\\b(newsletter|subscribe)\\b|join our (list|mailing)|mailing list/i],\n ['contact_form', /contact us|get in touch|start a project|\\benquir|\\binquir|send us/i],\n ['hours', /opening hours|\\bhours\\b|open (mon|tue|wed|thu|fri|sat|sun)/i],\n ['location', /find us|our location|directions|where we are|visit us/i],\n ['menu', /\\b(menus?|dishes)\\b|our food/i],\n ['inventory', /\\bbest ?sellers?\\b|\\b(shop|products?|collection)\\b|our range|in stock/i],\n ['process', /our process|how it works|the process|what to expect|\\bsteps?\\b/i],\n ['services', /\\b(services?|repairs?|treatments?|specialit|specialt)\\b|what we (do|offer)/i],\n ['integrations', /\\bintegrations?\\b|works with|connects? to/i],\n ['features', /\\b(features?|benefits?|capabilit)\\b|what you get/i],\n ['faq', /\\bfaq\\b|frequently asked|common questions?/i],\n ['comparison', /\\b(compare|comparison|versus)\\b|\\bvs\\./i],\n ['blog', /\\b(blog|news|articles?|insights?)\\b/i],\n ['careers', /\\b(careers?|jobs?)\\b|join (the|our) team|we.re hiring/i],\n ['legal', /\\bterms\\b|privacy policy|cookie policy|\\blegal\\b/i],\n];\n\n/**\n * schema.org `@type` → topic. The highest-precision signal available, and free:\n * the crawler already has the HTML, and a site that publishes structured data\n * has told us what a section is rather than leaving us to infer it from copy.\n *\n * Ranked BELOW structural rules deliberately — many sites emit an Organization\n * or WebSite blob site-wide in the header or footer, and letting that outrank a\n * `<nav>` would turn every such navigation bar into an `about` section.\n */\nconst SCHEMA_TOPIC: Record<string, string> = {\n FAQPage: 'faq',\n Question: 'faq',\n Review: 'reviews',\n AggregateRating: 'reviews',\n Offer: 'pricing_plans',\n AggregateOffer: 'pricing_plans',\n PriceSpecification: 'pricing_plans',\n Service: 'services',\n Product: 'inventory',\n ItemList: 'inventory',\n Menu: 'menu',\n MenuSection: 'menu',\n LocalBusiness: 'location',\n PostalAddress: 'location',\n OpeningHoursSpecification: 'hours',\n BreadcrumbList: 'breadcrumb',\n ImageGallery: 'gallery',\n JobPosting: 'careers',\n BlogPosting: 'blog',\n Article: 'blog',\n Organization: 'about',\n};\n\nconst TOPIC_CONTENT: Array<[string, RegExp]> = [\n ['pricing_plans', /(?:[$€£]\\s?\\d[\\d,.]*\\s*(?:\\/|per\\s)\\s*(?:mo|month|yr|year|seat|user))|(?:\\b(?:starter|basic|pro|growth|premium|enterprise)\\b[^.]{0,60}[$€£]\\s?\\d)/i],\n ['reviews', /(?:★{2,})|(?:\\b\\d(?:\\.\\d)?\\s*(?:out of|\\/)\\s*5\\b)|(?:\\brated\\b)|(?:[\"“][^\"”]{20,160}[\"”]\\s*[—–-]\\s*[A-Z][a-z]+)/],\n ['guarantee', /\\b(?:money[- ]back guarantee|free returns?|returns? within|refund(?:s)? within|cancel anytime)\\b/i],\n ['security', /\\b(?:encrypted|soc ?2|iso ?27001|gdpr[- ]compliant)\\b/i],\n ['comparison', /\\b(?:vs|versus)\\b\\.?[^.!?]{0,80}\\b(?:compare|comparison|plans?|features?|alternative)\\b|\\bhow (?:we|it) compares?\\b/i],\n];\n\n/** Which classifier stage produced the label. Persisted as provenance (the\n * `page_semantics.source` column distinguishes structured_data from\n * heuristic), so it must name the stage that actually MATCHED — a section\n * that merely CONTAINS a JSON-LD blob but was classified by its `<nav>` tag\n * is heuristic evidence, not the site telling us what it is. */\nexport type ClassifierStage = 'structural' | 'structured_data' | 'keyword' | 'content' | 'fallback';\n\n/**\n * Classify a section into a topic, its parent, and its functional role.\n * Structure and fallbacks are the shared implementation from ./classify.ts, so\n * the browser cannot disagree with the server about what a `<nav>` is.\n */\nexport function classifyTopic(\n f: SectionFeatures,\n): TopicRule & { strength: 'strong' | 'weak'; stage: ClassifierStage } {\n const structural = structuralTopicOf(f);\n if (structural) return { ...ruleFor(structural), strength: 'strong', stage: 'structural' };\n\n // Stage 0: the site told us what this is. Beats every inferred keyword.\n for (const t of f.structuredTypes ?? []) {\n const topic = SCHEMA_TOPIC[t];\n if (topic) return { ...ruleFor(topic), strength: 'strong', stage: 'structured_data' };\n }\n\n const hay = `${f.idClass} ${f.headingText}`.toLowerCase();\n for (const [topic, re] of TOPIC_KEYWORDS) {\n if (re.test(hay)) return { ...ruleFor(topic), strength: 'strong', stage: 'keyword' };\n }\n for (const [topic, re] of TOPIC_CONTENT) {\n if (re.test(f.bodyText)) return { ...ruleFor(topic), strength: 'strong', stage: 'content' };\n }\n return { ...ruleFor(fallbackTopicOf(f)), strength: 'weak', stage: 'fallback' };\n}\n\nexport type { SemanticType, SectionFeatures, TopicRule };\n"],"mappings":"owBAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,uBAAAE,EAAA,kBAAAC,IAAA,eAAAC,EAAAJ,GC+DA,IAAMK,EAAqC,CACzC,OAAQ,OACR,WAAY,aACZ,YAAa,QACf,EAQO,SAASC,EAAkBC,EAAmC,CACnE,IAAMC,EAAOD,EAAE,SAAWF,EAAWE,EAAE,SAAS,YAAY,CAAC,EAAI,OACjE,GAAIC,EAAM,OAAOA,EACjB,GAAID,EAAE,MAAQ,MAAO,MAAO,aAC5B,GAAIA,EAAE,MAAQ,SAAU,MAAO,SAO/B,GAAIA,EAAE,MAAQ,SAAU,MAAO,OAC/B,IAAME,EAAM,GAAGF,EAAE,OAAO,IAAIA,EAAE,WAAW,GAAG,YAAY,EACxD,MAAI,kEAAkE,KAAKE,CAAG,EACrE,SAAS,KAAKA,CAAG,EAAI,SAAW,aAMrC,iCAAiC,KAAKF,EAAE,OAAO,EAAU,OACtD,IACT,CAMO,SAASG,EAAgBH,EAA4B,CAE1D,OAAIA,EAAE,aAAe,GAAKA,EAAE,WAAa,GAAKA,EAAE,WAAa,IAAY,MAClE,SACT,CCrFO,IAAMI,EAA0C,CACrD,CAAE,MAAO,aAAc,OAAQ,aAAc,KAAM,YAAa,EAChE,CAAE,MAAO,SAAU,OAAQ,aAAc,KAAM,YAAa,EAC5D,CAAE,MAAO,aAAc,OAAQ,aAAc,KAAM,YAAa,EAChE,CAAE,MAAO,gBAAiB,OAAQ,aAAc,KAAM,YAAa,EACnE,CAAE,MAAO,OAAQ,OAAQ,OAAQ,KAAM,WAAY,EACnD,CAAE,MAAO,gBAAiB,OAAQ,UAAW,KAAM,WAAY,EAC/D,CAAE,MAAO,QAAS,OAAQ,UAAW,KAAM,WAAY,EACvD,CAAE,MAAO,YAAa,OAAQ,UAAW,KAAM,WAAY,EAC3D,CAAE,MAAO,MAAO,OAAQ,MAAO,KAAM,WAAY,EACjD,CAAE,MAAO,aAAc,OAAQ,aAAc,KAAM,WAAY,EAC/D,CAAE,MAAO,UAAW,OAAQ,eAAgB,KAAM,WAAY,EAC9D,CAAE,MAAO,eAAgB,OAAQ,eAAgB,KAAM,WAAY,EACnE,CAAE,MAAO,aAAc,OAAQ,eAAgB,KAAM,WAAY,EACjE,CAAE,MAAO,SAAU,OAAQ,eAAgB,KAAM,WAAY,EAC7D,CAAE,MAAO,SAAU,OAAQ,eAAgB,KAAM,WAAY,EAC7D,CAAE,MAAO,UAAW,OAAQ,eAAgB,KAAM,WAAY,EAC9D,CAAE,MAAO,QAAS,OAAQ,eAAgB,KAAM,WAAY,EAC5D,CAAE,MAAO,YAAa,OAAQ,QAAS,KAAM,WAAY,EACzD,CAAE,MAAO,WAAY,OAAQ,QAAS,KAAM,WAAY,EACxD,CAAE,MAAO,iBAAkB,OAAQ,QAAS,KAAM,WAAY,EAC9D,CAAE,MAAO,WAAY,OAAQ,QAAS,KAAM,WAAY,EACxD,CAAE,MAAO,YAAa,OAAQ,QAAS,KAAM,WAAY,EACzD,CAAE,MAAO,QAAS,OAAQ,QAAS,KAAM,WAAY,EACrD,CAAE,MAAO,OAAQ,OAAQ,QAAS,KAAM,WAAY,EACpD,CAAE,MAAO,WAAY,OAAQ,WAAY,KAAM,WAAY,EAC3D,CAAE,MAAO,WAAY,OAAQ,WAAY,KAAM,WAAY,EAC3D,CAAE,MAAO,UAAW,OAAQ,WAAY,KAAM,WAAY,EAC1D,CAAE,MAAO,OAAQ,OAAQ,WAAY,KAAM,WAAY,EACvD,CAAE,MAAO,YAAa,OAAQ,WAAY,KAAM,WAAY,EAC5D,CAAE,MAAO,eAAgB,OAAQ,WAAY,KAAM,WAAY,EAC/D,CAAE,MAAO,UAAW,OAAQ,MAAO,KAAM,WAAY,EACrD,CAAE,MAAO,eAAgB,OAAQ,MAAO,KAAM,WAAY,EAC1D,CAAE,MAAO,aAAc,OAAQ,MAAO,KAAM,WAAY,EACxD,CAAE,MAAO,QAAS,OAAQ,MAAO,KAAM,WAAY,EACnD,CAAE,MAAO,WAAY,OAAQ,MAAO,KAAM,WAAY,EACtD,CAAE,MAAO,MAAO,OAAQ,MAAO,KAAM,WAAY,EACjD,CAAE,MAAO,OAAQ,OAAQ,UAAW,KAAM,WAAY,EACtD,CAAE,MAAO,UAAW,OAAQ,UAAW,KAAM,WAAY,EACzD,CAAE,MAAO,QAAS,OAAQ,UAAW,KAAM,WAAY,EACvD,CAAE,MAAO,UAAW,OAAQ,UAAW,KAAM,WAAY,CAC3D,EAEMC,EAAO,IAAI,IAAID,EAAkB,IAAKE,GAAM,CAACA,EAAE,MAAOA,CAAC,CAAC,CAAC,EAE/D,SAASC,EAAQC,EAA0B,CAnE3C,IAAAC,EAoEE,OAAOA,EAAAJ,EAAK,IAAIG,CAAK,IAAd,KAAAC,EAAmB,CAAE,MAAO,UAAW,OAAQ,UAAW,KAAM,WAAY,CACrF,CAOA,IAAMC,EAA0C,CAC9C,CAAC,gBAAiB,0EAA0E,EAC5F,CAAC,aAAc,mBAAmB,EAClC,CAAC,YAAa,kCAAkC,EAChD,CAAC,WAAY,8CAA8C,EAC3D,CAAC,iBAAkB,yDAAyD,EAC5E,CAAC,WAAY,wDAAwD,EACrE,CAAC,QAAS,oFAAoF,EAC9F,CAAC,OAAQ,8CAA8C,EACvD,CAAC,UAAW,6DAA6D,EACzE,CAAC,SAAU,gFAAgF,EAC3F,CAAC,UAAW,+CAA+C,EAC3D,CAAC,aAAc,8DAA8D,EAC7E,CAAC,SAAU,2BAA2B,EACtC,CAAC,gBAAiB,+GAA+G,EACjI,CAAC,QAAS,4DAA4D,EACtE,CAAC,YAAa,6DAA6D,EAC3E,CAAC,UAAW,kEAAkE,EAC9E,CAAC,aAAc,kEAAkE,EACjF,CAAC,eAAgB,oEAAoE,EACrF,CAAC,QAAS,6DAA6D,EACvE,CAAC,WAAY,wDAAwD,EACrE,CAAC,OAAQ,+BAA+B,EACxC,CAAC,YAAa,wEAAwE,EACtF,CAAC,UAAW,iEAAiE,EAC7E,CAAC,WAAY,6EAA6E,EAC1F,CAAC,eAAgB,4CAA4C,EAC7D,CAAC,WAAY,mDAAmD,EAChE,CAAC,MAAO,6CAA6C,EACrD,CAAC,aAAc,yCAAyC,EACxD,CAAC,OAAQ,sCAAsC,EAC/C,CAAC,UAAW,wDAAwD,EACpE,CAAC,QAAS,mDAAmD,CAC/D,EAWMC,EAAuC,CAC3C,QAAS,MACT,SAAU,MACV,OAAQ,UACR,gBAAiB,UACjB,MAAO,gBACP,eAAgB,gBAChB,mBAAoB,gBACpB,QAAS,WACT,QAAS,YACT,SAAU,YACV,KAAM,OACN,YAAa,OACb,cAAe,WACf,cAAe,WACf,0BAA2B,QAC3B,eAAgB,aAChB,aAAc,UACd,WAAY,UACZ,YAAa,OACb,QAAS,OACT,aAAc,OAChB,EAEMC,EAAyC,CAC7C,CAAC,gBAAiB,oJAAoJ,EACtK,CAAC,UAAW,iHAAiH,EAC7H,CAAC,YAAa,mGAAmG,EACjH,CAAC,WAAY,wDAAwD,EACrE,CAAC,aAAc,sHAAsH,CACvI,EAcO,SAASC,EACdC,EACqE,CAtKvE,IAAAL,EAuKE,IAAMM,EAAaC,EAAkBF,CAAC,EACtC,GAAIC,EAAY,OAAOE,EAAAC,EAAA,GAAKX,EAAQQ,CAAU,GAAvB,CAA0B,SAAU,SAAU,MAAO,YAAa,GAGzF,QAAWT,KAAKG,EAAAK,EAAE,kBAAF,KAAAL,EAAqB,CAAC,EAAG,CACvC,IAAMD,EAAQG,EAAaL,CAAC,EAC5B,GAAIE,EAAO,OAAOS,EAAAC,EAAA,GAAKX,EAAQC,CAAK,GAAlB,CAAqB,SAAU,SAAU,MAAO,iBAAkB,EACtF,CAEA,IAAMW,EAAM,GAAGL,EAAE,OAAO,IAAIA,EAAE,WAAW,GAAG,YAAY,EACxD,OAAW,CAACN,EAAOY,CAAE,IAAKV,EACxB,GAAIU,EAAG,KAAKD,CAAG,EAAG,OAAOF,EAAAC,EAAA,GAAKX,EAAQC,CAAK,GAAlB,CAAqB,SAAU,SAAU,MAAO,SAAU,GAErF,OAAW,CAACA,EAAOY,CAAE,IAAKR,EACxB,GAAIQ,EAAG,KAAKN,EAAE,QAAQ,EAAG,OAAOG,EAAAC,EAAA,GAAKX,EAAQC,CAAK,GAAlB,CAAqB,SAAU,SAAU,MAAO,SAAU,GAE5F,OAAOS,EAAAC,EAAA,GAAKX,EAAQc,EAAgBP,CAAC,CAAC,GAA/B,CAAkC,SAAU,OAAQ,MAAO,UAAW,EAC/E","names":["index_topics_exports","__export","CLASSIFIER_TOPICS","classifyTopic","__toCommonJS","ARIA_TOPIC","structuralTopicOf","f","aria","hay","fallbackTopicOf","CLASSIFIER_TOPICS","RULE","t","ruleFor","topic","_a","TOPIC_KEYWORDS","SCHEMA_TOPIC","TOPIC_CONTENT","classifyTopic","f","structural","structuralTopicOf","__spreadProps","__spreadValues","hay","re","fallbackTopicOf"]}
@@ -0,0 +1,2 @@
1
+ import{b as c,c as p}from"./chunk-EWP6FTHE.mjs";import{a as t,b as o}from"./chunk-TMCGHANO.mjs";var u=[{topic:"navigation",parent:"navigation",role:"structural"},{topic:"footer",parent:"navigation",role:"structural"},{topic:"breadcrumb",parent:"navigation",role:"structural"},{topic:"cookie_banner",parent:"navigation",role:"structural"},{topic:"hero",parent:"hero",role:"converter"},{topic:"pricing_plans",parent:"pricing",role:"converter"},{topic:"quote",parent:"pricing",role:"converter"},{topic:"financing",parent:"pricing",role:"persuader"},{topic:"faq",parent:"faq",role:"persuader"},{topic:"comparison",parent:"comparison",role:"persuader"},{topic:"reviews",parent:"social_proof",role:"persuader"},{topic:"testimonials",parent:"social_proof",role:"persuader"},{topic:"case_study",parent:"social_proof",role:"persuader"},{topic:"brands",parent:"social_proof",role:"persuader"},{topic:"awards",parent:"social_proof",role:"persuader"},{topic:"gallery",parent:"social_proof",role:"persuader"},{topic:"stats",parent:"social_proof",role:"persuader"},{topic:"insurance",parent:"trust",role:"persuader"},{topic:"warranty",parent:"trust",role:"persuader"},{topic:"certifications",parent:"trust",role:"persuader"},{topic:"security",parent:"trust",role:"persuader"},{topic:"guarantee",parent:"trust",role:"persuader"},{topic:"about",parent:"trust",role:"persuader"},{topic:"team",parent:"trust",role:"persuader"},{topic:"services",parent:"features",role:"persuader"},{topic:"features",parent:"features",role:"persuader"},{topic:"process",parent:"features",role:"persuader"},{topic:"menu",parent:"features",role:"persuader"},{topic:"inventory",parent:"features",role:"converter"},{topic:"integrations",parent:"features",role:"persuader"},{topic:"booking",parent:"cta",role:"converter"},{topic:"contact_form",parent:"cta",role:"converter"},{topic:"newsletter",parent:"cta",role:"converter"},{topic:"hours",parent:"cta",role:"persuader"},{topic:"location",parent:"cta",role:"persuader"},{topic:"cta",parent:"cta",role:"converter"},{topic:"blog",parent:"generic",role:"persuader"},{topic:"careers",parent:"generic",role:"persuader"},{topic:"legal",parent:"generic",role:"persuader"},{topic:"generic",parent:"generic",role:"persuader"}],b=new Map(u.map(e=>[e.topic,e]));function s(e){var a;return(a=b.get(e))!=null?a:{topic:"generic",parent:"generic",role:"persuader"}}var g=[["cookie_banner",/\bcookies?\b[^.]{0,20}\b(banner|notice|consent|policy)\b|consent banner/i],["breadcrumb",/\bbreadcrumbs?\b/i],["insurance",/\b(insurance|insurer|insured)\b/i],["warranty",/\b(warrant(y|ies)|guarantees?|guaranteed)\b/i],["certifications",/\b(certifi|accredit|licen[cs]ed)\b|approved by|iso ?\d/i],["security",/\b(security|privacy|compliance|gdpr|encrypt)\b|soc ?2/i],["about",/why choose|why us|about us|our story|who we are|years? (of )?(experience|serving)/i],["team",/our team|meet the team|our people|our staff/i],["reviews",/\b(reviews?|ratings?|testimonial)\b|what our customers say/i],["brands",/\b(brands?|partners?|manufacturers?|logos)\b|we work with|trusted by|loved by/i],["gallery",/\b(galler(y|ies)|photos?)\b|before and after/i],["case_study",/case stud|selected work|portfolio|our projects?|recent work/i],["awards",/\b(awards?|accolades?)\b/i],["pricing_plans",/\b(pricing|price list|per month|\/mo|subscriptions?)\b|\bplans?\b(?=[^.]{0,40}(from|start|month|year|[$€£]))/i],["quote",/get a quote|request a quote|free estimate|get an estimate/i],["financing",/\b(financ(e|ing)|instal?ments?)\b|payment plan|pay monthly/i],["booking",/\b(book|booking|reserve|reservation|appointments?)\b|schedule a/i],["newsletter",/\b(newsletter|subscribe)\b|join our (list|mailing)|mailing list/i],["contact_form",/contact us|get in touch|start a project|\benquir|\binquir|send us/i],["hours",/opening hours|\bhours\b|open (mon|tue|wed|thu|fri|sat|sun)/i],["location",/find us|our location|directions|where we are|visit us/i],["menu",/\b(menus?|dishes)\b|our food/i],["inventory",/\bbest ?sellers?\b|\b(shop|products?|collection)\b|our range|in stock/i],["process",/our process|how it works|the process|what to expect|\bsteps?\b/i],["services",/\b(services?|repairs?|treatments?|specialit|specialt)\b|what we (do|offer)/i],["integrations",/\bintegrations?\b|works with|connects? to/i],["features",/\b(features?|benefits?|capabilit)\b|what you get/i],["faq",/\bfaq\b|frequently asked|common questions?/i],["comparison",/\b(compare|comparison|versus)\b|\bvs\./i],["blog",/\b(blog|news|articles?|insights?)\b/i],["careers",/\b(careers?|jobs?)\b|join (the|our) team|we.re hiring/i],["legal",/\bterms\b|privacy policy|cookie policy|\blegal\b/i]],d={FAQPage:"faq",Question:"faq",Review:"reviews",AggregateRating:"reviews",Offer:"pricing_plans",AggregateOffer:"pricing_plans",PriceSpecification:"pricing_plans",Service:"services",Product:"inventory",ItemList:"inventory",Menu:"menu",MenuSection:"menu",LocalBusiness:"location",PostalAddress:"location",OpeningHoursSpecification:"hours",BreadcrumbList:"breadcrumb",ImageGallery:"gallery",JobPosting:"careers",BlogPosting:"blog",Article:"blog",Organization:"about"},f=[["pricing_plans",/(?:[$€£]\s?\d[\d,.]*\s*(?:\/|per\s)\s*(?:mo|month|yr|year|seat|user))|(?:\b(?:starter|basic|pro|growth|premium|enterprise)\b[^.]{0,60}[$€£]\s?\d)/i],["reviews",/(?:★{2,})|(?:\b\d(?:\.\d)?\s*(?:out of|\/)\s*5\b)|(?:\brated\b)|(?:["“][^"”]{20,160}["”]\s*[—–-]\s*[A-Z][a-z]+)/],["guarantee",/\b(?:money[- ]back guarantee|free returns?|returns? within|refund(?:s)? within|cancel anytime)\b/i],["security",/\b(?:encrypted|soc ?2|iso ?27001|gdpr[- ]compliant)\b/i],["comparison",/\b(?:vs|versus)\b\.?[^.!?]{0,80}\b(?:compare|comparison|plans?|features?|alternative)\b|\bhow (?:we|it) compares?\b/i]];function m(e){var n;let a=c(e);if(a)return o(t({},s(a)),{strength:"strong",stage:"structural"});for(let i of(n=e.structuredTypes)!=null?n:[]){let r=d[i];if(r)return o(t({},s(r)),{strength:"strong",stage:"structured_data"})}let l=`${e.idClass} ${e.headingText}`.toLowerCase();for(let[i,r]of g)if(r.test(l))return o(t({},s(i)),{strength:"strong",stage:"keyword"});for(let[i,r]of f)if(r.test(e.bodyText))return o(t({},s(i)),{strength:"strong",stage:"content"});return o(t({},s(p(e))),{strength:"weak",stage:"fallback"})}export{u as CLASSIFIER_TOPICS,m as classifyTopic};
2
+ //# sourceMappingURL=index-topics.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/engagement/topics.ts"],"sourcesContent":["// Rich topic vocabulary for section classification (Phase 2b, spec 2026-09-04).\n//\n// SERVER-ONLY. This module is deliberately NOT imported by ./classify.ts, which\n// the browser bundles: the tables below cost ~1.5 KB gzip, which is 7% of the\n// always-on snippet, on every page view of every customer site — for a layer\n// the browser never reads (classifySection returns only the parent).\n//\n// It shares structuralTopicOf/fallbackTopicOf with ./classify.ts, so client and\n// server agree on structure and fallbacks; only vocabulary richness differs.\n//\n// The topic→parent mapping is DUPLICATED from packages/policy/src/taxonomy.ts,\n// for the same bundle reason — core must not import the policy taxonomy.\n// apps/api/src/domain/classifier-taxonomy-sync.test.ts fails if the two drift.\n\nimport {\n structuralTopicOf,\n fallbackTopicOf,\n type SectionFeatures,\n type SemanticType,\n type TopicRule,\n} from './classify.js';\n\nexport const CLASSIFIER_TOPICS: readonly TopicRule[] = [\n { topic: 'navigation', parent: 'navigation', role: 'structural' },\n { topic: 'footer', parent: 'navigation', role: 'structural' },\n { topic: 'breadcrumb', parent: 'navigation', role: 'structural' },\n { topic: 'cookie_banner', parent: 'navigation', role: 'structural' },\n { topic: 'hero', parent: 'hero', role: 'converter' },\n { topic: 'pricing_plans', parent: 'pricing', role: 'converter' },\n { topic: 'quote', parent: 'pricing', role: 'converter' },\n { topic: 'financing', parent: 'pricing', role: 'persuader' },\n { topic: 'faq', parent: 'faq', role: 'persuader' },\n { topic: 'comparison', parent: 'comparison', role: 'persuader' },\n { topic: 'reviews', parent: 'social_proof', role: 'persuader' },\n { topic: 'testimonials', parent: 'social_proof', role: 'persuader' },\n { topic: 'case_study', parent: 'social_proof', role: 'persuader' },\n { topic: 'brands', parent: 'social_proof', role: 'persuader' },\n { topic: 'awards', parent: 'social_proof', role: 'persuader' },\n { topic: 'gallery', parent: 'social_proof', role: 'persuader' },\n { topic: 'stats', parent: 'social_proof', role: 'persuader' },\n { topic: 'insurance', parent: 'trust', role: 'persuader' },\n { topic: 'warranty', parent: 'trust', role: 'persuader' },\n { topic: 'certifications', parent: 'trust', role: 'persuader' },\n { topic: 'security', parent: 'trust', role: 'persuader' },\n { topic: 'guarantee', parent: 'trust', role: 'persuader' },\n { topic: 'about', parent: 'trust', role: 'persuader' },\n { topic: 'team', parent: 'trust', role: 'persuader' },\n { topic: 'services', parent: 'features', role: 'persuader' },\n { topic: 'features', parent: 'features', role: 'persuader' },\n { topic: 'process', parent: 'features', role: 'persuader' },\n { topic: 'menu', parent: 'features', role: 'persuader' },\n { topic: 'inventory', parent: 'features', role: 'converter' },\n { topic: 'integrations', parent: 'features', role: 'persuader' },\n { topic: 'booking', parent: 'cta', role: 'converter' },\n { topic: 'contact_form', parent: 'cta', role: 'converter' },\n { topic: 'newsletter', parent: 'cta', role: 'converter' },\n { topic: 'hours', parent: 'cta', role: 'persuader' },\n { topic: 'location', parent: 'cta', role: 'persuader' },\n { topic: 'cta', parent: 'cta', role: 'converter' },\n { topic: 'blog', parent: 'generic', role: 'persuader' },\n { topic: 'careers', parent: 'generic', role: 'persuader' },\n { topic: 'legal', parent: 'generic', role: 'persuader' },\n { topic: 'generic', parent: 'generic', role: 'persuader' },\n];\n\nconst RULE = new Map(CLASSIFIER_TOPICS.map((t) => [t.topic, t]));\n\nfunction ruleFor(topic: string): TopicRule {\n return RULE.get(topic) ?? { topic: 'generic', parent: 'generic', role: 'persuader' };\n}\n\n// ORDER IS LOAD-BEARING — first hit wins, so specific must precede general:\n// * `insurance` precedes `services`, or \"Insurance Approved Repairs\" becomes\n// a services section on the strength of \"repairs\".\n// * `brands` precedes `case_study`, or \"Brands We Work With\" matches \"work\".\n// * `pricing_plans` requires real pricing context — see fix 3 in ./classify.ts.\nconst TOPIC_KEYWORDS: Array<[string, RegExp]> = [\n ['cookie_banner', /\\bcookies?\\b[^.]{0,20}\\b(banner|notice|consent|policy)\\b|consent banner/i],\n ['breadcrumb', /\\bbreadcrumbs?\\b/i],\n ['insurance', /\\b(insurance|insurer|insured)\\b/i],\n ['warranty', /\\b(warrant(y|ies)|guarantees?|guaranteed)\\b/i],\n ['certifications', /\\b(certifi|accredit|licen[cs]ed)\\b|approved by|iso ?\\d/i],\n ['security', /\\b(security|privacy|compliance|gdpr|encrypt)\\b|soc ?2/i],\n ['about', /why choose|why us|about us|our story|who we are|years? (of )?(experience|serving)/i],\n ['team', /our team|meet the team|our people|our staff/i],\n ['reviews', /\\b(reviews?|ratings?|testimonial)\\b|what our customers say/i],\n ['brands', /\\b(brands?|partners?|manufacturers?|logos)\\b|we work with|trusted by|loved by/i],\n ['gallery', /\\b(galler(y|ies)|photos?)\\b|before and after/i],\n ['case_study', /case stud|selected work|portfolio|our projects?|recent work/i],\n ['awards', /\\b(awards?|accolades?)\\b/i],\n ['pricing_plans', /\\b(pricing|price list|per month|\\/mo|subscriptions?)\\b|\\bplans?\\b(?=[^.]{0,40}(from|start|month|year|[$€£]))/i],\n ['quote', /get a quote|request a quote|free estimate|get an estimate/i],\n ['financing', /\\b(financ(e|ing)|instal?ments?)\\b|payment plan|pay monthly/i],\n ['booking', /\\b(book|booking|reserve|reservation|appointments?)\\b|schedule a/i],\n ['newsletter', /\\b(newsletter|subscribe)\\b|join our (list|mailing)|mailing list/i],\n ['contact_form', /contact us|get in touch|start a project|\\benquir|\\binquir|send us/i],\n ['hours', /opening hours|\\bhours\\b|open (mon|tue|wed|thu|fri|sat|sun)/i],\n ['location', /find us|our location|directions|where we are|visit us/i],\n ['menu', /\\b(menus?|dishes)\\b|our food/i],\n ['inventory', /\\bbest ?sellers?\\b|\\b(shop|products?|collection)\\b|our range|in stock/i],\n ['process', /our process|how it works|the process|what to expect|\\bsteps?\\b/i],\n ['services', /\\b(services?|repairs?|treatments?|specialit|specialt)\\b|what we (do|offer)/i],\n ['integrations', /\\bintegrations?\\b|works with|connects? to/i],\n ['features', /\\b(features?|benefits?|capabilit)\\b|what you get/i],\n ['faq', /\\bfaq\\b|frequently asked|common questions?/i],\n ['comparison', /\\b(compare|comparison|versus)\\b|\\bvs\\./i],\n ['blog', /\\b(blog|news|articles?|insights?)\\b/i],\n ['careers', /\\b(careers?|jobs?)\\b|join (the|our) team|we.re hiring/i],\n ['legal', /\\bterms\\b|privacy policy|cookie policy|\\blegal\\b/i],\n];\n\n/**\n * schema.org `@type` → topic. The highest-precision signal available, and free:\n * the crawler already has the HTML, and a site that publishes structured data\n * has told us what a section is rather than leaving us to infer it from copy.\n *\n * Ranked BELOW structural rules deliberately — many sites emit an Organization\n * or WebSite blob site-wide in the header or footer, and letting that outrank a\n * `<nav>` would turn every such navigation bar into an `about` section.\n */\nconst SCHEMA_TOPIC: Record<string, string> = {\n FAQPage: 'faq',\n Question: 'faq',\n Review: 'reviews',\n AggregateRating: 'reviews',\n Offer: 'pricing_plans',\n AggregateOffer: 'pricing_plans',\n PriceSpecification: 'pricing_plans',\n Service: 'services',\n Product: 'inventory',\n ItemList: 'inventory',\n Menu: 'menu',\n MenuSection: 'menu',\n LocalBusiness: 'location',\n PostalAddress: 'location',\n OpeningHoursSpecification: 'hours',\n BreadcrumbList: 'breadcrumb',\n ImageGallery: 'gallery',\n JobPosting: 'careers',\n BlogPosting: 'blog',\n Article: 'blog',\n Organization: 'about',\n};\n\nconst TOPIC_CONTENT: Array<[string, RegExp]> = [\n ['pricing_plans', /(?:[$€£]\\s?\\d[\\d,.]*\\s*(?:\\/|per\\s)\\s*(?:mo|month|yr|year|seat|user))|(?:\\b(?:starter|basic|pro|growth|premium|enterprise)\\b[^.]{0,60}[$€£]\\s?\\d)/i],\n ['reviews', /(?:★{2,})|(?:\\b\\d(?:\\.\\d)?\\s*(?:out of|\\/)\\s*5\\b)|(?:\\brated\\b)|(?:[\"“][^\"”]{20,160}[\"”]\\s*[—–-]\\s*[A-Z][a-z]+)/],\n ['guarantee', /\\b(?:money[- ]back guarantee|free returns?|returns? within|refund(?:s)? within|cancel anytime)\\b/i],\n ['security', /\\b(?:encrypted|soc ?2|iso ?27001|gdpr[- ]compliant)\\b/i],\n ['comparison', /\\b(?:vs|versus)\\b\\.?[^.!?]{0,80}\\b(?:compare|comparison|plans?|features?|alternative)\\b|\\bhow (?:we|it) compares?\\b/i],\n];\n\n/** Which classifier stage produced the label. Persisted as provenance (the\n * `page_semantics.source` column distinguishes structured_data from\n * heuristic), so it must name the stage that actually MATCHED — a section\n * that merely CONTAINS a JSON-LD blob but was classified by its `<nav>` tag\n * is heuristic evidence, not the site telling us what it is. */\nexport type ClassifierStage = 'structural' | 'structured_data' | 'keyword' | 'content' | 'fallback';\n\n/**\n * Classify a section into a topic, its parent, and its functional role.\n * Structure and fallbacks are the shared implementation from ./classify.ts, so\n * the browser cannot disagree with the server about what a `<nav>` is.\n */\nexport function classifyTopic(\n f: SectionFeatures,\n): TopicRule & { strength: 'strong' | 'weak'; stage: ClassifierStage } {\n const structural = structuralTopicOf(f);\n if (structural) return { ...ruleFor(structural), strength: 'strong', stage: 'structural' };\n\n // Stage 0: the site told us what this is. Beats every inferred keyword.\n for (const t of f.structuredTypes ?? []) {\n const topic = SCHEMA_TOPIC[t];\n if (topic) return { ...ruleFor(topic), strength: 'strong', stage: 'structured_data' };\n }\n\n const hay = `${f.idClass} ${f.headingText}`.toLowerCase();\n for (const [topic, re] of TOPIC_KEYWORDS) {\n if (re.test(hay)) return { ...ruleFor(topic), strength: 'strong', stage: 'keyword' };\n }\n for (const [topic, re] of TOPIC_CONTENT) {\n if (re.test(f.bodyText)) return { ...ruleFor(topic), strength: 'strong', stage: 'content' };\n }\n return { ...ruleFor(fallbackTopicOf(f)), strength: 'weak', stage: 'fallback' };\n}\n\nexport type { SemanticType, SectionFeatures, TopicRule };\n"],"mappings":"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","names":["CLASSIFIER_TOPICS","RULE","t","ruleFor","topic","_a","TOPIC_KEYWORDS","SCHEMA_TOPIC","TOPIC_CONTENT","classifyTopic","f","structural","structuralTopicOf","__spreadProps","__spreadValues","hay","re","fallbackTopicOf"]}
package/dist/index.d.cts CHANGED
@@ -1,3 +1,3 @@
1
- export { A as AssignResult, a as Assignment, B as BLOCK_ALIGNS, c as BLOCK_EMPHASES, d as BLOCK_FITS, e as BLOCK_GAPS, f as BLOCK_GRID_COLUMNS, g as BLOCK_HEADING_LEVELS, h as BLOCK_JUSTIFIES, i as BLOCK_RATIOS, j as BLOCK_SIZES, k as BLOCK_TEXT_ALIGNS, l as BLOCK_TONES, m as BLOCK_WEIGHTS, n as BadgeBlock, o as BlockAlign, p as BlockEmphasis, q as BlockFit, r as BlockGap, s as BlockJustify, t as BlockNode, u as BlockRatio, v as BlockSize, w as BlockTextAlign, x as BlockTone, y as BlockWeight, z as ButtonBlock, C as ComponentGoalOptions, D as ComponentWeightEntry, E as CompoundLocator, F as DecideInput, G as DecideOutcome, H as DecisionSnapshot, J as EventType, K as GoalDefinition, L as GoalOptions, N as GraphConfig, O as GraphSnapshot, P as GridBlock, Q as HeadingBlock, R as ImageBlock, S as LEGACY_SESSION_COOKIE_NAME, T as LOCAL_MODE_BANNER, U as LinkBlock, V as MAX_BLOCK_ARMS, W as MAX_BLOCK_CHILDREN, X as MAX_BLOCK_DEPTH, Y as MAX_BLOCK_NODES, Z as MAX_BLOCK_TEXT_LEN, _ as MicroSignalEmitter, $ as MicroSignalType, a0 as PROD_KEYLESS_ERROR, a2 as QueueConfig, a3 as SNAPSHOT_STORAGE_KEY_PREFIX, a4 as SectionMapEntry, a5 as SentientClient, a6 as SentientConfig, a7 as SentientEvent, a8 as SessionConfig, a9 as SessionManager, aa as SitePalette, ab as SlotConfigEntry, ac as SlotOps, ad as SpacerBlock, ae as StackBlock, af as TextBlock, ag as WeightEntry, ah as attachMicroSignalDetectors, ai as grantConsent, aj as init, ak as isDoNotTrackEnabled, al as readSnapshot, am as renderPrePaintScript, ao as sessionCookieName, ap as writeSnapshot } from './index-D3_vlZ3z.cjs';
1
+ export { A as AssignResult, a as Assignment, B as BLOCK_ALIGNS, c as BLOCK_EMPHASES, d as BLOCK_FITS, e as BLOCK_GAPS, f as BLOCK_GRID_COLUMNS, g as BLOCK_HEADING_LEVELS, h as BLOCK_JUSTIFIES, i as BLOCK_RATIOS, j as BLOCK_SIZES, k as BLOCK_TEXT_ALIGNS, l as BLOCK_TONES, m as BLOCK_WEIGHTS, n as BadgeBlock, o as BlockAlign, p as BlockEmphasis, q as BlockFit, r as BlockGap, s as BlockJustify, t as BlockNode, u as BlockRatio, v as BlockSize, w as BlockTextAlign, x as BlockTone, y as BlockWeight, z as ButtonBlock, C as ComponentGoalOptions, D as ComponentWeightEntry, E as CompoundLocator, F as DecideInput, G as DecideOutcome, H as DecisionSnapshot, J as EventType, K as GoalDefinition, L as GoalOptions, N as GraphConfig, O as GraphSnapshot, P as GridBlock, Q as HeadingBlock, R as ImageBlock, S as LEGACY_SESSION_COOKIE_NAME, T as LOCAL_MODE_BANNER, U as LinkBlock, V as MAX_BLOCK_ARMS, W as MAX_BLOCK_CHILDREN, X as MAX_BLOCK_DEPTH, Y as MAX_BLOCK_NODES, Z as MAX_BLOCK_TEXT_LEN, _ as MicroSignalEmitter, $ as MicroSignalType, a0 as PROD_KEYLESS_ERROR, a2 as QueueConfig, a3 as SNAPSHOT_STORAGE_KEY_PREFIX, a4 as SectionMapEntry, a5 as SentientClient, a6 as SentientConfig, a7 as SentientEvent, a8 as SessionConfig, a9 as SessionManager, aa as SitePalette, ab as SlotConfigEntry, ac as SlotOps, ad as SpacerBlock, ae as StackBlock, af as TextBlock, ag as WeightEntry, ah as _registerConsentUpgradeInit, ai as attachMicroSignalDetectors, aj as grantConsent, ak as init, al as isDoNotTrackEnabled, am as readSnapshot, an as renderPrePaintScript, ap as sessionCookieName, aq as writeSnapshot } from './index-CXxvWxCB.cjs';
2
2
  export { A as AGENT_INTENTS, a as AgentIntent, b as SlotDeclInput, c as agentIntent, d as agentUaList, e as armOfResult, f as baselineResultFor, g as baselineSlots, i as classifiedAgents, j as deriveSessionSegment, k as detectDeviceClass, l as detectTimeOfDay, m as detectTrafficSource, n as matchedAgentToken, r as referrerDomainFromReferer, t as toWireSlot, u as uaTokenMatch } from './session-meta-BVvq5RBB.cjs';
3
3
  export { SlotResult } from '@sentientui/policy';
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export { A as AssignResult, a as Assignment, B as BLOCK_ALIGNS, c as BLOCK_EMPHASES, d as BLOCK_FITS, e as BLOCK_GAPS, f as BLOCK_GRID_COLUMNS, g as BLOCK_HEADING_LEVELS, h as BLOCK_JUSTIFIES, i as BLOCK_RATIOS, j as BLOCK_SIZES, k as BLOCK_TEXT_ALIGNS, l as BLOCK_TONES, m as BLOCK_WEIGHTS, n as BadgeBlock, o as BlockAlign, p as BlockEmphasis, q as BlockFit, r as BlockGap, s as BlockJustify, t as BlockNode, u as BlockRatio, v as BlockSize, w as BlockTextAlign, x as BlockTone, y as BlockWeight, z as ButtonBlock, C as ComponentGoalOptions, D as ComponentWeightEntry, E as CompoundLocator, F as DecideInput, G as DecideOutcome, H as DecisionSnapshot, J as EventType, K as GoalDefinition, L as GoalOptions, N as GraphConfig, O as GraphSnapshot, P as GridBlock, Q as HeadingBlock, R as ImageBlock, S as LEGACY_SESSION_COOKIE_NAME, T as LOCAL_MODE_BANNER, U as LinkBlock, V as MAX_BLOCK_ARMS, W as MAX_BLOCK_CHILDREN, X as MAX_BLOCK_DEPTH, Y as MAX_BLOCK_NODES, Z as MAX_BLOCK_TEXT_LEN, _ as MicroSignalEmitter, $ as MicroSignalType, a0 as PROD_KEYLESS_ERROR, a2 as QueueConfig, a3 as SNAPSHOT_STORAGE_KEY_PREFIX, a4 as SectionMapEntry, a5 as SentientClient, a6 as SentientConfig, a7 as SentientEvent, a8 as SessionConfig, a9 as SessionManager, aa as SitePalette, ab as SlotConfigEntry, ac as SlotOps, ad as SpacerBlock, ae as StackBlock, af as TextBlock, ag as WeightEntry, ah as attachMicroSignalDetectors, ai as grantConsent, aj as init, ak as isDoNotTrackEnabled, al as readSnapshot, am as renderPrePaintScript, ao as sessionCookieName, ap as writeSnapshot } from './index-BcRtGYVH.js';
1
+ export { A as AssignResult, a as Assignment, B as BLOCK_ALIGNS, c as BLOCK_EMPHASES, d as BLOCK_FITS, e as BLOCK_GAPS, f as BLOCK_GRID_COLUMNS, g as BLOCK_HEADING_LEVELS, h as BLOCK_JUSTIFIES, i as BLOCK_RATIOS, j as BLOCK_SIZES, k as BLOCK_TEXT_ALIGNS, l as BLOCK_TONES, m as BLOCK_WEIGHTS, n as BadgeBlock, o as BlockAlign, p as BlockEmphasis, q as BlockFit, r as BlockGap, s as BlockJustify, t as BlockNode, u as BlockRatio, v as BlockSize, w as BlockTextAlign, x as BlockTone, y as BlockWeight, z as ButtonBlock, C as ComponentGoalOptions, D as ComponentWeightEntry, E as CompoundLocator, F as DecideInput, G as DecideOutcome, H as DecisionSnapshot, J as EventType, K as GoalDefinition, L as GoalOptions, N as GraphConfig, O as GraphSnapshot, P as GridBlock, Q as HeadingBlock, R as ImageBlock, S as LEGACY_SESSION_COOKIE_NAME, T as LOCAL_MODE_BANNER, U as LinkBlock, V as MAX_BLOCK_ARMS, W as MAX_BLOCK_CHILDREN, X as MAX_BLOCK_DEPTH, Y as MAX_BLOCK_NODES, Z as MAX_BLOCK_TEXT_LEN, _ as MicroSignalEmitter, $ as MicroSignalType, a0 as PROD_KEYLESS_ERROR, a2 as QueueConfig, a3 as SNAPSHOT_STORAGE_KEY_PREFIX, a4 as SectionMapEntry, a5 as SentientClient, a6 as SentientConfig, a7 as SentientEvent, a8 as SessionConfig, a9 as SessionManager, aa as SitePalette, ab as SlotConfigEntry, ac as SlotOps, ad as SpacerBlock, ae as StackBlock, af as TextBlock, ag as WeightEntry, ah as _registerConsentUpgradeInit, ai as attachMicroSignalDetectors, aj as grantConsent, ak as init, al as isDoNotTrackEnabled, am as readSnapshot, an as renderPrePaintScript, ap as sessionCookieName, aq as writeSnapshot } from './index-BbrtAtrY.js';
2
2
  export { A as AGENT_INTENTS, a as AgentIntent, b as SlotDeclInput, c as agentIntent, d as agentUaList, e as armOfResult, f as baselineResultFor, g as baselineSlots, i as classifiedAgents, j as deriveSessionSegment, k as detectDeviceClass, l as detectTimeOfDay, m as detectTrafficSource, n as matchedAgentToken, r as referrerDomainFromReferer, t as toWireSlot, u as uaTokenMatch } from './session-meta-BVvq5RBB.js';
3
3
  export { SlotResult } from '@sentientui/policy';