@sentientui/core 0.24.0 → 0.26.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.
- package/dist/{chunk-RL5B6M4G.mjs → chunk-RCN2SIB6.mjs} +2 -2
- package/dist/chunk-RCN2SIB6.mjs.map +1 -0
- package/dist/chunk-WKXD7KI3.mjs +2 -0
- package/dist/chunk-WKXD7KI3.mjs.map +1 -0
- package/dist/chunk-Z6ILOYBD.mjs +2 -0
- package/dist/chunk-Z6ILOYBD.mjs.map +1 -0
- package/dist/{index-Cd0gMdJv.d.cts → index-Ce3IPLrF.d.cts} +16 -0
- package/dist/{index-BjVvN7cU.d.ts → index-DubrRV6l.d.ts} +16 -0
- package/dist/index-engagement.d.cts +1 -1
- package/dist/index-engagement.d.ts +1 -1
- package/dist/index-engagement.js +1 -1
- package/dist/index-engagement.js.map +1 -1
- package/dist/index-engagement.mjs +1 -1
- package/dist/index-engagement.mjs.map +1 -1
- package/dist/index-graph.d.cts +2 -2
- package/dist/index-graph.d.ts +2 -2
- package/dist/index-graph.js +1 -1
- package/dist/index-graph.js.map +1 -1
- package/dist/index-graph.mjs +1 -1
- package/dist/index-local.d.cts +1 -1
- package/dist/index-local.d.ts +1 -1
- package/dist/index-topics.d.cts +16 -1
- package/dist/index-topics.d.ts +16 -1
- package/dist/index-topics.js +1 -1
- package/dist/index-topics.js.map +1 -1
- package/dist/index-topics.mjs +1 -1
- package/dist/index-topics.mjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +2 -2
- package/dist/chunk-EWP6FTHE.mjs +0 -2
- package/dist/chunk-EWP6FTHE.mjs.map +0 -1
- package/dist/chunk-KIP52DUJ.mjs +0 -2
- package/dist/chunk-KIP52DUJ.mjs.map +0 -1
- package/dist/chunk-RL5B6M4G.mjs.map +0 -1
package/dist/index-graph.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
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-RL5B6M4G.mjs";import{a as N,b as C,c as b,j,k as K,l as U,o as F,p as q,r as z,s as B,t as H,u as Y}from"./chunk-2NVSFI4V.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};
|
|
1
|
+
import{a as w}from"./chunk-WKXD7KI3.mjs";import{a as v,g as A}from"./chunk-Z6ILOYBD.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-RCN2SIB6.mjs";import{a as N,b as C,c as b,j,k as K,l as U,o as F,p as q,r as z,s as B,t as H,u as Y}from"./chunk-2NVSFI4V.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
|
package/dist/index-local.d.cts
CHANGED
package/dist/index-local.d.ts
CHANGED
package/dist/index-topics.d.cts
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
import { T as TopicRule, a as SectionFeatures } from './classify-DYlSjkFP.cjs';
|
|
2
2
|
export { b as SemanticType } from './classify-DYlSjkFP.cjs';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Version of the classification BEHAVIOUR (rules + taxonomy + stages), read by
|
|
6
|
+
* the server's shadow-label rollout (section-classify.ts): a project whose
|
|
7
|
+
* promoted version differs gets new labels written to SHADOW only, while its
|
|
8
|
+
* old labels keep serving until an operator promotes (with one-click rollback,
|
|
9
|
+
* the A4 pattern).
|
|
10
|
+
*
|
|
11
|
+
* BUMP THIS whenever a change here or in ./classify.ts alters what any section
|
|
12
|
+
* would be labelled — rule edits, threshold changes, new stages, taxonomy
|
|
13
|
+
* moves. Comment-only and refactor-only changes do not bump. The corpus gates
|
|
14
|
+
* catch accuracy regressions; this constant is what keeps a deliberate
|
|
15
|
+
* improvement from silently re-labelling every live project's serving rows on
|
|
16
|
+
* their next audit.
|
|
17
|
+
*/
|
|
18
|
+
declare const CLASSIFIER_VERSION = "2026-09-05";
|
|
4
19
|
declare const CLASSIFIER_TOPICS: readonly TopicRule[];
|
|
5
20
|
/** Which classifier stage produced the label. Persisted as provenance (the
|
|
6
21
|
* `page_semantics.source` column distinguishes structured_data from
|
|
@@ -18,4 +33,4 @@ declare function classifyTopic(f: SectionFeatures): TopicRule & {
|
|
|
18
33
|
stage: ClassifierStage;
|
|
19
34
|
};
|
|
20
35
|
|
|
21
|
-
export { CLASSIFIER_TOPICS, SectionFeatures, TopicRule, classifyTopic };
|
|
36
|
+
export { CLASSIFIER_TOPICS, CLASSIFIER_VERSION, SectionFeatures, TopicRule, classifyTopic };
|
package/dist/index-topics.d.ts
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
import { T as TopicRule, a as SectionFeatures } from './classify-DYlSjkFP.js';
|
|
2
2
|
export { b as SemanticType } from './classify-DYlSjkFP.js';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Version of the classification BEHAVIOUR (rules + taxonomy + stages), read by
|
|
6
|
+
* the server's shadow-label rollout (section-classify.ts): a project whose
|
|
7
|
+
* promoted version differs gets new labels written to SHADOW only, while its
|
|
8
|
+
* old labels keep serving until an operator promotes (with one-click rollback,
|
|
9
|
+
* the A4 pattern).
|
|
10
|
+
*
|
|
11
|
+
* BUMP THIS whenever a change here or in ./classify.ts alters what any section
|
|
12
|
+
* would be labelled — rule edits, threshold changes, new stages, taxonomy
|
|
13
|
+
* moves. Comment-only and refactor-only changes do not bump. The corpus gates
|
|
14
|
+
* catch accuracy regressions; this constant is what keeps a deliberate
|
|
15
|
+
* improvement from silently re-labelling every live project's serving rows on
|
|
16
|
+
* their next audit.
|
|
17
|
+
*/
|
|
18
|
+
declare const CLASSIFIER_VERSION = "2026-09-05";
|
|
4
19
|
declare const CLASSIFIER_TOPICS: readonly TopicRule[];
|
|
5
20
|
/** Which classifier stage produced the label. Persisted as provenance (the
|
|
6
21
|
* `page_semantics.source` column distinguishes structured_data from
|
|
@@ -18,4 +33,4 @@ declare function classifyTopic(f: SectionFeatures): TopicRule & {
|
|
|
18
33
|
stage: ClassifierStage;
|
|
19
34
|
};
|
|
20
35
|
|
|
21
|
-
export { CLASSIFIER_TOPICS, SectionFeatures, TopicRule, classifyTopic };
|
|
36
|
+
export { CLASSIFIER_TOPICS, CLASSIFIER_VERSION, SectionFeatures, TopicRule, classifyTopic };
|
package/dist/index-topics.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var p=Object.defineProperty,
|
|
1
|
+
"use strict";var p=Object.defineProperty,h=Object.defineProperties,w=Object.getOwnPropertyDescriptor,v=Object.getOwnPropertyDescriptors,T=Object.getOwnPropertyNames,l=Object.getOwnPropertySymbols;var g=Object.prototype.hasOwnProperty,S=Object.prototype.propertyIsEnumerable;var b=(e,t,r)=>t in e?p(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,n=(e,t)=>{for(var r in t||(t={}))g.call(t,r)&&b(e,r,t[r]);if(l)for(var r of l(t))S.call(t,r)&&b(e,r,t[r]);return e},i=(e,t)=>h(e,v(t));var x=(e,t)=>{for(var r in t)p(e,r,{get:t[r],enumerable:!0})},_=(e,t,r,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of T(t))!g.call(e,o)&&o!==r&&p(e,o,{get:()=>t[o],enumerable:!(s=w(t,o))||s.enumerable});return e};var R=e=>_(p({},"__esModule",{value:!0}),e);var I={};x(I,{CLASSIFIER_TOPICS:()=>u,CLASSIFIER_VERSION:()=>m,classifyTopic:()=>y});module.exports=R(I);var C={banner:"hero",navigation:"navigation",contentinfo:"footer"};function d(e){let t=e.ariaRole?C[e.ariaRole.toLowerCase()]:void 0;if(t)return t;if(e.tag==="nav")return"navigation";if(e.tag==="footer")return"footer";if(e.tag==="header")return e.actionCount>=5?"navigation":"hero";let r=`${e.idClass} ${e.headingText}`.toLowerCase();return/\b(navbar|nav-bar|navigation|site-nav|main-nav|topbar|footer)\b/.test(r)?/footer/.test(r)?"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 m="2026-09-05",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"}],k=new Map(u.map(e=>[e.topic,e]));function c(e){var t;return(t=k.get(e))!=null?t:{topic:"generic",parent:"generic",role:"persuader"}}var E=[["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]],A={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"},q=[["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 y(e){var s;let t=d(e);if(t)return i(n({},c(t)),{strength:"strong",stage:"structural"});for(let o of(s=e.structuredTypes)!=null?s:[]){let a=A[o];if(a)return i(n({},c(a)),{strength:"strong",stage:"structured_data"})}let r=`${e.idClass} ${e.headingText}`.toLowerCase();for(let[o,a]of E)if(a.test(r))return i(n({},c(o)),{strength:"strong",stage:"keyword"});for(let[o,a]of q)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,CLASSIFIER_VERSION,classifyTopic});
|
|
2
2
|
//# sourceMappingURL=index-topics.js.map
|
package/dist/index-topics.js.map
CHANGED
|
@@ -1 +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":"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","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"]}
|
|
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 CLASSIFIER_VERSION,\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 //\n // …EXCEPT when the header IS the site's navigation. The hold-out corpus\n // (2026-09-05, six real unseen sites) showed most real pages wrap their nav\n // in <header> — mega-menus with 10–114 links and almost no prose — and\n // hero-typing those cost the classifier a 0.11 hero precision. A hero header\n // carries a headline and one or two calls to action; a nav header is\n // link-dominated, so the action count is the discriminator. Measured on the\n // hold-out: nav headers had 10+ actions (one at 4), authored hero headers\n // have 0–2.\n if (f.tag === 'header') return f.actionCount >= 5 ? 'navigation' : '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\n/**\n * Version of the classification BEHAVIOUR (rules + taxonomy + stages), read by\n * the server's shadow-label rollout (section-classify.ts): a project whose\n * promoted version differs gets new labels written to SHADOW only, while its\n * old labels keep serving until an operator promotes (with one-click rollback,\n * the A4 pattern).\n *\n * BUMP THIS whenever a change here or in ./classify.ts alters what any section\n * would be labelled — rule edits, threshold changes, new stages, taxonomy\n * moves. Comment-only and refactor-only changes do not bump. The corpus gates\n * catch accuracy regressions; this constant is what keeps a deliberate\n * improvement from silently re-labelling every live project's serving rows on\n * their next audit.\n */\nexport const CLASSIFIER_VERSION = '2026-09-05';\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":["index_topics_exports","__export","CLASSIFIER_TOPICS","CLASSIFIER_VERSION","classifyTopic","__toCommonJS","ARIA_TOPIC","structuralTopicOf","f","aria","hay","fallbackTopicOf","CLASSIFIER_VERSION","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-topics.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{b as c,c as p}from"./chunk-
|
|
1
|
+
import{b as c,c as p}from"./chunk-Z6ILOYBD.mjs";import{a as t,b as o}from"./chunk-TMCGHANO.mjs";var b="2026-09-05",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"}],g=new Map(u.map(e=>[e.topic,e]));function s(e){var a;return(a=g.get(e))!=null?a:{topic:"generic",parent:"generic",role:"persuader"}}var d=[["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]],f={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"},m=[["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 y(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=f[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 d)if(r.test(l))return o(t({},s(i)),{strength:"strong",stage:"keyword"});for(let[i,r]of m)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,b as CLASSIFIER_VERSION,y as classifyTopic};
|
|
2
2
|
//# sourceMappingURL=index-topics.mjs.map
|
|
@@ -1 +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"]}
|
|
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\n/**\n * Version of the classification BEHAVIOUR (rules + taxonomy + stages), read by\n * the server's shadow-label rollout (section-classify.ts): a project whose\n * promoted version differs gets new labels written to SHADOW only, while its\n * old labels keep serving until an operator promotes (with one-click rollback,\n * the A4 pattern).\n *\n * BUMP THIS whenever a change here or in ./classify.ts alters what any section\n * would be labelled — rule edits, threshold changes, new stages, taxonomy\n * moves. Comment-only and refactor-only changes do not bump. The corpus gates\n * catch accuracy regressions; this constant is what keeps a deliberate\n * improvement from silently re-labelling every live project's serving rows on\n * their next audit.\n */\nexport const CLASSIFIER_VERSION = '2026-09-05';\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_VERSION","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 _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-
|
|
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-Ce3IPLrF.cjs';
|
|
2
2
|
export { A as AGENT_INTENTS, a as AgentIntent, C as CLICK_ID_KEYS, 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 extractTrackedParams, o as matchedAgentToken, r as referrerDomainFromReferer, t as toWireSlot, u as uaTokenMatch } from './session-meta-C-bMqVeq.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 _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-
|
|
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-DubrRV6l.js';
|
|
2
2
|
export { A as AGENT_INTENTS, a as AgentIntent, C as CLICK_ID_KEYS, 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 extractTrackedParams, o as matchedAgentToken, r as referrerDomainFromReferer, t as toWireSlot, u as uaTokenMatch } from './session-meta-C-bMqVeq.js';
|
|
3
3
|
export { SlotResult } from '@sentientui/policy';
|