@fangorn-network/westmarch 0.0.13-dev → 0.0.15-dev

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fangorn-network/westmarch",
3
- "version": "0.0.13-dev",
3
+ "version": "0.0.15-dev",
4
4
  "description": "Publisher and consumer surfaces for a Fangorn app: shape a graph, publish it, read it back as a searchable, agent-drivable catalog",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/site/agent.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // The stock page's tools for agents in a browser: the same verbs as fangorn-mcp's data
2
2
  // tools, over the rows this page loaded. `ctx` holds them; ship's card captures these.
3
- import { describe, facet, getRow, search } from "../src/agent/tools.js";
3
+ import { describe, facet, getRow, search, threads } from "../src/agent/tools.js";
4
4
  import { KNOBS, discover } from "../src/taste/taste.js";
5
5
 
6
6
  const ok = (o) => ({ content: [{ type: "text", text: JSON.stringify(o) }] });
@@ -34,6 +34,12 @@ export function registerAgent(ctx) {
34
34
  inputSchema: obj({ field: { type: "string" }, where: { type: "object" }, limit: { type: "number" } }, ["field"]),
35
35
  execute: async ({ field, where, limit = 20 }) => ok(facet(ctx.rows, field, { where, limit })),
36
36
  });
37
+ mc.registerTool({
38
+ name: "threads",
39
+ description: "One thing followed across records (a matter from committee to council): each thread's steps in date order, the latest-moving first, with the head record when there is one. `id` for one thread (a step's thread field); `where` narrows the steps.",
40
+ inputSchema: obj({ id: { type: "string" }, where: { type: "object" }, limit: { type: "number" }, offset: { type: "number" } }),
41
+ execute: async ({ id, where, limit = 20, offset = 0 }) => ok(threads(ctx.rows, ctx.roles, { id, where, limit, offset })),
42
+ });
37
43
  // What the person using this page has done: saved, passed on, searched, and the taste
38
44
  // that makes. Theirs, in their browser; an agent in the same browser can act on it.
39
45
  mc.registerTool({
@@ -0,0 +1,8 @@
1
+ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./transformers.web-CVP3pJOS.js","./rolldown-runtime-BcKkbAw3.js","./preload-helper-uBIymjUX.js","./__vite-browser-external-CitKSmYk.js","./roles-32M1l9jI.js","./roles-DKPW79Ca.js","./wallet-DMLc-ukR.js","./defineChain-CiuCXUZW.js","./chain-DM3oH6Px.js","./utils-BkR7EQjg.js","./formatter-CVbp4SlJ.js","./baseSepolia-DX-qGsik.js","./serializeAuthorizationList-CX8hGL56.js","./sha2-BU_QyRTx.js"])))=>i.map(i=>d[i]);
2
+ import{s as e}from"./rolldown-runtime-BcKkbAw3.js";import{t}from"./preload-helper-uBIymjUX.js";import{c as n,d as r,f as i,g as a,h as o,m as s,n as c,p as l,r as u,s as d}from"./roles-DKPW79Ca.js";(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),t.credentials=e.crossOrigin===`use-credentials`?`include`:e.crossOrigin===`anonymous`?`omit`:`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var f=`nomic-ai/nomic-embed-text-v1.5`,p=256,m={}.WESTMARCH_EMBED_DTYPE??`q8`,h=null,g=()=>h??=(async()=>{let{pipeline:e}=await t(async()=>{let{pipeline:e}=await import(`./transformers.web-CVP3pJOS.js`);return{pipeline:e}},__vite__mapDeps([0,1,2]),import.meta.url),n={}.WESTMARCH_EMBED_DEVICE;return e(`feature-extraction`,f,{dtype:m,...n&&{device:n}})})();function _(e,t=p){let n=e.length,r=0;for(let t=0;t<n;t++)r+=e[t];r/=n;let i=0;for(let t=0;t<n;t++)i+=(e[t]-r)**2;let a=Math.sqrt(i/n+1e-5),o=Array(Math.min(t,n));for(let t=0;t<o.length;t++)o[t]=(e[t]-r)/a;let s=0;for(let e of o)s+=e*e;if(s=Math.sqrt(s),s)for(let e=0;e<o.length;e++)o[e]/=s;return o}async function v(e){return _((await(await g())(`search_query: ${e}`,{pooling:`mean`,normalize:!1})).data)}var ee=()=>typeof window<`u`&&typeof Worker<`u`,y=null,te=0,b=new Map;function ne(){return y||(y=new Worker(new URL(new URL(`embed.worker-ugGhEqCV.js`,import.meta.url).href,``+import.meta.url),{type:`module`}),y.onmessage=({data:e})=>{let t=b.get(e.id);t&&(b.delete(e.id),e.error?t.reject(Error(e.error)):t.resolve(e.vec))},y.onerror=e=>{for(let t of b.values())t.reject(Error(`embed worker: ${e?.message??`failed to start`}`));b.clear(),y=null},y)}var re=(e,t)=>new Promise((n,r)=>{let i=++te;b.set(i,{resolve:n,reject:r}),ne().postMessage({id:i,kind:e,text:t})}),ie=e=>ee()?re(`query`,e):v(e);function x(e){let t=new Uint8Array(e.length);for(let n=0;n<e.length;n++){let r=Math.round(Math.max(-1,Math.min(1,e[n]))*127);t[n]=r<0?r+256:r}let n=``;for(let e of t)n+=String.fromCharCode(e);return btoa(n)}function S(e){try{let t=atob(e),n=new(globalThis.Float16Array??Float32Array)(t.length);for(let e=0;e<t.length;e++){let r=t.charCodeAt(e);n[e]=(r>127?r-256:r)/127}return n.length?n:null}catch{return null}}function ae(e){let t=new Uint8Array(Math.ceil(e.length/8));for(let n=0;n<e.length;n++)e[n]>=0&&(t[n>>3]|=128>>(n&7));let n=``;for(let e of t)n+=String.fromCharCode(e);return btoa(n)}function oe(e){try{let t=atob(e.padEnd(Math.ceil(e.length/4)*4,`=`)),n=new Float32Array(t.length*8);for(let e=0;e<n.length;e++)n[e]=t.charCodeAt(e>>3)>>7-(e&7)&1?1:-1;return n.length?n:null}catch{return null}}if(typeof process<`u`&&import.meta.url===`file://${process.argv[1]}`&&process.argv[1].endsWith(`/embed.js`)){let e=_(Array.from({length:768},(e,t)=>Math.sin(t))),t=0;for(let n of e)t+=n*n;if(Math.abs(Math.sqrt(t)-1)>1e-6)throw Error(`matryoshka must L2-normalize`);if(e.length!==p)throw Error(`matryoshka must truncate to ${p}`);let n=S(x(e));if(n.length!==e.length)throw Error(`pack/unpack changed the dimension`);let r=0,i=0;for(let t=0;t<e.length;t++)r+=e[t]*n[t],i+=n[t]*n[t];let a=r/Math.sqrt(i);if(a<.999)throw Error(`int8 round-trip cosine ${a} — too lossy to rank with`);let o=S(x([-1,-.5,0,.5,1]));if(o[0]>-.99||o[1]>-.49||o[1]<-.51)throw Error(`negative components corrupted: ${[...o]}`);if(S(`!!!not base64!!!`)!==null)throw Error(`malformed vector must return null, not throw`);let s=ae(e);if(s.length>48)throw Error(`sign bits must stay link-sized, got ${s.length} chars`);let c=oe(s);if(c.length!==e.length)throw Error(`sign round-trip changed the dimension`);for(let t=0;t<e.length;t++)if(Math.sign(c[t])!==(e[t]<0?-1:1))throw Error(`sign flipped at ${t}`);if(ae(c)!==s)throw Error(`sign packing must be idempotent`);if(oe(`!!!not base64!!!`)!==null)throw Error(`malformed bits must return null, not throw`);console.log(`embed.js self-check ok — matryoshka norm, int8 round-trip, negatives, malformed input, sign bits`)}var C=(e,t,n)=>{let r=0;for(let n=0;n<t.length&&n<e.vector.length;n++)r+=e.vector[n]*t[n];return r/(e.norm*n)},w=e=>{let t=0;for(let n of e)t+=n*n;return Math.sqrt(t)||1};function se(e,t=1){if(e.length<3)return-1/0;let n=e.reduce((e,t)=>e+t,0)/e.length,r=Math.sqrt(e.reduce((e,t)=>e+(t-n)**2,0)/e.length);return r>1e-6?n+t*r:-1/0}function ce(e,{limit:t=10,lambda:n=.7,key:r=`s`}={}){if(n>=1||e.length<=1)return e.slice(0,t);let i=e.slice(0,Math.max(t*6,30)),a=[];for(;a.length<t&&i.length;){let e=0,t=-1/0;for(let o=0;o<i.length;o++){let s=0;for(let e of a){if(!i[o].row?.vector||!e.row?.vector)continue;let t=C(i[o].row,e.row.vector,e.row.norm);t>s&&(s=t)}let c=n*i[o][r]-(1-n)*s;c>t&&(t=c,e=o)}a.push(i.splice(e,1)[0])}return a}var le=e=>e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`),ue=e=>RegExp(`\\b${le(e)}\\b`);function T(e,t){let n=String(e.text??``).toLowerCase();if(!n)return 0;let r=t.split(/\s+/).filter(Boolean);if(!r.length)return 0;let i=0;for(let e of r)ue(e).test(n)&&i++;if(!i)return 0;let a=n.indexOf(t);return Math.min(1,.5*(i/r.length)+(ue(t).test(n)?.35:0)+(a>=0?.15*(1-a/n.length):0))}function de(e,t){let n=e[0],r=0;for(let i of e){let e=T({text:i.text},t);e>r&&(r=e,n=i)}return n}function fe(e=[],t){let n=w(t);return e.map(e=>{let r=e.coverage;if(!r?.vectors?.length)return{...e,affinity:null};let i=-1;for(let e of r.vectors){let r=0,a=0;for(let n=0;n<e.length&&n<t.length;n++)r+=e[n]*t[n],a+=e[n]*e[n];let o=r/((Math.sqrt(a)||1)*n);o>i&&(i=o)}return{...e,affinity:i}}).sort((e,t)=>(t.affinity??-1/0)-(e.affinity??-1/0))}if(typeof process<`u`&&import.meta.url===`file://${process.argv[1]}`&&process.argv[1].endsWith(`/rank.js`)){if(T({text:`intoxicated education`},`cat`)!==0)throw Error(`substring matched — word boundaries lost`);if(T({text:`a cat sat`},`cat`)<=0)throw Error(`whole word must match`);let e=T({text:`cold war submarines`},`cold war`),t=T({text:`cold nights, the war ended`},`cold war`);if(!(e>t))throw Error(`phrase bonus lost: ${e} vs ${t}`);if(T({text:`what is c++`},`c++`)===void 0)throw Error(`must not throw on regex metachars`);if(Math.abs(C({vector:[1,0,0,0],norm:1},[1,0],1)-1)>1e-9)throw Error(`cosine must truncate to the query`);if(se([.5,.5,.5,.5])!==-1/0)throw Error(`no spread must admit everything, not nothing`);if(se([.4,.4])!==-1/0)throw Error(`too few rows to have a distribution`);let n=se([.4,.41,.42,.9]);if(!(.9>=n&&.42<n))throw Error(`floor did not separate the outlier: ${n}`);let r=[{text:`opening titles`},{text:`the submarine dives`}];if(de(r,`submarine`).text!==`the submarine dives`)throw Error(`wrong passage quoted`);if(de(r,`nothing here`).text!==`opening titles`)throw Error(`must fall back to the first passage`);{let e=(e,t)=>({vector:[e,t],norm:Math.sqrt(e*e+t*t)||1}),t=[{row:{id:`ep1`,...e(1,0)},s:.9},{row:{id:`ep2`,...e(1,.01)},s:.89},{row:{id:`ep3`,...e(1,.02)},s:.88},{row:{id:`other`,...e(0,1)},s:.7}],n=ce(t,{limit:2,lambda:1}).map(e=>e.row.id);if(n.join()!==`ep1,ep2`)throw Error(`lambda 1 must be the old behaviour: ${n}`);let r=ce(t,{limit:2,lambda:.7}).map(e=>e.row.id);if(r.join()!==`ep1,other`)throw Error(`MMR must not return the same episode twice: ${r}`);if(ce(t,{limit:4,lambda:.5})[0].row.id!==`ep1`)throw Error(`the top hit must survive`);if(ce([{row:{id:`a`},s:1}],{limit:3}).length!==1)throw Error(`vectorless rows must pass through`)}let i=fe([{name:`old`,coverage:null},{name:`far`,coverage:{vectors:[[0,1]]}},{name:`near`,coverage:{vectors:[[1,0]]}}],[1,0]);if(i.map(e=>e.name).join(`,`)!==`near,far,old`)throw Error(`domains misranked: ${i.map(e=>e.name)}`);if(i[2].affinity!==null)throw Error(`an unmeasured domain must be null, not 0`);console.log(`rank.js self-check ok — word boundaries, matryoshka cosine, z-floor, MMR drops near-duplicates without moving the top hit, passage pick, domain affinity`)}var pe=null,me=null,E=null,he=new Map,ge=new Map,_e=new Map,ve=e=>[e.name,e.path,e.desc].filter(Boolean).join(` `),ye=null,be=null;function D({resolveView:e,rowText:t,onManifests:n}={}){e&&(ye=e),t&&(ve=t),n&&(be=n)}var xe=()=>{he.clear(),ge.clear(),_e.clear(),pe=null,me=null,E=null};function Se(e){me=e?we(e):null}var Ce=()=>me,we=e=>String(e).replace(/\/+$/,``).replace(/\/(stream|cdn)$/,``);function Te(){return ye?pe??=Promise.resolve().then(()=>ye()).then(e=>e?we(e):null).catch(e=>{throw pe=null,e}):Promise.resolve(null)}function Ee(e){let t=(Array.isArray(e)?e:String(e??``).split(`,`)).map(e=>String(e).trim()).filter(Boolean);E=t.length?t:null}var De=e=>!E||E.some(t=>e===t||e.split(`/`).pop()===t||e.startsWith(t)),Oe=`no quickbeam view configured — pass a view base, or wire one with configure({ resolveView })`;async function O(e,t){let n=e??me??await Te();if(!n)throw Error(Oe);let r=ge.get(n);r||ge.set(n,r=new Set),t&&r.add(t);let i=he.get(n);if(i)return i;let a=Ie(n,e=>{for(let t of r)try{t(e)}catch{}}).finally(()=>ge.delete(n)).catch(e=>{throw he.delete(n),e});return he.set(n,a),a}async function ke(e){let t=await fetch(e);if(!t.ok)throw Error(`shard: HTTP ${t.status} for ${e}`);return Ae(new Uint8Array(await t.arrayBuffer()))}var Ae=async e=>e[0]===31&&e[1]===139?await new Response(new Blob([e]).stream().pipeThrough(new DecompressionStream(`gzip`))).text():new TextDecoder().decode(e);function je(e,t){let n=e.fields??{},r=typeof e.v==`string`?S(e.v):Array.isArray(e.embedding)?e.embedding:typeof n.embed?.vec==`string`?S(n.embed.vec):null,i=0;if(r)for(let e of r)i+=e*e;return{id:e.track_id,owner:e.owner,...n,text:ve(n,t),vector:r,norm:Math.sqrt(i)||1}}async function Me(e){let t=0;for(let n of e)t+=n.length;let n=new Uint8Array(t),r=0;for(let t of e)n.set(t,r),r+=t.length;return[...new Uint8Array(await crypto.subtle.digest(`SHA-256`,n))].map(e=>e.toString(16).padStart(2,`0`)).join(``)}async function Ne(e,t,n,r){if(!t)return;if(!r){console.warn(`shard: cannot verify ${e} — the transport decoded it (Content-Encoding), and the manifest digest is over the gzipped bytes`);return}let i=await Me(n);if(i!==t)throw Error(`shard: ${e} does not match the digest in its manifest (expected ${t.slice(0,12)}…, got ${i.slice(0,12)}…)`)}async function Pe(e,t,n){let r=await fetch(e);if(!r.ok)throw Error(`shard: HTTP ${r.status} for ${e}`);if(!r.body){let i=new Uint8Array(await r.arrayBuffer());await Ne(e,n,[i],i[0]===31&&i[1]===139);for(let e of(await Ae(i)).split(`
3
+ `))t(e);return}let i=r.body.getReader(),a=await i.read(),o=a.value??new Uint8Array,s=n?[]:null,c=new ReadableStream({start(e){o.length&&(s?.push(o),e.enqueue(o)),a.done&&e.close()},async pull(e){let{done:t,value:n}=await i.read();t?e.close():(s?.push(n),e.enqueue(n))},cancel(e){return i.cancel(e)}}),l=o[0]===31&&o[1]===139,u=l?c.pipeThrough(new DecompressionStream(`gzip`)):c,d=new TextDecoder,f=``;for(let r=u.getReader();;){let{done:i,value:a}=await r.read();f+=i?d.decode():d.decode(a,{stream:!0});let o=f.split(`
4
+ `);f=i?``:o.pop();for(let e of o)t(e);if(i){f&&t(f),await Ne(e,n,s,l);return}}}var Fe=250;async function Ie(e,t){let n=`${e}/cdn`,{domains:r=[]}=JSON.parse(await ke(`${n}/catalog`)),i=r.filter(e=>De(e.name));E&&i.length!==r.length&&console.info(`shard: pulling ${i.length} of ${r.length} domains (allowlist: ${E.join(`,`)})`),E&&!i.length&&console.warn(`shard: allowlist ${E.join(`,`)} matched none of ${r.map(e=>e.name).join(`, `)} — nothing will load`),i.length||console.warn(`shard: quickbeam view ${n} has no baked domains yet — nothing to read until the watcher bakes one.`);let a=new Map,o=new Set,s=await Promise.all(i.map(async e=>{let t=`${n}/domains/${encodeURIComponent(e.name)}`,r=JSON.parse(await ke(`${t}/manifest`));for(let e of r.tombstones??[])o.add(e);return{at:t,manifest:r,shards:r.shards??[]}})),c=s.map(e=>e.manifest);_e.set(e,c);try{be?.(c,e)}catch(e){console.warn(`shard: onManifests threw, continuing with the default rowText —`,e)}let l=Date.now(),u=e=>{!t||!e&&Date.now()-l<Fe||(t([...a.values()]),l=Date.now())};return await Promise.all(s.map(async({at:t,shards:n})=>{for(let r of n)await Pe(`${t}/shards/${r.file}`,t=>{if(!t.trim())return;let n=je(JSON.parse(t),e);o.has(n.id)||a.set(n.id,n),u()},r.sha256),u(!0)})),[...a.values()]}function Le(e,t){let n=null,r=!1;return(async()=>{let i=t??me??await Te();if(!i||r)return;n=new EventSource(`${i}/stream`);let a=()=>{xe(),e()};n.addEventListener(`added`,a),n.addEventListener(`change`,a)})().catch(()=>{}),()=>{r=!0,n?.close()}}async function Re(e,{url:t}={}){let n=t??me??await Te();if(!n)throw Error(Oe);let{domains:r=[]}=JSON.parse(await ke(`${n}/cdn/catalog`));return fe(r,e)}if(typeof process<`u`&&import.meta.url===`file://${process.argv[1]}`&&process.argv[1].endsWith(`/shard.js`)){let{gzipSync:n}=await t(async()=>{let{gzipSync:t}=await import(`./__vite-browser-external-CitKSmYk.js`).then(t=>e(t.default,1));return{gzipSync:t}},__vite__mapDeps([3,1]),import.meta.url),r=console.warn,i=console.info;console.warn=console.info=()=>{};let a=(e,t,n={},r)=>({track_id:`${e}:${t}`,owner:e,...r?{embedding:r}:{},fields:{entityType:`file`,name:t.split(`/`).pop(),path:t,...n}}),o=e=>e.map(e=>JSON.stringify(e)).join(`
5
+ `),s=`https://registry.test/q/qb_test`,c=e=>{let t=Buffer.from(e),n=Math.max(1,Math.floor(t.length/2));return{ok:!0,headers:new Headers,arrayBuffer:async()=>t,body:new ReadableStream({start(e){e.enqueue(new Uint8Array(t.subarray(0,n))),e.enqueue(new Uint8Array(t.subarray(n))),e.close()}})}},l=async e=>[...new Uint8Array(await crypto.subtle.digest(`SHA-256`,e))].map(e=>e.toString(16).padStart(2,`0`)).join(``),u=async(e,{tombstones:t={},gzipped:r=!0,keep:i=!1,coverage:a={},digest:u=!1,corrupt:d=!1}={})=>{let f=new Map;for(let[r,i]of Object.entries(e)){let e=i.map((e,t)=>`shard-000${t}-${r}.ndjson.gz`),a=new Map(e.map((e,t)=>[e,o(i[t])])),s=u?await Promise.all(e.map(async e=>l(n(Buffer.from(d?a.get(e)+`
6
+ `:a.get(e)))))):e.map(()=>void 0);f.set(r,{manifest:JSON.stringify({name:r,shards:e.map((e,t)=>({file:e,...s[t]?{sha256:s[t]}:{}})),tombstones:t[r]??[]}),bodies:a})}globalThis.fetch=async e=>{let{pathname:t}=new URL(String(e),`https://app.test`);if(t.includes(`/stream/`))return{ok:!1,status:404};if(t.endsWith(`/cdn/catalog`))return c(JSON.stringify({domains:[...f.keys()].map(e=>({name:e,...a[e]?{coverage:a[e]}:{}}))}));let i=t.match(/\/cdn\/domains\/([^/]+)\/manifest$/);if(i)return f.has(i[1])?c(f.get(i[1]).manifest):{ok:!1,status:404};if(i=t.match(/\/cdn\/domains\/([^/]+)\/shards\/([^/]+)$/),i){let e=f.get(i[1])?.bodies.get(i[2]);return e===void 0?{ok:!1,status:404}:c(r?n(Buffer.from(e)):Buffer.from(e))}return{ok:!1,status:404}},i||xe(),D({resolveView:()=>`${s}/stream`})},d=async(e,t={})=>u({d0:e},{...t,tombstones:{d0:t.tombstones??[]}}),f=e=>e.map(e=>e.path).sort().join(`,`);if(await d([[a(`0xa`,`a.md`)]]),f(await O())!==`a.md`)throw Error(`resolveView never reached the loader`);if(await d([[a(`0xa`,`plain.md`)]],{gzipped:!1}),f(await O())!==`plain.md`)throw Error(`an already-decoded body must still parse`);if(await d([[a(`0xa`,`signed.md`)]],{digest:!0}),f(await O())!==`signed.md`)throw Error(`a shard matching its digest must load`);await d([[a(`0xa`,`tampered.md`)]],{digest:!0,corrupt:!0});let p=null;try{await O()}catch(e){p=e}if(!p)throw Error(`a shard that does not match its digest must fail the load`);if(!/does not match the digest/.test(p.message))throw Error(`wrong error: ${p.message}`);p=null;try{await O()}catch(e){p=e}if(!p)throw Error(`a failed verification must not leave the rows in the cache`);let m=``;if(console.warn=e=>{m+=e},await d([[a(`0xa`,`unverifiable.md`)]],{digest:!0,gzipped:!1}),f(await O())!==`unverifiable.md`)throw Error(`an unverifiable body must still load`);if(!/cannot verify/.test(m))throw Error(`an unverifiable shard must say so, not pass silently`);if(console.warn=()=>{},await u({aaa:[[a(`0xa`,`a.md`,{desc:`alpha`}),a(`0xa`,`gone.md`,{desc:`retracted`})]],bbb:[[a(`0xb`,`b.md`,{desc:`beta`})]]},{tombstones:{aaa:[`0xa:gone.md`]}}),f(await O())!==`a.md,b.md`)throw Error(`domains must merge and tombstones retract`);for(let e of[`bbb`,[`bbb`],`bb`])if(await u({aaa:[[a(`0xa`,`a.md`)]],bbb:[[a(`0xb`,`b.md`)]]}),Ee(e),f(await O())!==`b.md`)throw Error(`allowlist ${JSON.stringify(e)} did not narrow the load`);if(await u({aaa:[[a(`0xa`,`a.md`)]]}),Ee(null),f(await O())!==`a.md`)throw Error(`a null allowlist must pull everything`);await d([[a(`0xa`,`a.md`),a(`0xa`,`gone.md`)],[a(`0xa`,`b.md`)]],{tombstones:[`0xa:gone.md`]});{let e=[],t=await O(void 0,t=>e.push(t.length));if(e.length!==2||e[0]!==1||e[1]!==2)throw Error(`must emit per shard, got ${e}`);if(t.length!==2)throw Error(`the final rows must still be whole`);if(e.some(e=>e>2))throw Error(`a tombstoned row must never surface in a partial emit`)}await d([[a(`0xa`,`a.md`)],[a(`0xa`,`b.md`)]]);{let e=[],t=[],n=O(void 0,t=>e.push(t.length)),r=O(void 0,e=>t.push(e.length));if(await Promise.all([n,r]),!e.length)throw Error(`the starting caller must still receive partials`);if(!t.length)throw Error(`a caller that joins an in-flight load must receive partials too`)}if(xe(),D({resolveView:()=>`${s}/stream`}),globalThis.fetch=async()=>({ok:!1,status:500}),await O().then(()=>{throw Error(`a 500 must reject`)},()=>{}),await d([[a(`0xa`,`a.md`)]],{keep:!0}),f(await O())!==`a.md`)throw Error(`a failed load poisoned the cache — the next read must retry`);{let e=[];await u({d0:[[a(`0xa`,`a.md`,{blurb:`declared prose`})]]}),D({onManifests:t=>e.push(`manifests:${t[0]?.name}`),rowText:t=>(e.push(`rowText`),t.blurb??``)});let t=await O();if(e[0]!==`manifests:d0`)throw Error(`onManifests must fire first, got ${e}`);if(!e.includes(`rowText`))throw Error(`rowText never ran`);if(t[0].text!==`declared prose`)throw Error(`the manifest-derived rowText did not reach toRow`);if(xe(),D({resolveView:()=>`${s}/stream`,onManifests:()=>{throw Error(`boom`)}}),(await O()).length!==1)throw Error(`a throwing onManifests must not fail the load`);D({onManifests:()=>{}})}if(await d([[a(`0xa`,`ep1.md`,{cues:[{text:`the submarine dives`}]})]]),(await O())[0].text.includes(`submarine`))throw Error(`the default rowText must not know about cues`);if(D({rowText:e=>e.cues?.length?e.cues.map(e=>e.text).join(` `):[e.name,e.path,e.desc].filter(Boolean).join(` `)}),xe(),D({resolveView:()=>`${s}/stream`}),!(await O())[0].text.includes(`submarine`))throw Error(`a configured rowText must reach toRow`);{let e=new Set;if(D({rowText:(t,n)=>(e.add(n),t.name??``),onManifests:(t,n)=>{e.add(`m:${n}`)}}),xe(),D({resolveView:()=>`${s}/stream`}),await O(),!e.has(s))throw Error(`rowText must receive its trimmed view base, saw ${[...e]}`);if(!e.has(`m:${s}`))throw Error(`onManifests must receive its view`);D({rowText:e=>[e.name,e.path,e.desc].filter(Boolean).join(` `),onManifests:()=>{}})}await d([[a(`0xa`,`v.md`,{},[3,4]),a(`0xa`,`packed.md`,{embed:{vec:`fwA=`}}),a(`0xa`,`none.md`)]]);{let e=new Map((await O()).map(e=>[e.path,e]));if(Math.abs(e.get(`v.md`).norm-5)>1e-9)throw Error(`norm must be precomputed from the plain float array`);if(!e.get(`packed.md`).vector)throw Error(`the base64 int8 form a publisher commits must unpack`);if(e.get(`none.md`).vector!==null)throw Error(`a row with no vector must be null, and rank lexically`);if(e.get(`none.md`).norm!==1)throw Error(`a zero norm would divide search by zero`)}{await u({d0:[[{track_id:`p1`,owner:`0xa`,v:`AH+B`,fields:{entityType:`file`,name:`packed.md`,path:`packed.md`}},{track_id:`p2`,owner:`0xa`,embedding:[0,1,-1],fields:{entityType:`file`,name:`old.md`,path:`old.md`}}]]});let e=new Map((await O()).map(e=>[e.path,e])),t=e.get(`packed.md`).vector,n=e.get(`old.md`).vector;if(!t)throw Error("the bake's `v` key must unpack");for(let e=0;e<3;e++)if(Math.abs(t[e]-n[e])>1/127)throw Error(`packed and float forms disagree at ${e}: ${t[e]} vs ${n[e]}`);if(Math.abs(e.get(`packed.md`).norm-e.get(`old.md`).norm)>.02)throw Error(`norm must match across wire forms`)}await u({near:[[a(`0xa`,`a.md`)]],far:[[a(`0xb`,`b.md`)]],old:[[a(`0xc`,`c.md`)]]},{coverage:{near:{vectors:[[1,0]]},far:{vectors:[[0,1]]}}});{let e=await Re([1,0]);if(e.map(e=>e.name).join(`,`)!==`near,far,old`)throw Error(`suggestDomains misranked: ${e.map(e=>e.name)}`)}await d([[a(`0xa`,`before.md`)]]);{let e=new Map,t=``,n=!1;globalThis.EventSource=class{constructor(e){t=e}addEventListener(t,n){e.set(t,n)}close(){n=!0}};let r=0,i=Le(()=>{r++});if(await new Promise(e=>setTimeout(e,0)),t!==`${s}/stream`)throw Error(`watchShard must open the view's SSE stream, got ${t}`);if(e.has(`snapshot`))throw Error(`snapshot is the connect-time census, not a change`);if(f(await O())!==`before.md`)throw Error(`watchShard fixture never loaded`);if(await d([[a(`0xa`,`after.md`)]],{keep:!0}),e.get(`change`)(),r!==1)throw Error("a `change` event must reach the caller");if(f(await O())!==`after.md`)throw Error("a `change` must drop the cache so the next read re-pulls");if(i(),!n)throw Error(`the unsubscribe must close the stream`)}if(await u({d0:[[a(`0xa`,`home.md`)]],other:[[a(`0xb`,`guest.md`)]]}),await O(),Se(`${s}/cdn`),Ce()!==s)throw Error(`setView must trim to the base every route hangs off`);if(Se(null),Ce()!==null)throw Error(`setView(null) must go back to the app default`);console.warn=r,console.info=i,console.log(`shard.js self-check ok — resolveView, gzip sniff, shard digests, domain merge + tombstones + allowlist, streaming, late subscriber, cache poisoning, rowText, vectors, suggestDomains, watchShard`)}var ze=new Set([`text`,`vector`,`norm`,`id`,`owner`,`embed`]),k=(e,t,n=[])=>{let r=c(e,t,n),i=d(e,t);return i?{...r,url:i}:r};function Be(e){let t=new Map,n=0,r=0;for(let i of e){i.vector&&(n++,r||=i.vector.length);for(let e of Object.keys(i))ze.has(e)||i[e]!=null&&i[e]!==``&&t.set(e,(t.get(e)??0)+1)}return{rows:e.length,withVectors:n,vectorDim:r,owners:[...new Set(e.map(e=>e.owner).filter(Boolean))],fields:[...t].sort((e,t)=>t[1]-e[1]).map(([t,n])=>({name:t,on:n,pct:Math.round(100*n/(e.length||1))}))}}var Ve=.15,He=new WeakMap,Ue=(e,t)=>{let n=He.get(e);return n===void 0&&(n=[s(e,t),i(e,t)??``,...(t.tags??[]).flatMap(t=>a(e[t]??``)),e.text??``].join(` `),He.set(e,n)),n};function A(e,t,n,{qv:r=null,limit:i=10,fields:a,where:o,lexBoost:s=Ve}={}){let c=t.trim().toLowerCase();if(!c)return[];let l=r?w(r):1;o&&(e=e.filter(e=>We(e,o)));let u=e.map(e=>r&&e.vector?{r:e,score:C(e,r,l)+(s?s*T({text:Ue(e,n)},c):0),mode:`semantic`}:{r:e,score:T({text:Ue(e,n)},c),mode:`lexical`}),d=u.filter(e=>e.mode===`semantic`).map(e=>e.score),f=Math.min(se(d),d.length?Math.max(...d):1/0);return u.filter(e=>e.mode===`semantic`?e.score>=f:e.score>0).sort((e,t)=>t.score-e.score).slice(0,i).map(({r:e,score:t,mode:r})=>({...k(e,n,a),score:Number(t.toFixed(4)),mode:r}))}var We=(e,t)=>!t||Object.entries(t).every(([t,n])=>a(e[t]??``).some(e=>e.toLowerCase()===String(n).toLowerCase()));function Ge(e,t,{limit:n=20,where:r}={}){let i=new Map,o=0;for(let n of e){if(!We(n,r))continue;let e=n[t];if(e==null||e===``){o++;continue}for(let t of a(e))i.set(t,(i.get(t)??0)+1)}let s=[...i].sort((e,t)=>t[1]-e[1]);return{field:t,distinct:i.size,missing:o,top:s.slice(0,n).map(([e,t])=>({value:e,count:t}))}}function j(e,t,{type:n,where:r,sort:i,desc:a=!0,limit:s=20,offset:c=0,fields:l}={}){let d=e.filter(e=>(!n||o(e)?.toLowerCase()===String(n).toLowerCase())&&We(e,r)),f=d.length;if(i){let e=e=>e==null||e===``?null:Number(e);d=[...d].sort((t,n)=>{let[r,o]=[t[i],n[i]];if((r==null||r===``)!=(o==null||o===``))return r==null||r===``?1:-1;let[s,c]=[e(r),e(o)],l=Number.isFinite(s)&&Number.isFinite(c)?s-c:String(r??``).localeCompare(String(o??``));return a?-l:l})}return{total:f,offset:c,returned:Math.min(s,Math.max(0,f-c)),collections:n?void 0:u(t,e).map(e=>({type:e.type,plural:e.plural,count:e.count})),rows:d.slice(c,c+s).map(e=>k(e,t,l))}}function Ke(e,t,n,{limit:r=10,fields:i}={}){let a=e.find(e=>e.id===t);if(!a?.vector)return{seed:a?k(a,n,i):null,near:[]};let o=a.norm;return{seed:k(a,n,i),near:e.filter(e=>e.id!==t&&e.vector).map(e=>({r:e,score:C(e,a.vector,o)})).sort((e,t)=>t.score-e.score).slice(0,r).map(({r:e,score:t})=>({...k(e,n,i),score:Number(t.toFixed(4))}))}}function qe(e,t,{id:r,where:a,min:o=2,limit:c=20,offset:l=0,fields:u=[]}={}){let f=t.thread;if(!f)return{total:0,offset:l,threads:[],error:`this corpus declares no thread relation`};let p=e=>String(n(e,t.temporal)??``).slice(0,10),m=new Map;for(let n of e){let e=n[f]==null?``:String(n[f]);if(!e||r!=null&&e!==String(r)||!We(n,a))continue;let o=`${p(n)}|${i(n,t)??``}`,s=m.get(e)??m.set(e,new Map).get(e),c=s.get(o);(!c||String(n.text??``).length>String(c.text??``).length)&&s.set(o,n)}let h=new Map;for(let t of e)if(t[f]==null)for(let e of Object.values(t))typeof e==`string`&&m.has(e)&&!h.has(e)&&h.set(e,t);let g=[...m].map(([e,n])=>{let r=[...n.values()].sort((e,t)=>p(e).localeCompare(p(t))),i=h.get(e),a=r.at(-1);return{id:e,title:s(i??a,t),url:d(i??a,t),first:p(r[0]),date:p(a),head:i?k(i,t,u):null,steps:r.map(e=>({...k(e,t,u),date:p(e)}))}}).filter(e=>e.steps.length>=o||r!=null).sort((e,t)=>t.date.localeCompare(e.date)||e.id.localeCompare(t.id));return{total:g.length,offset:l,threads:g.slice(l,l+c)}}function Je(e,t,n){if(!t)return null;let r=e.find(e=>e.id===t)??(n?.identity?e.find(e=>String(e[n.identity])===String(t)):null)??e.find(e=>e.path===t||e.name===t||s(e,n)===t);if(!r)return null;let{vector:i,norm:a,...o}=r;return{...o,hasVector:!!i,vectorDim:i?.length??0}}if(typeof process<`u`&&import.meta.url===`file://${process.argv[1]}`&&process.argv[1].endsWith(`/tools.js`)){let{rolesFrom:e}=await t(async()=>{let{rolesFrom:e}=await import(`./roles-32M1l9jI.js`);return{rolesFrom:e}},__vite__mapDeps([4,5]),import.meta.url),n=(e,t,n)=>({id:e,owner:`0xa`,...t,text:[t.name,t.desc].filter(Boolean).join(` `),vector:n??null,norm:n?w(n):1}),r=[n(`1`,{name:`Duet S01E02`,series:`Duet`,year:`1987`,subject:`['Sitcom', 'Comedy']`,desc:`a forgotten fox sitcom`},[1,0,0]),n(`2`,{name:`Duet S01E03`,series:`Duet`,year:`1987`,subject:`['Sitcom']`,desc:`more of the same sitcom`},[.99,.1,0]),n(`3`,{name:`Atomic Cafe`,series:``,year:`1982`,subject:`['Documentary', 'Cold War']`,desc:`nuclear test footage`},[0,1,0]),n(`4`,{name:`untitled reel`,year:`1954`})],i=e([],r);if(i.title[0]!==`name`)throw Error(`fixture roles wrong: ${i.title}`);let o=Be(r);if(o.rows!==4||o.withVectors!==3||o.vectorDim!==3)throw Error(`describe lost the corpus shape`);let s=o.fields.find(e=>e.name===`series`);if(s.on!==2||s.pct!==50)throw Error(`empty strings must not count as coverage: ${JSON.stringify(s)}`);if(o.fields.some(e=>ze.has(e.name)))throw Error(`plumbing fields must not be offered as content`);if(o.owners.join()!==`0xa`)throw Error(`owners lost`);let c=A(r,`sitcom`,i,{qv:[1,0,0],limit:10});if(c[0].id!==`1`||c[0].title!==`Duet S01E02`)throw Error(`semantic: wrong top hit ${c[0]?.id}`);if(c[0].mode!==`semantic`)throw Error(`mode must be reported, not inferred`);if(c.some(e=>e.id===`3`))throw Error(`the z-floor must drop the unrelated row: ${JSON.stringify(c)}`);if(!A([r[0],r[1],r[2]],`sitcom`,i,{qv:[1,0,0]}).length)throw Error(`an exact match must never be filtered out by its own floor`);if(c=A(r,`nuclear`,i,{limit:10}),c[0]?.id!==`3`||c[0].mode!==`lexical`)throw Error(`must degrade to lexical, and say so`);if(A(r,` `,i).length)throw Error(`an empty query must return nothing, not everything`);if(A(r,`untitled reel`,i)[0]?.id!==`4`)throw Error(`a vectorless row must still be searchable lexically`);let l=Ge(r,`subject`);if(l.top[0].value!==`Sitcom`||l.top[0].count!==2)throw Error(`list-ish values must split: ${JSON.stringify(l.top)}`);if(l.missing!==1)throw Error(`a field absent from a row must be counted missing, not skipped silently`);if(Ge(r,`year`).distinct!==3)throw Error(`plain values must not be split`);if(a(`Sitcom,Comedy,Fox`).length!==3)throw Error(`a machine-joined list must split`);if(a(`Wilson, Michael G.`).length!==1)throw Error(`a comma followed by a space is prose, not a list`);if(a(`1987`).length!==1)throw Error(`a plain value must stay whole`);let u=Ge(r,`year`,{where:{subject:`Sitcom`}});if(u.distinct!==1||u.top[0].value!==`1987`)throw Error(`where must narrow the facet: ${JSON.stringify(u.top)}`);if(Ge(r,`year`,{where:{subject:`sitcom`}}).distinct!==1)throw Error(`where must match case-insensitively`);if(Ge(r,`year`,{where:{subject:`Sit`}}).distinct!==0)throw Error(`where must match whole values, not substrings`);let d=j(r,i,{limit:2});if(d.total!==4||d.rows.length!==2)throw Error(`browse must page: ${JSON.stringify(d)}`);if(d.collections.length!==0)throw Error(`a typeless corpus must report zero collections, not invent one`);if(j(r,i,{offset:3}).rows.length!==1)throw Error(`offset must page to the tail`);if(j(r,i,{offset:99}).rows.length!==0)throw Error(`an offset past the end must be empty, not wrapped`);let f=r.map(e=>({...e,entityType:e.id===`3`?`Film`:`Episode`})),p=j(f,i,{limit:1}).collections;if(p.map(e=>`${e.type}:${e.count}`).join()!==`Episode:3,Film:1`)throw Error(`collections must count and sort: ${JSON.stringify(p)}`);if(j(f,i,{type:`film`}).total!==1)throw Error(`type must match case-insensitively`);if(j(f,i,{type:`film`}).collections!==void 0)throw Error(`a typed browse is already inside a collection`);if(j(f,i,{type:`Episode`,where:{year:`1987`}}).total!==2)throw Error(`browse must honour where`);let m=[{id:`a`,rating:`9`},{id:`b`,rating:`10`},{id:`c`}],h=j(m,i,{sort:`rating`}).rows.map(e=>e.id);if(h.join()!==`b,a,c`)throw Error(`numeric sort + missing-last wrong: ${h}`);if(j(m,i,{sort:`rating`,desc:!1}).rows[0].id!==`a`)throw Error(`ascending sort ignored`);let g=Ke(r,`1`,i);if(g.near[0].id!==`2`)throw Error(`nearest neighbour missed the near-identical row`);if(g.near.some(e=>e.id===`1`))throw Error(`a row must not be its own neighbour`);if(g.near.some(e=>e.id===`4`))throw Error(`a vectorless row cannot be a neighbour`);if(Ke(r,`4`,i).near.length)throw Error(`a vectorless seed must return nothing, not throw`);if(Ke(r,`nope`,i).seed!==null)throw Error(`an unknown seed must be null, not undefined`);let _=Je(r,`3`,i);if(_.name!==`Atomic Cafe`||!_.hasVector||_.vectorDim!==3)throw Error(`getRow lost the record`);if(`vector`in _||`norm`in _)throw Error(`the raw vector must never reach a tool result`);if(Je(r,`nope`,i)!==null)throw Error(`an unknown id must be null`);let v=k(n(`x`,{name:`n`,categories:`d`.repeat(500)}),e([{role_map:{title:`name`,tags:[`categories`]}}]));if(v.categories.length>200)throw Error(`brief must truncate a long field`);if(v.title!==`n`)throw Error(`brief must title from the declared field`);{let e=[{id:`a`,name:`cold war thriller`,year:1965,vector:[1,0],norm:1},{id:`b`,name:`cold war comedy`,year:1999,vector:[1,0],norm:1},{id:`c`,name:`beach party`,year:1964,vector:[0,1],norm:1},{id:`d`,name:`surf movie`,year:1966,vector:[0,1],norm:1}],t={title:[`name`],subtitle:[],tags:[],text:[`name`],measures:[],fields:[`year`]};if(!A(e,`cold war`,t,{qv:[1,0],limit:10}).some(e=>e.title===`cold war comedy`))throw Error(`unfiltered search must see every era`);let n=A(e,`cold war`,t,{qv:[1,0],limit:10,where:{year:1965}});if(n.length!==1||n[0].title!==`cold war thriller`)throw Error(`where must narrow a semantic search: ${JSON.stringify(n)}`);if(A(e,`cold war`,t,{qv:[0,1],limit:10,where:{year:1964}})[0]?.title!==`beach party`)throw Error(`the floor must be measured over the filtered rows`)}{let e=[{id:`a`,name:`cold war thriller`,vector:[1,0],norm:1}],t={title:[`name`],subtitle:[],tags:[],text:[`name`],measures:[],fields:[]},n=A(e,`cold war`,t,{qv:[1,0],limit:5});if(!Array.isArray(n))throw Error(`search must return a flat array, not {hits}`);if(n[0].score===void 0||n[0].mode===void 0)throw Error(`search rows carry score+mode`);for(let n of[void 0,null,``])if(Je(e,n,t)!==null)throw Error(`getRow(${JSON.stringify(n)}) must be null`)}{let e={title:[`heading`,`title`],subtitle:[`meeting`],temporal:[`date`],tags:[],measures:[],thread:`part_of`,externalUrl:{}},t=[{id:`1`,heading:`Rezone Oak St`,meeting:`Plan Commission`,date:`2026-01-05`,part_of:`p:7`,text:`on the agenda`},{id:`2`,heading:`Rezone Oak St`,meeting:`Plan Commission`,date:`2026-01-05`,part_of:`p:7`,text:`recommended for approval 5-0`},{id:`3`,heading:`Rezone Oak St`,meeting:`Village Board`,date:`2026-01-20`,part_of:`p:7`,text:`adopted`},{id:`4`,heading:`Pool bid`,meeting:`Village Board`,date:`2026-03-01`,part_of:`p:9`},{id:`5`,heading:`Pool bid`,meeting:`Finance`,date:`2026-02-20`,part_of:`p:9`,place:`A`},{id:`6`,heading:`Once`,meeting:`Village Board`,date:`2026-04-01`,part_of:`p:11`},{id:`m7`,title:`Rezoning of Oak Street`,matter_id:`p:7`}],n=qe(t,e);if(n.threads.map(e=>e.id).join()!==`p:9,p:7`)throw Error(`latest-moving first, one step is not a thread: ${n.threads.map(e=>e.id)}`);let r=n.threads[1];if(r.steps.map(e=>e.id).join()!==`2,3`||r.title!==`Rezoning of Oak Street`||r.head?.id!==`m7`)throw Error(`an agenda and its minutes are one step, the head titles the thread: ${JSON.stringify(r)}`);if(qe(t,e,{id:`p:11`}).threads[0]?.steps.length!==1)throw Error(`asked for by id, any thread comes back`);if(qe(t,e,{where:{place:`A`},min:1}).total!==1)throw Error(`where narrows the steps`);if(!qe(t,{...e,thread:null}).error)throw Error(`no thread role must say so`)}console.log(`tools.js self-check ok — coverage not schema, semantic/lexical modes + z-floor, list-ish facets + where, neighbours, whole-row fetch, previews truncate, threads`)}var Ye=e=>new Float32Array(e),Xe=e=>new Float32Array(e),Ze=(e,t)=>{let n=new Float32Array(e.length);for(let r=0;r<e.length;r++)n[r]=e[r]+t[r];return n},Qe=(e,t)=>{let n=new Float32Array(e.length);for(let r=0;r<e.length;r++)n[r]=e[r]-t[r];return n},M=(e,t)=>{let n=new Float32Array(e.length);for(let r=0;r<e.length;r++)n[r]=e[r]*t;return n},$e=(e,t)=>{let n=0;for(let r=0;r<e.length;r++)n+=e[r]*t[r];return n},et=e=>Math.sqrt($e(e,e)),tt=(e,t)=>et(Qe(e,t)),nt=e=>{if(e.length===0)throw Error(`centroid: empty input`);let t=e[0].length,n=new Float32Array(t);for(let r of e)for(let e=0;e<t;e++)n[e]+=r[e];for(let r=0;r<t;r++)n[r]/=e.length;return n},rt=(e,t)=>{let n=et(t);if(n<1e-10)return Xe(e);let r=M(t,1/n),i=$e(e,r);return i<=0?Xe(e):Qe(e,M(r,i))},N={d:768,alpha:.1,beta:.3,lambda_max:.4,sigma_base:.5,sigma_max:1,rho:.8,epsilon:1e-6,prior_k:1,pass_window:20,gamma_base:.15,gamma_reg:2,temp_base:1,temp_max:3,alpha_facet:.3,facet_decay:.05,pass_facet_pen:.2,beta_group:.7,group_logistic_c:3,rho_scalar:.8,scalar_sigma:.5,tau_facet:1,tau_group:2,tau_scalar:.5,facet_floor:.01,delta_pass:1,theta_B:3,fatigue_threshold:.3},it=(e,t)=>{let n=1-t;return Object.fromEntries(Object.entries(e).map(([e,t])=>[e,Object.fromEntries(Object.entries(t).map(([e,t])=>[e,t*n]))]))},at=(e,t,n)=>{if(!t)return e;let r={...e};for(let[e,i]of Object.entries(t)){if(!i.length)continue;let t={...r[e]??{}};for(let e of i)t[e]=Math.max((t[e]??0)+n,0);r[e]=t}return r},ot=e=>typeof e==`number`&&e>0&&Number.isFinite(e),st=e=>{if(e.length===0)return{passCentroid:null,passRadius:0};let t=nt(e),n=0;for(let r of e){let e=tt(r,t);e>n&&(n=e)}return{passCentroid:t,passRadius:n}},ct=(e={})=>{let{sigma_base:t=N.sigma_base,d:n=N.d}=e;return{mu:Ye(n),v:Ye(n),sigma:t,passes:[],passCentroid:null,passRadius:0,t:0,entropy:.2,facets:{},groups:{},scalarPref:null,neg:{},blacklist:new Set,muted:new Set}},lt=(e,t={})=>{let{lambda_max:n=N.lambda_max}=t,r=et(e.v);if(r<1e-10)return Xe(e.mu);let i=M(e.v,1/r),a=n*Math.tanh(r);return Ze(e.mu,M(i,a))},ut=(e,t,n=null,r={})=>{let{alpha:i=N.alpha,beta:a=N.beta,sigma_base:o=N.sigma_base,rho:s=N.rho,prior_k:c=N.prior_k,alpha_facet:l=N.alpha_facet,facet_decay:u=N.facet_decay,beta_group:d=N.beta_group,rho_scalar:f=N.rho_scalar}=r,p=t.embedding,m=e.mu,h=e.t+1,g=i/(1-(1-i)**h),_=Ze(m,M(Qe(p,m),g));if(n!==null){let e=c/h;_=Ze(_,M(Qe(n,_),Math.min(e,1)))}let v=e.t===0?e.v:Ze(M(e.v,a),M(Qe(p,m),1-a)),ee=s*e.sigma+(1-s)*o,y=at(it(e.facets,u),t.facets,l),te=t.group===void 0?e.groups:{...e.groups,[t.group]:d*(e.groups[t.group]??0)+(1-d)},b=ot(t.scalar)?e.scalarPref===null?Math.log(t.scalar):f*e.scalarPref+(1-f)*Math.log(t.scalar):e.scalarPref;return{...e,mu:_,v,sigma:ee,t:h,entropy:Math.max(e.entropy*.95,.05),facets:y,groups:te,scalarPref:b}},dt=(e,t,n={})=>{let{sigma_max:r=N.sigma_max,gamma_base:i=N.gamma_base,pass_window:a=N.pass_window,pass_facet_pen:o=N.pass_facet_pen,beta_group:s=N.beta_group,delta_pass:c=N.delta_pass,theta_B:l=N.theta_B,epsilon:u=N.epsilon,fatigue_threshold:d=N.fatigue_threshold}=n,f=t.embedding,p=e.mu,m=e.entropy,h=t.group,g=e.neg,_=e.blacklist,v=e.groups,ee=e.muted;if(h!==void 0){let t=e.groups[h]??0;t>d?(ee=new Set(e.muted),ee.add(h)):(g={...e.neg,[h]:(e.neg[h]??0)+c},_=new Set(e.blacklist),g[h]>l&&_.add(h),v={...e.groups,[h]:s*t+(1-s)*-1})}let y=at(e.facets,t.facets,-o),te=rt(e.v,Qe(f,p)),b=Qe(p,f),ne=et(b),re=i*(1+m),ie=e.t>0&&ne>=u?Ze(p,M(b,re/ne)):p,x=[...e.passes,Xe(f)];x.length>a&&x.shift();let{passCentroid:S,passRadius:ae}=st(x);return{...e,mu:ie,v:te,sigma:Math.min(e.sigma*1.1,r),passes:x,passCentroid:S,passRadius:ae,entropy:Math.min(m*1.1+.02,1),facets:y,groups:v,neg:g,blacklist:_,muted:ee}},ft=e=>{if(!e)return null;let t=w(e),n=new Float32Array(e.length);for(let r=0;r<e.length;r++)n[r]=e[r]/t;return n};function P(e=[],t=[],{drift:n=.35}={}){let r=e.filter(e=>e?.vector?.length);if(!r.length)return null;let i={d:r[0].vector.length,alpha:1-2**(-1/8)},a=e=>({embedding:Float32Array.from(e.vector)}),o=ct(i);for(let e of r)o=ut(o,a(e),null,i);r.length<4&&(o={...o,v:new Float32Array(i.d)});let s=t.filter(e=>e?.vector?.length);for(let e of s)o=dt(o,a(e),i);return{mu:ft(o.mu),v:w(o.v)>1e-6&&r.length>=4?ft(o.v):null,q:ft(lt(o,{lambda_max:n})),no:ft(o.passCentroid),drift:n,from:r.map(e=>e.title??e.id).slice(-12),rejected:s.map(e=>e.title??e.id).slice(-12),n:r.length}}var pt=.6,mt=(e,t)=>{if(!e?.vector||!t)return null;let n=gt(t),r=e.norm?e:{vector:e.vector,norm:w(e.vector)},i=C(r,t.q,n.q);return t.no?i-pt*Math.max(0,C(r,t.no,n.no)):i},ht=new WeakMap;function gt(e){let t=ht.get(e);return(!t||t.qv!==e.q||t.nov!==e.no)&&ht.set(e,t={qv:e.q,nov:e.no,q:w(e.q),no:e.no?w(e.no):1}),t}function F(e,t,{limit:n=10,exclude:r=new Set,lambda:i=.7}={}){return t?ce(e.filter(e=>e.vector&&!r.has(e.id)).map(e=>({row:e,s:mt(e,t)})).sort((e,t)=>t.s-e.s),{limit:n,lambda:i}).map(({row:e,s:t})=>({row:e,score:Number(t.toFixed(4))})):[]}var _t={lookahead:[.35,`how far past what you like, toward where your recent likes are heading (the kernel's drift)`],variety:[.3,`how different the picks are from each other`],surprise:[0,`how much chance reorders the list; 0 is strict best-first`],reach:[0,`how far out from the closest matches to start: 0 is nearest, 1 skips the nearest ~400`]},vt=(e,t)=>Math.min(1,Math.max(0,Number.isFinite(Number(e))?Number(e):t));function I(e,t=[],n=[],{limit:r=20,seed:i=1,exclude:a=new Set,...o}={}){let s=Object.fromEntries(Object.entries(_t).map(([e,[t]])=>[e,vt(o[e],t)])),c=P(t,n,{drift:s.lookahead});if(!c)return{taste:null,knobs:{...s,seed:i},picks:[]};let l=new Set([...a,...t.map(e=>e.id),...n.map(e=>e.id)]),u=e.filter(e=>e.vector&&!l.has(e.id)),d=Math.round(s.reach*Math.min(400,u.length/4)),f=d+300,p=[];for(let e of u){let t=mt(e,c);if(p.length===f&&!(t>p[f-1].s))continue;let n=p.length;for(;n>0&&p[n-1].s<t;)n--;p.splice(n,0,{row:e,s:t}),p.length>f&&p.pop()}let m=p.slice(d);if(s.surprise){let e=Number(i)>>>0||1,t=()=>{e=e+1831565813>>>0;let t=Math.imul(e^e>>>15,1|e);return t^=t+Math.imul(t^t>>>7,61|t),((t^t>>>14)>>>0)/4294967296};m=m.map(e=>({...e,s:e.s+.04*s.surprise*-Math.log(-Math.log(t()*.999998+1e-6))})).sort((e,t)=>t.s-e.s)}return{taste:{from:c.from,rejected:c.rejected,heading:!!c.v,n:c.n},knobs:{...s,seed:i},picks:ce(m,{limit:r,lambda:1-.6*s.variety}).map(({row:e,s:t})=>({row:e,score:Number(t.toFixed(4))}))}}var L=e=>e&&{v:1,model:`nomic-256`,drift:e.drift,n:e.n,q:x(Array.from(e.q)),mu:x(Array.from(e.mu)),...e.v?{d:x(Array.from(e.v))}:{},...e.no?{no:x(Array.from(e.no))}:{},from:e.from,rejected:e.rejected};function yt(e){if(!e?.q)return null;let t=S(e.q),n=S(e.mu??e.q);return t?{q:t,mu:n??t,v:e.d?S(e.d):null,no:e.no?S(e.no):null,drift:e.drift??.35,from:e.from??[],rejected:e.rejected??[],n:e.n??0}:null}var bt=`(remembered`;function xt(e,{carried:t=null,likes:n=[],dislikes:r=[]}={}){if(!e)return e;let i=t??{from:[],rejected:[],n:0},a=e=>e.filter(e=>!String(e?.id??``).startsWith(bt)).map(e=>e.title??e.id),o=a(n);return{...e,v:e.v??i.v??null,from:[...i.from??[],...o].slice(-12),rejected:[...i.rejected??[],...a(r)].slice(-12),n:(i.n??0)+o.length}}if(typeof process<`u`&&import.meta.url===`file://${process.argv[1]}`&&process.argv[1].endsWith(`/taste.js`)){{let e=F([{id:`a`,vector:[1,0],norm:1},{id:`b`,vector:[0,1],norm:1}],{q:[1,0]});if(!e.length)throw Error(`recommend must rank rows with vectors`);if(e[0].row===void 0)throw Error(`recommend rows carry .row`);if(typeof e[0].score!=`number`)throw Error(`recommend rows carry .score (not .s)`)}let e=(e,t,n=e)=>({id:e,title:n,vector:Float32Array.from(t),norm:w(t)}),t=[1,0,0],n=[0,1,0],r=[0,0,1],i=(e,t,n)=>e.map((e,r)=>e*(1-n)+t[r]*n),a=P([e(`a`,t),e(`b`,i(t,r,.2)),e(`c`,i(t,r,.1))]);if(!a)throw Error(`three likes must make a taste`);if(a.v!==null)throw Error(`under four picks there is no older half — direction must not be invented`);if(C({vector:a.q,norm:w(a.q)},Float32Array.from(t),1)<.9)throw Error(`q must sit near what was liked`);let o=[e(`x`,n,`a comedy`),e(`y`,i(t,r,.15),`a bleak documentary`),e(`z`,r,`a documentary`)];if(F(o,a)[0].row.title!==`a bleak documentary`)throw Error(`taste must rank an unseen pool`);let s=[e(`1`,t),e(`2`,i(t,n,.2)),e(`3`,i(t,n,.4)),e(`4`,i(t,n,.6))];if(a=P(s),a.v===null)throw Error(`four picks must yield a direction`);let c=C({vector:a.q,norm:w(a.q)},Float32Array.from(n),1),l=C({vector:a.mu,norm:w(a.mu)},Float32Array.from(n),1);if(!(c>l))throw Error(`q must lean further toward where it is heading than mu: ${c} vs ${l}`);let u=P(s,[],{drift:0});if(C({vector:u.q,norm:w(u.q)},u.mu,w(u.mu))<.999)throw Error(`drift 0 must be mu`);let d=[e(`p`,i(t,n,.7))],f=[e(`m`,t,`bleak`),e(`n`,n,`comic`)];if(F(f,P(d))[0].row.title!==`comic`)throw Error(`sanity: the taste leans comic`);if(F(f,P(d,[e(`q`,n)]))[0].row.title!==`bleak`)throw Error(`rejecting comic must flip the ranking to bleak`);let p=e(`a`,t,`the one they liked`);if(F([p,e(`other`,i(t,r,.1),`similar`)],P([p]),{exclude:new Set([`a`])}).some(e=>e.row.id===`a`))throw Error(`must not recommend what the taste was built from`);a=P(s);let m=L(a),h=JSON.stringify(m).length,g=yt(m);if(!g)throw Error(`a taste must survive the round trip`);let _=F(o,a).map(e=>e.row.id).join(),v=F(o,g).map(e=>e.row.id).join();if(_!==v)throw Error(`ranking changed across the wire: ${_} vs ${v}`);if(!m.from.length)throw Error(`a taste must say what it was built from, or it cannot be corrected`);if(!a.v)throw Error(`this fixture is meant to have a heading`);if(!g.v)throw Error(`a heading must survive the wire — q leans on it, so an export without it lies`);if(C({vector:g.v,norm:w(g.v)},a.v,w(a.v))<.98)throw Error(`…and must survive it intact`);if(yt({})!==null||yt(null)!==null)throw Error(`a malformed taste must be null, not a crash`);if(P([])!==null)throw Error(`no likes is no taste, not an empty one`);if(F(o,null).length)throw Error(`no taste recommends nothing`);if(mt({vector:null},a)!==null)throw Error(`a vectorless row scores null, not NaN`);{let e=(e,t)=>{let n=t*Math.PI/180;return{id:e,vector:[Math.cos(n),Math.sin(n)],norm:1}},t=[e(`run1`,0),e(`run2`,.8),e(`run3`,1.6),e(`run4`,2.4),e(`other`,60)],n=P([{id:`seed`,...e(`seed`,30)}]),r=F(t,n,{limit:3,lambda:1}).map(e=>e.row.id);if(r.some(e=>e===`other`))throw Error(`relevance alone must fill up with the run: ${r}`);let i=F(t,n,{limit:3}).map(e=>e.row.id);if(!i.includes(`other`))throw Error(`MMR must spend a slot on something else: ${i}`);if(i[0]!==r[0])throw Error(`the best match must still come first: ${i[0]} vs ${r[0]}`)}{let a=L(P([e(`a`,t,`The Golem`),e(`b`,n,`Nosferatu`),e(`c`,r,`Metropolis`)],[e(`z`,r,`Wrestling`)]));if(a.n!==3||a.from.length!==3)throw Error(`a fresh export carries what it was built from`);let o=yt(a),s=[{id:`(remembered)`,title:`what you liked before · ${o.n} picks`,vector:o.q}],c=o.no?[{id:`(remembered-no)`,title:`what you passed on before`,vector:o.no}]:[];if(L(P(s,c)).n!==1)throw Error(`this is the loss the next assertion is about`);let l=L(xt(P(s,c),{carried:o,likes:s,dislikes:c}));if(l.n!==3)throw Error(`a restored taste must not report itself as one pick: ${l.n}`);if(!l.from.includes(`Nosferatu`))throw Error(`…and must still name what it was built from`);if(l.from.some(e=>e.startsWith(`(remembered`)))throw Error(`the synthetic placeholder is not a title`);if(!l.rejected.includes(`Wrestling`))throw Error(`rejections are provenance too`);let u=[...s,e(`d`,n,`Sunrise`)],d=L(xt(P(u,c),{carried:o,likes:u,dislikes:c}));if(d.n!==4)throw Error(`a new pick must count once: ${d.n}`);if(d.from.filter(e=>e===`Nosferatu`).length!==1)throw Error(`…and must not duplicate the carried ones`);{let r=yt(L(P([e(`p`,t,`1`),e(`q`,i(t,n,.3),`2`),e(`r`,i(t,n,.6),`3`),e(`s`,n,`4`)]))),a=[{id:`(remembered)`,title:`before`,vector:r.q}];if(!xt(P(a),{carried:r,likes:a}).v)throw Error(`a restored session must keep the heading its own q was built with`);if(P(a).v)throw Error(`…which the kernel alone cannot supply from one entry`)}let f=L(xt(P(s,c),{carried:yt(l),likes:s,dislikes:c}));if(f.n!==3||f.from.join()!==l.from.join())throw Error(`a second reload must change nothing`)}{let e=Array.from({length:40},(e,t)=>{let n=t*Math.PI/80;return{id:`g${t}`,title:`g${t}`,vector:[Math.cos(n),Math.sin(n)],norm:1}}),t=[e[0]],n=[e[39]],r=e=>e.picks.map(e=>e.row.id).join(),i=I(e,t,n,{limit:5});if(i.picks[0].row.id!==`g1`)throw Error(`nearest first by default: ${r(i)}`);if(i.picks.some(e=>e.row.id===`g0`||e.row.id===`g39`))throw Error(`discover must not return what was voted on`);if(I(e,t,[],{limit:5,reach:1}).picks[0].row.id===`g1`)throw Error(`reach must skip the nearest`);if(r(I(e,t,[],{limit:5,surprise:1,seed:7}))!==r(I(e,t,[],{limit:5,surprise:1,seed:7})))throw Error(`the same seed must give the same list`);if(I(e,[],[],{}).picks.length)throw Error(`no likes, no picks`)}console.log(`taste.js self-check ok — discover's knobs, mu/v/q, drift leans forward, rejection steers, portable in ${h}B, ranks a pool it never saw, MMR keeps a run of near-duplicates from filling the list, provenance and heading survive the restore collapse and do not drift on reload`)}var R=e=>({content:[{type:`text`,text:JSON.stringify(e)}]}),z=(e,t=[])=>({type:`object`,properties:e,required:t});function St(e){let t=document.modelContext;t?.registerTool&&(t.registerTool({name:`describe`,description:`What this app holds: its fields and how often each is present. Call it first.`,inputSchema:z({}),execute:async()=>R(Be(e.rows))}),t.registerTool({name:`search`,description:"Search by meaning: a question in plain words. Optional equality filters in `where`.",inputSchema:z({query:{type:`string`},limit:{type:`number`},where:{type:`object`}},[`query`]),execute:async({query:t,limit:n=10,where:r})=>R(A(e.rows,t,e.roles,{qv:await e.queryVector(t),limit:n,where:r}))}),t.registerTool({name:`get`,description:`One record in full, by the id search returns.`,inputSchema:z({id:{type:`string`}},[`id`]),execute:async({id:t})=>R(Je(e.rows,t,e.roles)??{error:`no record ${t}`})}),t.registerTool({name:`count`,description:`Count the distinct values of a field: the shape of the data before reading it.`,inputSchema:z({field:{type:`string`},where:{type:`object`},limit:{type:`number`}},[`field`]),execute:async({field:t,where:n,limit:r=20})=>R(Ge(e.rows,t,{where:n,limit:r}))}),t.registerTool({name:`threads`,description:"One thing followed across records (a matter from committee to council): each thread's steps in date order, the latest-moving first, with the head record when there is one. `id` for one thread (a step's thread field); `where` narrows the steps.",inputSchema:z({id:{type:`string`},where:{type:`object`},limit:{type:`number`},offset:{type:`number`}}),execute:async({id:t,where:n,limit:r=20,offset:i=0})=>R(qe(e.rows,e.roles,{id:t,where:n,limit:r,offset:i}))}),t.registerTool({name:`session`,description:`What the person on this page saved and searched this session, and the taste (a vector over this app's records) their choices make.`,inputSchema:z({}),execute:async()=>R(e.session?.()??{saved:[],searches:[],taste:null})}),t.registerTool({name:`rate`,description:`Record that the person liked or disliked a record (or clear it), by the id search returns. Builds the taste discover ranks with.`,inputSchema:z({id:{type:`string`},verdict:{type:`string`,enum:[`like`,`dislike`,`clear`]}},[`id`,`verdict`]),execute:async({id:t,verdict:n})=>R(e.rate?.(t,n)??{error:`this page keeps no taste`})}),t.registerTool({name:`discover`,description:`Records for a taste: a kernel over what was liked (newest last) and disliked, not a text search. Pass likes and dislikes (ids from search), or omit them to use the taste on this page. Knobs are 0–1.`,inputSchema:z({likes:{type:`array`,items:{type:`string`}},dislikes:{type:`array`,items:{type:`string`}},...Object.fromEntries(Object.entries(_t).map(([e,[t,n]])=>[e,{type:`number`,minimum:0,maximum:1,default:t,description:n}])),seed:{type:`integer`,description:`Same seed, same shuffle`},limit:{type:`integer`,minimum:1,maximum:50,default:10}}),execute:async({likes:t,dislikes:n,limit:r=10,...i})=>{let a=t=>t.map(t=>e.rows.find(n=>n.id===Je(e.rows,t,e.roles)?.id)).filter(e=>e?.vector),o=t?{likes:a(t),dislikes:a(n??[])}:e.votes?.()??{likes:[],dislikes:[]};if(!o.likes.length)return R({error:`like at least one record first (ids from search, or rate)`});let s=I(e.rows,o.likes,o.dislikes,{...i,limit:Math.min(50,r)});return R({...s,picks:s.picks.map(({row:t,score:n})=>({...Je(e.rows,t.id,e.roles),score:n}))})}}))}function Ct(e,t){let n=t=>new Set(e.slice(0,5e3).flatMap(e=>a(e[t]??``))).size;return t.facets?.[0]??t.tags.find(e=>{let t=n(e);return t>=2&&t<=12})??null}function wt(e,t,{facet:r=null,only:a=null,today:o=new Date().toISOString().slice(0,10)}={}){let c=new Map;for(let o of e){let e=String(n(o,t.temporal)??``).slice(0,10);if(!e)continue;let l=r?String(o[r]??``):``;if(a&&l!==a)continue;let u=i(o,t)??``,d=`${l}|${u}|${e}`,f=c.get(d);f||c.set(d,f={key:d,date:e,place:l,label:u,byTitle:new Map});let p=s(o,t).toLowerCase(),m=f.byTitle.get(p);(!m||B(m,t).length<B(o,t).length)&&f.byTitle.set(p,o)}let l=[...c.values()].map(({byTitle:e,...t})=>({...t,items:[...e.values()]}));return{upcoming:l.filter(e=>e.date>o).sort((e,t)=>e.date.localeCompare(t.date)||e.place.localeCompare(t.place)),past:l.filter(e=>e.date<=o).sort((e,t)=>t.date.localeCompare(e.date)||e.place.localeCompare(t.place))}}function B(e,t){let n=s(e,t),r=String(e.text??``),i=r.startsWith(n.replace(/\.$/,``))?r.slice(n.replace(/\.$/,``).length).replace(/^[.\s]+/,``):r;return i===n?``:i.trim()}if(typeof process<`u`&&process.argv[1]?.endsWith(`/feed.js`)){let{rolesFrom:e}=await t(async()=>{let{rolesFrom:e}=await import(`./roles-32M1l9jI.js`);return{rolesFrom:e}},__vite__mapDeps([4,5]),import.meta.url),n=e([{role_map:{title:`heading`,subtitle:`meeting`,temporal:`date`,tags:[`city`,`body`],text:[`text`]},presentation:{facets:[`city`]}}]),r=(e,t,n,r=n)=>({city:e,date:t,heading:n,meeting:`Board · ${t}`,body:`Board`,text:r}),i=[r(`A`,`2026-01-01`,`Budget`),r(`A`,`2026-01-01`,`Budget`,`Budget. Approved 5-0.`),r(`A`,`2026-01-01`,`Roads`),r(`B`,`2026-02-01`,`Parks`),r(`A`,`2026-03-01`,`Zoning`),{heading:`undated`}],a=(e,t,n)=>{if(JSON.stringify(e)!==JSON.stringify(t))throw Error(`${n}: ${JSON.stringify(e)}`)};a(Ct(i,n),`city`,`the declared facet wins`);let{upcoming:o,past:s}=wt(i,n,{facet:`city`,today:`2026-02-15`});a(o.map(e=>e.key),[`A|Board · 2026-03-01|2026-03-01`],`a future date is coming up`),a(s.map(e=>`${e.place}${e.date}`),[`B2026-02-01`,`A2026-01-01`],`past, latest first`),a(s[1].items.map(e=>B(e,n)),[`Approved 5-0.`,``],`one item per title, the one that says what happened`),a(wt(i,n,{facet:`city`,only:`B`,today:`2026-02-15`}).past.length,1,`narrowed to one place`),console.log(`feed.js self-check ok — declared facet, upcoming vs past, one item per title keeping the decision, narrowed by place`)}var Tt=(e,t,n)=>{try{return JSON.parse(e?.getItem(t)??`null`)??n}catch{return n}},V=(e,t,n)=>{try{e?.setItem(t,JSON.stringify(n))}catch{}};function Et({local:e,session:t,app:n=`app`}={}){let r={saved:`${n}:saved`,passed:`${n}:passed`,history:`${n}:history`};return{saved:()=>Tt(e,r.saved,[]),passed:()=>Tt(e,r.passed,[]),history:()=>Tt(t,r.history,[]),isSaved(e){return this.saved().some(t=>t.key===e)},toggleSave(t){let n=this.saved(),i=n.findIndex(e=>e.key===t.key);return i>=0?n.splice(i,1):n.push({...t,savedAt:new Date().toISOString()}),V(e,r.saved,n),V(e,r.passed,this.passed().filter(e=>e.key!==t.key)),i<0},pass(t){V(e,r.passed,[...this.passed().filter(e=>e.key!==t.key),t].slice(-50)),V(e,r.saved,this.saved().filter(e=>e.key!==t.key))},unvote(t){V(e,r.saved,this.saved().filter(e=>e.key!==t)),V(e,r.passed,this.passed().filter(e=>e.key!==t))},remember(e){let n=e.trim();n&&V(t,r.history,[{q:n,at:new Date().toISOString()},...this.history().filter(e=>e.q!==n)].slice(0,50))},clear(n){V(n===`history`?t:e,r[n],[])},votes(){let e=e=>({id:e.key,title:e.title,vector:e.v?S(e.v):null});return{likes:this.saved().map(e),dislikes:this.passed().map(e)}},taste(){let{likes:e,dislikes:t}=this.votes();return P(e,t)},bundle({name:e=n,url:t=``}={}){return{app:e,url:t,exported:new Date().toISOString(),saved:this.saved().map(({v:e,...t})=>t),searches:this.history(),taste:L(this.taste())}}}}var Dt=(e,{key:t,title:n,subtitle:r,place:i,date:a,link:o,detail:s})=>({key:t,title:n,subtitle:r,place:i,date:a,url:o??null,detail:s??``,...e.vector?{v:x(Array.from(e.vector))}:{}}),Ot=e=>`"${String(e??``).replace(/"/g,`""`)}"`,kt=e=>[`title,place,subtitle,date,url,detail`,...e.map(e=>[e.title,e.place,e.subtitle,e.date,e.url,e.detail].map(Ot).join(`,`))].join(`
7
+ `),At=(e,t=`Saved`)=>[`# ${t}`,``,...e.map(e=>`- **${e.url?`[${e.title}](${e.url})`:e.title}** — ${[e.place,e.subtitle].filter(Boolean).join(` · `)}${e.detail?`\n ${e.detail}`:``}`)].join(`
8
+ `);if(typeof process<`u`&&process.argv[1]?.endsWith(`/store.js`)){let e=()=>{let e=new Map;return{getItem:t=>e.get(t)??null,setItem:(t,n)=>e.set(t,n)}},t=Et({local:e(),session:e(),app:`t`}),n=e=>({vector:Array.from({length:8},(t,n)=>n===e?1:.1)}),r=Dt(n(0),{key:`a`,title:`Budget`,place:`X`,date:`2026-01-01`}),i=Dt(n(1),{key:`b`,title:`Roads, "east"`,place:`Y`}),a=(e,t,n)=>{if(JSON.stringify(e)!==JSON.stringify(t))throw Error(`${n}: ${JSON.stringify(e)}`)};if(a(t.taste(),null,`no taste before a save`),t.toggleSave(r),t.toggleSave(i),a(t.saved().map(e=>e.key),[`a`,`b`],`saved in order`),!t.taste()?.q)throw Error(`saves make a taste`);t.pass(i),a([t.saved().map(e=>e.key),t.passed().map(e=>e.key)],[[`a`],[`b`]],`passing on an item unsaves it`),a(t.taste().rejected,[`Roads, "east"`],`and it steers the taste away`),t.remember(`snow`),t.remember(`budget`),t.remember(`snow`),a(t.history().map(e=>e.q),[`snow`,`budget`],`history: newest first, no repeats`);let o=t.bundle({name:`T`});if(o.saved[0].v||!o.taste?.q||o.searches.length!==2)throw Error(`the bundle carries choices and taste, not raw vectors`);if(!kt([i]).includes(`"Roads, ""east"""`))throw Error(`CSV quoting`);if(Et({local:{getItem(){throw Error(`blocked`)},setItem(){throw Error(`blocked`)}}}).saved().length!==0)throw Error(`blocked storage reads as empty`);console.log(`store.js self-check ok — saves and passes steer a taste, history dedupes, the bundle carries no raw vectors, CSV quotes, blocked storage is empty not fatal`)}var jt=`http://www.w3.org/2000/svg`,Mt=(e,t={})=>{let n=document.createElementNS(jt,e);for(let[e,r]of Object.entries(t))n.setAttribute(e,r);return n},Nt=e=>e.toLocaleString(),Pt=new Set([`state`]);function Ft(e,{el:t,show:n,row:r,placeOf:i,ctx:s}){let c=s.roles,l=Object.keys(e.paths),u=null,d=null,f=null,p={q:``,hits:null};function m(){if(u?.n===s.rows.length)return u;let t=s.rows.map(t=>{let n=a(t[e.field]??``).filter(t=>e.paths[t]);return n.length?n:[e.places?.[i(t)]].filter(Boolean)}),n=new Map;for(let e of s.rows){let t=o(e);n.set(t,(n.get(t)??0)+1)}let r=[...n].sort((e,t)=>t[1]-e[1])[0]?.[0],c=new Map(l.map(e=>[e,[]]));s.rows.forEach((e,n)=>{for(let e of t[n])c.get(e)?.push(n)});let d=0,f=null,p=0,m=new Map;s.rows.forEach((e,n)=>{if(!e.vector||o(e)!==r||!t[n].length)return;d||=e.vector.length,f??=new Float64Array(d);let i=1/(e.norm||Math.hypot(...e.vector)||1);for(let r of t[n]){let t=m.get(r)??{v:new Float64Array(d),n:0};for(let n=0;n<d;n++)t.v[n]+=e.vector[n]*i;t.n++,m.set(r,t)}for(let t=0;t<d;t++)f[t]+=e.vector[t]*i;p++});let h=new Map;for(let[e,t]of m){if(t.n<20)continue;let n=t.v.map((e,n)=>e/t.n-f[n]/p),r=Math.hypot(...n)||1;h.set(e,n.map(e=>e/r))}return u={n:s.rows.length,regionsOf:t,by:c,main:r,types:n,centroid:h},u}function h(e){let{centroid:t}=m(),n=t.get(e);return n?[...t].filter(([t])=>t!==e).map(([e,t])=>({region:e,score:t.reduce((e,t,r)=>e+t*n[r],0)})).sort((e,t)=>t.score-e.score).slice(0,5):[]}let g=(e,t=2)=>{let n=c.types?.[e];return(t===1?n?.singular:n?.plural)??e??`Records`},_=e=>c.types?.[e]?.icon??``;function v(e){let{regionsOf:t}=m(),n=new Map,r=e??s.rows.map((e,t)=>t);for(let e of r)if(!(f&&o(s.rows[e])!==f))for(let r of t[e])n.set(r,(n.get(r)??0)+1);return n}async function ee(e){if(!e)return null;if(p.q===e&&p.hits)return p.hits;let t=s.queryVector(e),n=await Promise.race([t,new Promise(e=>setTimeout(()=>e(void 0),400))]);return n===void 0&&t.then(t=>{t&&p.q===e&&!p.meaning&&(p={q:e,hits:y(e,t),meaning:!0},b(e,d))}),p={q:e,hits:y(e,n??null),meaning:n!==void 0},p.hits}function y(e,t){let n=new Map(s.rows.map((e,t)=>[e.id,t]));return A(s.rows,e,c,{qv:t,limit:600}).map(e=>n.get(e.id)).filter(e=>e!=null)}let te=(e,t=d)=>{location.hash=`#/map/${encodeURIComponent(e)}${t?`/${encodeURIComponent(t)}`:``}`};async function b(r=``,i=null){d=i&&e.paths[i]?i:null;let a=await ee(r),c=v(a),u=Math.max(1,...c.values()),y=e=>e?Math.min(5,1+Math.floor((a?e/u:Math.log10(e)/Math.log10(u))*4.999)):0,re=d?new Set(h(d).map(e=>e.region)):new Set,ie=Mt(`svg`,{viewBox:`0 0 ${e.width} ${e.height}`,role:`img`,"aria-label":a?`Map: where “${r}” comes up`:`Map: where records are`,class:`map`});for(let t of l){let n=Mt(`path`,{d:e.paths[t],class:`h${y(c.get(t)??0)}${t===d?` on`:``}${re.has(t)?` near`:``}`,tabindex:0,"aria-label":`${t}: ${Nt(c.get(t)??0)}`});n.append(Mt(`title`)),n.firstChild.textContent=`${t} · ${Nt(c.get(t)??0)} ${a?`matching`:(f?g(f):`records`).toLowerCase()}`;let i=()=>te(r,d===t?null:t);n.addEventListener(`click`,i),n.addEventListener(`keydown`,e=>{(e.key===`Enter`||e.key===` `)&&(e.preventDefault(),i())}),ie.append(n)}let x=t(`input`,{type:`search`,value:r,placeholder:`Search the map: data center, PFAS, solar rezoning…`,"aria-label":`Search the map`});x.addEventListener(`keydown`,e=>{e.key===`Enter`&&te(x.value.trim())});let S=a?new Map:m().types;for(let e of a??[]){let t=o(s.rows[e]);S.set(t,(S.get(t)??0)+1)}let ae=S.size>1||a?t(`div`,{className:`chips`},[[null,a?.length??s.rows.length],...S].map(([e,n])=>t(`button`,{type:`button`,className:e===f?`chip on`:`chip`,onclick:()=>{f=e,b(r,d)}},`${e?`${_(e)} ${g(e,n)}`:`All`} (${Nt(n)})`))):null,oe=l.filter(e=>c.get(e)).length,C=a?`“${r}”: ${Nt([...c.values()].reduce((e,t)=>e+t,0))} matches in ${oe} of ${l.length} regions. Darker is more.${p.meaning?``:` (Matching words; search by meaning is loading.)`}`:`${f?g(f):`Records`} in ${oe} of ${l.length} regions. Select a region, or search.`;n(t(`div`,{className:`mapbar`},x),ae,t(`p`,{className:`hint`},C),t(`div`,{className:`mapwrap`},ie,t(`aside`,{className:`panel`},ne(d,r,a))),t(`p`,{className:`hint`},`Legend: `,[0,1,2,3,4,5].map(e=>t(`span`,{className:`sw h${e}`})),a?` none → most matches`:` none → most records`))}function ne(n,i,l){if(!n)return[t(`h3`,{},`Select a region`),t(`p`,{className:`hint`},`Its records by kind and tag, the ones that match, and the regions most like it.`)];let{by:u}=m(),d=new Set(u.get(n)),p=(l?l.filter(e=>d.has(e)):[...d]).filter(e=>!f||o(s.rows[e])===f).map(e=>s.rows[e]),v=new Map;for(let e of p){let t=o(e);v.set(t,(v.get(t)??0)+1)}let ee=(c.tags??[]).filter(t=>t!==e.field&&!Pt.has(t)).map(e=>{let t=new Map;for(let n of p)for(let r of a(n[e]??``))t.set(r,(t.get(r)??0)+1);return[e,[...t].sort((e,t)=>t[1]-e[1]).slice(0,6)]}).filter(([,e])=>e.length>1),y=e=>String(e[c.temporal?.[0]]??``),b=(l?p:p.slice().sort((e,t)=>y(t).localeCompare(y(e)))).slice(0,25),ne=h(n);return[t(`h3`,{},n,` `,t(`button`,{type:`button`,className:`linkish`,"aria-label":`Close`,onclick:()=>te(i,null)},`✕`)),t(`p`,{className:`hint`},[...v].map(([e,t])=>`${_(e)} ${Nt(t)} ${g(e,t).toLowerCase()}`).join(` · `)||`Nothing here`+(l?` matches.`:` yet.`)),ne.length?t(`div`,{className:`chips`},t(`small`,{title:`Regions whose records are closest in meaning; outlined on the map`},`Reads most like: `),ne.map(({region:e})=>t(`button`,{type:`button`,className:`chip`,onclick:()=>te(i,e)},e))):null,ee.map(([e,n])=>t(`div`,{className:`chips`},t(`small`,{},`${e.replace(/_/g,` `)}: `),n.map(([e,n])=>t(`button`,{type:`button`,className:`chip`,title:`Search the map for “${e}”`,onclick:()=>te(e)},`${e} (${n})`)))),b.length?t(`ul`,{className:`list`},b.map(e=>r(e))):null,p.length>b.length?t(`p`,{className:`hint`},`${Nt(p.length-b.length)} more. `,t(`a`,{href:`#/search/${encodeURIComponent(i||n)}`},`Open in search`)):null]}return{render:b}}var H=e=>document.querySelector(e),U=(e,t={},...n)=>{let r=Object.assign(document.createElement(e),t);return r.append(...n.flat(1/0).filter(e=>e!=null&&e!==!1)),r},It=(e,t=220)=>e.length>t?`${e.slice(0,e.lastIndexOf(` `,t))} …`:e,Lt=e=>e?new Date(`${String(e).slice(0,10)}T12:00:00`).toLocaleDateString(void 0,{weekday:`short`,month:`short`,day:`numeric`,year:`numeric`}):``,W={rows:[],roles:r([]),queryVector:e=>ie(e).catch(()=>null)},Rt,zt=new Promise(e=>{Rt=e});D({onManifests:e=>{W.roles=r(e),Rt()},rowText:e=>l(e,W.roles)});var Bt=await fetch(`./.well-known/agent-card.json`).then(e=>e.json()).catch(()=>({})),G=(Bt.capabilities?.extensions??[]).map(e=>e.params?.paid).find(Boolean)??null,Vt=Bt.name??`Fangorn app`;document.title=Vt,G&&(H(`#walletlink`).hidden=!1),H(`#name`).textContent=Vt,fetch(`./nav.json`).then(e=>e.json()).then(e=>H(`nav`).append(...e.map(e=>U(`a`,{href:e.href},e.label)))).catch(()=>{});var Ht=await fetch(`./map.json`).then(e=>(e.headers.get(`content-type`)??``).includes(`json`)?e.json():null).catch(()=>null);Ht&&H(`nav`).prepend(U(`a`,{href:`#/map`},`Map`));var Ut=H(`#q input`);Ut.disabled=!1;var Wt=Vt.toLowerCase().replace(/\W+/g,`-`),K=Et({local:globalThis.localStorage,session:globalThis.sessionStorage,app:Wt});W.session=()=>K.bundle({name:Vt,url:location.origin});var Gt=new URL(`view`,location.href).href,Kt=await fetch(`${Gt}/cdn/catalog`).then(e=>e.json()).then(e=>e.domains.reduce((e,t)=>e+(t.count??0),0)).catch(()=>0),qt=!1,Jt=0,Yt=O(Gt,e=>{W.rows=e,en(),Date.now()-Jt>1500&&/^(#\/?(signals)?)?$/.test(location.hash)&&(Jt=Date.now(),qn())});await zt;var q=W.roles;q.thread&&H(`nav`).prepend(U(`a`,{href:`#/signals`},`Signals`));var Xt=e=>String((q.identity&&e[q.identity])??e.id),Zt=new Map,J=new Map,Y=null,Qt=[],$t=null;function en(){Zt=new Map(W.rows.map(e=>[Xt(e),e])),J=new Map(W.rows.map(e=>[e.id,e])),$t=null,Y??=Ct(W.rows,q),Qt=Y?[...W.rows.reduce((e,t)=>{let n=X(t);return n&&e.set(n,(e.get(n)??0)+1),e},new Map)].sort((e,t)=>e[0].localeCompare(t[0])):[];let e=W.rows.length.toLocaleString();Ut.placeholder=qt&&!W.rows.length?`No records yet`:qt?`Search ${e} records${Qt.length>1?` in ${Qt.length} places`:``}…`:`Loading ${e}${Kt?` of ${Kt.toLocaleString()}`:``} records… (search what is here)`}var X=e=>Y?a(e[Y]??``)[0]??``:``,tn=e=>String(e[q.temporal?.[0]]??``).slice(0,10),nn=e=>d(e,q),rn=e=>Dt(e,{key:Xt(e),title:s(e,q),subtitle:i(e,q),place:X(e),date:tn(e),link:nn(e),detail:B(e,q)}),an=e=>/[a-z]/.test(e)||!/[A-Z]{5}/.test(e)?e:e.toLowerCase().replace(/\b[a-z]{1,3}\b/g,(e,t)=>/^(and|or|of|the|to|for|in|on|at|by|a|an|as|is|be|no|from)$/.test(e)?e:e.toUpperCase()).replace(/^\W*\w/,e=>e.toUpperCase()).replace(/([.:;!?]\s+|\u2013\s+|-\s+)(\w)/g,(e,t,n)=>t+n.toUpperCase()),on=e=>an(s(e,q)),sn=e=>`#/item/${encodeURIComponent(Xt(e))}`,cn=(e,t)=>{if(!t.length)return[e];let n=RegExp(`(${t.map(e=>e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)).join(`|`)})`,`ig`);return String(e).split(n).map((e,t)=>t%2?U(`mark`,{},e):e)},ln=e=>K.isSaved(e)?`like`:K.passed().some(t=>t.key===e)?`dislike`:null;W.rate=(e,t)=>{if(![`like`,`dislike`,`clear`].includes(t))return{error:`verdict is like, dislike or clear`};let n=J.get(e)??Zt.get(String(e))??J.get(Je(W.rows,e,q)?.id);if(!n)return{error:`no record ${e}`};let r=Xt(n);return K.unvote(r),t===`like`&&K.toggleSave(rn(n)),t===`dislike`&&K.pass(rn(n)),dn(),Kn(),/^#\/for-you/.test(location.hash)&&Hn(),{likes:K.saved().map(e=>e.title),dislikes:K.passed().map(e=>e.title)}},W.votes=()=>{let e=e=>{let t=Zt.get(e.id);return t?{id:t.id,title:e.title,vector:t.vector}:e},{likes:t,dislikes:n}=K.votes();return{likes:t.map(e).filter(e=>e.vector),dislikes:n.map(e).filter(e=>e.vector)}};var un=e=>{let t=U(`button`,{type:`button`,className:`vote star`,title:`Save`,ariaLabel:`Save: ${s(e,q)}`,onclick:t=>{t.preventDefault(),W.rate(e.id,ln(Xt(e))===`like`?`clear`:`like`)}});return Object.assign(t.dataset,{key:Xt(e),vote:`like`}),U(`span`,{className:`votes`},t)};function dn(){for(let e of document.querySelectorAll(`button.vote[data-vote]`)){let t=ln(e.dataset.key)===e.dataset.vote;e.setAttribute(`aria-pressed`,String(t)),e.textContent=t?`★`:`☆`,e.title=t?`Saved`:`Save`}}var fn=(e,{terms:t=[],also:n=0,meta:r=!0}={})=>{let a=B(e,q);return U(`li`,{className:`row`},U(`div`,{className:`main`},U(`a`,{href:sn(e),className:`title`},cn(on(e),t)),r?U(`small`,{},[X(e),i(e,q)].filter(Boolean).join(` · `)):null,a?U(`p`,{className:`detail`},cn(It(a),t)):null,n?U(`small`,{className:`also`},`and ${n} more record${n>1?`s`:``} of the same item`):null),un(e))},pn=()=>{if(!$t){let e=q.thread?qe(W.rows,q,{min:1,limit:1/0}).threads:[];$t={list:e.filter(e=>e.steps.length>=2),byId:new Map(e.map(e=>[e.id,e])),byHead:new Map(e.filter(e=>e.head).map(e=>[e.head.id,e]))}}return $t},mn=e=>q.thread?e[q.thread]==null?pn().byHead.get(e.id):pn().byId.get(String(e[q.thread])):null,hn=e=>e?.outcome??null,gn=e=>(i(e,q)??``).replace(/\s*·\s*\d{4}-\d\d-\d\d$/,``),_n=e=>e?new Date(`${e}T12:00:00`).toLocaleDateString(void 0,{month:`short`,day:`numeric`,year:`numeric`}):``,vn=e=>U(`ol`,{className:`steps`},e.steps.map(e=>J.get(e.id)).filter(Boolean).map(e=>U(`li`,{},U(`time`,{dateTime:tn(e)},_n(tn(e))),U(`span`,{},U(`a`,{href:sn(e)},gn(e)||on(e)),hn(e)?` · ${hn(e)}`:``)))),Z=null,yn=()=>Qt.length>1?U(`label`,{className:`picker`},U(`span`,{},`${Y?Y[0].toUpperCase()+Y.slice(1):`Place`}`),U(`select`,{id:`place`,onchange:e=>{Z=e.target.value||null,qn()}},[[null,W.rows.length],...Qt].map(([e,t])=>U(`option`,{value:e??``,selected:e===Z},`${e??`All`} (${t.toLocaleString()})`)))):null,bn=e=>{let t=e.search(/[:.;]\s/);return t<0||t>160?U(`p`,{className:`about`},e):U(`details`,{className:`about`},U(`summary`,{},e.slice(0,t+1).replace(/:$/,`.`)),U(`p`,{},e.slice(t+2)))},xn=e=>!Z||X(e)===Z,Sn=H(`#view`),Q=(...e)=>{Sn.replaceChildren(...e.flat().filter(Boolean)),dn(),window.scrollTo(0,0)};function Cn(e){let t=U(`ul`,{},e.items.slice(0,5).map(e=>fn(e,{meta:!1}))),n=e.items.length>5?U(`button`,{type:`button`,className:`more`,onclick:n=>{t.append(...e.items.slice(5).map(e=>fn(e,{meta:!1}))),n.target.remove()}},`${e.items.length-5} more`):null;return U(`article`,{},U(`header`,{},U(`time`,{dateTime:e.date},Lt(e.date)),U(`h3`,{},[e.place,e.label.replace(/\s*·\s*\d{4}-\d\d-\d\d$/,``)].filter(Boolean).join(` — `))),t,n)}var wn=12;function Tn(e){let t=J.get(e.steps.at(-1).id),n=e.head&&J.get(e.head.id),r=G?e.steps.map(e=>J.get(e.id)).reverse().find(e=>e?.paid_sha256):null,a=B(t,q);return U(`article`,{className:`signal`},U(`div`,{className:`meta`},U(`span`,{},X(t)),n&&i(n,q)?U(`span`,{},i(n,q)):null,hn(t)?U(`span`,{className:`stage`},hn(t)):null,U(`time`,{dateTime:e.date},_n(e.date))),U(`h3`,{},U(`a`,{href:sn(n??t),className:`title`},an(e.title))),a?U(`p`,{className:`detail`},It(a)):null,vn(e),r?Rn(r):null,U(`div`,{className:`acts`},un(n??t),e.url?U(`a`,{href:e.url,target:`_blank`,rel:`noopener`,className:`act`},`Official record ↗`):null))}var En=null,Dn=30;function On(){let e=e=>J.get(e.steps.at(-1).id),{list:t}=pn(),n=[...new Set(t.map(t=>hn(e(t))).filter(Boolean))].sort(),r=t.filter(t=>xn(e(t))&&(!En||hn(e(t))===En));Q(U(`h2`,{className:`page`},`Signals`),U(`p`,{className:`hint`},`Decisions followed across meetings, from committee to council, the latest to move first. Each links to its official record.`),U(`div`,{className:`chips`},yn(),n.length?U(`label`,{className:`picker`},U(`span`,{},`Stage`),U(`select`,{onchange:e=>{En=e.target.value||null,Dn=30,On()}},[null,...n].map(e=>U(`option`,{value:e??``,selected:e===En},e??`Any stage`)))):null),U(`p`,{className:`hint`},`${r.length.toLocaleString()} signal${r.length===1?``:`s`}`),r.length?r.slice(0,Dn).map(Tn):U(`p`,{className:`empty`},qt?`Nothing followed across meetings here yet.`:`Loading the records…`),r.length>Dn?U(`button`,{type:`button`,className:`more`,onclick:()=>{Dn+=30,On()}},`Show more`):null)}function kn(){let e=pn().byHead,{upcoming:t,past:n}=wt(e.size?W.rows.filter(t=>!e.has(t.id)):W.rows,q,{facet:Y,only:Z}),{likes:r,dislikes:i}=W.votes(),a=I(W.rows.filter(xn),r,i,{...$,limit:6});Q(Bt.description?bn(Bt.description):null,yn(),a.picks.length?U(`section`,{},U(`h2`,{},`For you`),U(`p`,{className:`hint`},`From the ${a.taste.n} item${a.taste.n>1?`s`:``} you liked. `,U(`a`,{href:`#/for-you`},`Tune`)),U(`ul`,{className:`list`},a.picks.map(({row:e})=>fn(e)))):null,t.length?U(`section`,{},U(`h2`,{},`Coming up`),t.slice(0,8).map(Cn)):null,n.length?U(`section`,{},U(`h2`,{},`Recently`),n.slice(0,wn).map(Cn),n.length>wn?U(`button`,{type:`button`,className:`more`,onclick:()=>{wn+=12,kn()}},`Show more`):null):null,!t.length&&!n.length?U(`p`,{className:`empty`},qt?W.rows.length?`Nothing dated here yet. Search above.`:`This app is registered and being built. Its records will appear here once they are published.`:`Loading the records…`):null)}var An=`relevance`,jn=0,Mn=null;async function Nn(e){document.activeElement!==H(`#q input`)&&(H(`#q input`).value=e);let t=++jn,n=W.queryVector(e),r=await Promise.race([n,new Promise(e=>setTimeout(()=>e(void 0),400))]);t===jn&&(r===void 0&&n.then(n=>{n&&t===jn&&Pn(e,n)}),Pn(e,r??null,r===void 0))}function Pn(e,t,n=!1){let r=A(W.rows,e,q,{qv:t,limit:80,...Z&&Y?{where:{[Y]:Z}}:{}}),i=new Map;for(let e of r){let t=J.get(e.id);if(!t)continue;let n=`${X(t)}|${s(t,q).toLowerCase()}`,r=i.get(n);r?(r.also++,B(t,q).length>B(r.r,q).length&&(r.r=t)):i.set(n,{r:t,also:0})}let a=[...i.values()];An===`newest`&&(a=a.sort((e,t)=>tn(t.r).localeCompare(tn(e.r))));let o=e.toLowerCase().split(/\W+/).filter(e=>e.length>2),c=U(`div`,{className:`sort`},`Sort: `,[`relevance`,`newest`].map(r=>U(`button`,{type:`button`,className:r===An?`chip on`:`chip`,onclick:()=>{An=r,Pn(e,t,n)}},r)));Q(yn(),U(`div`,{className:`resultsbar`},U(`span`,{},`${a.length} result${a.length===1?``:`s`} for “${e}”${Z?` in ${Z}`:``}`,n?U(`small`,{},` · matching words; search by meaning is loading`):null),c),a.length?U(`ul`,{className:`list`},a.slice(0,40).map(({r:e,also:t})=>fn(e,{terms:o,also:t}))):U(`p`,{className:`empty`},`No matches. Try other words, or All places.`))}function Fn(e){let t=Zt.get(e);if(!t)return Q(U(`p`,{className:`empty`},`That record is not in this app any more. `,U(`a`,{href:`#/`},`Home`)));let n=nn(t),r=B(t,q),a=new Set([`id`,`owner`,`text`,`vector`,`norm`,`embed`,`entityType`,q.identity,q.thread,...q.title,...q.subtitle,...q.media,...q.temporal??[]]),o=Object.entries(t).filter(([e,t])=>!a.has(e)&&!/(_id|_sha256)$/.test(e)&&(typeof t==`string`||typeof t==`number`)&&String(t).length<120),c=U(`div`,{className:`doc`}),l=n?U(`button`,{type:`button`,className:`act`,onclick:()=>{if(c.firstChild){c.replaceChildren(),l.textContent=`View document`;return}c.append(U(`iframe`,{src:`./doc?u=${encodeURIComponent(n)}${Number(t.page)>1?`#page=${t.page}`:``}`,title:`Source document`,loading:`lazy`})),l.textContent=`Hide document`}},`View document`):null,u=wt(W.rows.filter(e=>X(e)===X(t)&&tn(e)===tn(t)&&i(e,q)===i(t,q)),q,{facet:Y}),d=[...u.upcoming,...u.past].flatMap(e=>e.items).filter(e=>s(e,q)!==s(t,q)),f=mn(t),p=Ke(W.rows,t.id,q,{limit:12}).near.map(e=>J.get(e.id)).filter(e=>e&&s(e,q)!==s(t,q)).slice(0,6);Q(U(`p`,{},U(`a`,{href:`#/`,onclick:e=>{history.length>1&&(e.preventDefault(),history.back())}},`← Back`)),U(`article`,{className:`record`},U(`small`,{},[X(t),i(t,q)].filter(Boolean).join(` · `)),U(`h2`,{},on(t)),r?U(`p`,{},r):U(`p`,{className:`hint`},`No decision is recorded under this item (it may be on an agenda, before the meeting).`),o.length?U(`dl`,{},o.map(([e,t])=>[U(`dt`,{},e.replace(/_/g,` `)),U(`dd`,{},String(t))])):null,U(`div`,{className:`acts`},un(t),l,n?U(`a`,{href:n,target:`_blank`,rel:`noopener`,className:`act`},`Source ↗`):null),G&&t.paid_sha256?Rn(t):null,c),f&&f.steps.length>1?U(`section`,{},U(`h2`,{},`Followed across meetings`),vn(f)):null,d.length?U(`section`,{},U(`h2`,{},`Same meeting`),U(`ul`,{className:`list`},d.slice(0,12).map(e=>fn(e,{meta:!1})))):null,p.length?U(`section`,{},U(`h2`,{},`Similar`),U(`ul`,{className:`list`},p.map(e=>fn(e)))):null)}var In=G?`${Number(G.price)/10**(G.decimals??6)} ${G.symbol??`USDC`}`:``,Ln={get(e){try{return JSON.parse(localStorage.getItem(`${Wt}:paid:${e}`))}catch{return null}},set(e,t){try{localStorage.setItem(`${Wt}:paid:${e}`,JSON.stringify(t))}catch{}}};function Rn(e){let n=e[q.identity]??e.id,r=U(`section`,{className:`paid`}),i=e=>{let t=e.record;r.replaceChildren(U(`h3`,{},`Decision record`),t.summary?U(`p`,{},t.summary):null,U(`dl`,{},[[`Action`,t.action],[`Outcome`,t.outcome],[`Vote`,t.vote&&`${t.vote.for}–${t.vote.against}`]].filter(([,e])=>e).map(([e,t])=>[U(`dt`,{},e),U(`dd`,{},t)])),t.steps?.length>1?U(`ol`,{},t.steps.map(e=>U(`li`,{},e.result?`${e.motion} · ${e.result}`:e.motion))):null,t.amounts?.length?U(`ul`,{},t.amounts.map(e=>U(`li`,{},typeof e==`string`?e:[e.amount,e.for?` · ${e.for}`:``]))):null,t.organizations?.length?U(`p`,{},`Organizations: `,t.organizations.map(e=>typeof e==`string`?e:`${e.name}${e.role===`other`?``:` (${e.role})`}`).join(`, `)):null,U(`small`,{},e.verified?`✓ Matches the record this app published`:e.verified===!1?`✗ Does not match the record this app published`:``,e.receipt?.transaction?[` · `,U(`span`,{},`paid, tx ${e.receipt.transaction.slice(0,10)}…`)]:``))},a=Ln.get(n);if(a)return i(a),r;let o=U(`small`,{}),s=U(`button`,{type:`button`,className:`act`},`Read the decision record · ${In}`);return s.onclick=async()=>{s.disabled=!0,o.textContent=`Opening your wallet…`;try{let r=await(await t(async()=>{let{wallet:e}=await import(`./wallet-DMLc-ukR.js`);return{wallet:e}},__vite__mapDeps([6,1,2,7,8,9,10,11,12,13]),import.meta.url)).wallet(G);await r.signIn(),o.textContent=`Paying…`;let a=await r.buy(n,e.paid_sha256);if(a.error)throw Error(a.error);Ln.set(n,a),i(a)}catch(e){s.disabled=!1,o.replaceChildren(`Couldn't buy it: ${e.message}. `,U(`a`,{href:`#/wallet`},`Your wallet`))}},r.append(U(`p`,{className:`hint`},`What was decided, as fields: action, outcome, vote, amounts and who they are for, organizations. Agents buy the same record over x402.`),U(`div`,{className:`acts`},s,o)),r}function zn(e,t,n){let r=U(`a`,{href:URL.createObjectURL(new Blob([t],{type:n})),download:e});document.body.append(r),r.click(),r.remove()}var Bn={get(){try{return JSON.parse(localStorage.getItem(`${Wt}:knobs`))??{}}catch{return{}}},set(e){try{localStorage.setItem(`${Wt}:knobs`,JSON.stringify(e))}catch{}}},$={...Object.fromEntries(Object.entries(_t).map(([e,[t]])=>[e,t])),...Bn.get(),seed:1},Vn={lookahead:[`Lookahead`,`what you like now`,`where you're heading`],variety:[`Variety`,`close to each other`,`all different`],surprise:[`Surprise`,`best first`,`shuffled`],reach:[`Reach`,`nearest`,`further out`]};function Hn(){let{likes:e,dislikes:t}=W.votes(),n=I(W.rows.filter(xn),e,t,{...$,limit:30}),r=e=>{let[t,n,r]=Vn[e],i=U(`output`,{},`${Math.round($[e]*100)}%`),a=U(`input`,{type:`range`,min:0,max:1,step:.05,value:$[e],id:`k-${e}`,title:_t[e][1]});return a.oninput=()=>{i.textContent=`${Math.round(a.value*100)}%`},a.onchange=()=>{$[e]=Number(a.value),Bn.set({...$,seed:void 0}),Hn(),document.getElementById(`k-${e}`)?.focus()},U(`label`,{className:`knob`,htmlFor:`k-${e}`},U(`span`,{},t,i),a,U(`small`,{},`${n} ↔ ${r}`))},i=(e,t)=>U(`li`,{className:t?`no`:``},e.title,U(`button`,{type:`button`,ariaLabel:`Forget ${e.title}`,onclick:()=>W.rate(e.id,`clear`)},`×`));Q(U(`h2`,{className:`page`},`For you`),yn(),U(`section`,{className:`taste`},e.length||t.length?U(`ul`,{className:`pills`},e.slice(-30).map(e=>i(e)),t.slice(-10).map(e=>i(e,!0))):U(`p`,{className:`hint`},`Save records with ☆ and this fills with more like them. Newer saves count more; it all stays in this browser.`),n.taste?U(`p`,{className:`hint`},n.taste.heading?`Your recent likes point somewhere new; Lookahead follows them.`:`Like 4 or more and Lookahead can follow where your taste is heading.`):null,U(`div`,{className:`knobs`},Object.keys(_t).map(r)),U(`button`,{type:`button`,className:`act`,disabled:!$.surprise,title:$.surprise?``:`Turn up Surprise to reroll`,onclick:()=>{$.seed++,Hn()}},`Reroll`)),n.picks.length?U(`ul`,{className:`list`},n.picks.map(({row:e})=>fn(e))):null)}function Un(){let e=K.saved(),t=K.taste(),n=Vt.toLowerCase().replace(/\W+/g,`-`),r=()=>JSON.stringify(K.bundle({name:Vt,url:location.origin}),null,1),i=U(`button`,{type:`button`,className:`act`,onclick:async()=>{try{await navigator.clipboard.writeText(r()),i.textContent=`Copied`}catch{zn(`${n}-session.json`,r(),`application/json`)}}},`Copy for an agent`);Q(U(`h2`,{className:`page`},`Saved (${e.length})`),e.length?U(`div`,{className:`acts`},U(`button`,{type:`button`,className:`act`,onclick:()=>zn(`${n}-saved.json`,r(),`application/json`)},`Export JSON`),U(`button`,{type:`button`,className:`act`,onclick:()=>zn(`${n}-saved.csv`,kt(e),`text/csv`)},`CSV`),U(`button`,{type:`button`,className:`act`,onclick:()=>zn(`${n}-saved.md`,At(e,`${Vt}: saved`),`text/markdown`)},`Markdown`),i):U(`p`,{className:`empty`},`Nothing saved yet. Tap ☆ on a record; what you save stays on this device.`),t?U(`p`,{className:`hint`},`"For you" is built from these ${t.n}${t.rejected.length?`, and away from ${t.rejected.length} you passed on`:``}. It lives in this browser; the export carries it, so an agent can use it too. `,U(`button`,{type:`button`,className:`linkish`,onclick:()=>{K.clear(`passed`),Un(),Kn()}},`Forget what I passed on`)):null,U(`ul`,{className:`list`},e.slice().reverse().map(e=>{let t=Zt.get(e.key);return U(`li`,{className:`row`},U(`div`,{className:`main`},U(`a`,{href:t?sn(t):e.url??`#/`,className:`title`},e.title),U(`small`,{},[e.place,e.subtitle].filter(Boolean).join(` · `)),e.detail?U(`p`,{className:`detail`},It(e.detail)):null),U(`button`,{type:`button`,className:`vote`,ariaLabel:`Forget ${e.title}`,title:`Forget`,onclick:()=>{K.unvote(e.key),Un(),Kn()}},`×`))})))}function Wn(){let e=K.history();Q(U(`h2`,{className:`page`},`Searches this session`),e.length?U(`ul`,{className:`list`},e.map(({q:e,at:t})=>U(`li`,{className:`row`},U(`div`,{className:`main`},U(`a`,{href:`#/search/${encodeURIComponent(e)}`,className:`title`},e),U(`small`,{},new Date(t).toLocaleTimeString()))))):U(`p`,{className:`empty`},`No searches yet.`),e.length?U(`button`,{type:`button`,className:`act`,onclick:()=>{K.clear(`history`),Wn()}},`Clear`):null)}async function Gn(){Q(U(`p`,{className:`empty`},`Opening your wallet…`));let e;try{e=await(await t(async()=>{let{wallet:e}=await import(`./wallet-DMLc-ukR.js`);return{wallet:e}},__vite__mapDeps([6,1,2,7,8,9,10,11,12,13]),import.meta.url)).wallet(G)}catch(e){return Q(U(`p`,{className:`empty`},`The wallet is unavailable: ${e.message}`))}let n=async()=>{if(!location.hash.startsWith(`#/wallet`))return;if(!e.authenticated)return Q(U(`h2`,{},`Wallet`),U(`p`,{},`Sign in to buy decision records (${In} each). An email or Google sign-in gives you a wallet here, the same one in every Fangorn app; nothing to install.`),U(`div`,{className:`acts`},U(`button`,{type:`button`,className:`act`,onclick:()=>e.signIn()},`Sign in`)));let t=e.address?await e.balance().catch(()=>null):null;Q(U(`h2`,{},`Wallet`),U(`dl`,{},U(`dt`,{},`Address`),U(`dd`,{},e.address??`making your wallet…`),U(`dt`,{},`Balance`),U(`dd`,{},t==null?`…`:`${t.toFixed(2)} ${G.symbol??`USDC`} on ${e.chain.name}`)),U(`div`,{className:`acts`},U(`button`,{type:`button`,className:`act`,onclick:()=>e.addFunds()},e.faucet?`Get test funds (faucet)`:`Add funds`),U(`button`,{type:`button`,className:`act`,onclick:()=>e.logout()},`Sign out`)),e.faucet?U(`p`,{className:`hint`},`This app runs on a test network, where funds are free: use the faucet with your address above.`):null)};e.onChange(()=>{n()})}function Kn(){let e=K.saved().length;H(`#nsaved`).textContent=e?` (${e})`:``}function qn(){let[,e=``,t=``]=location.hash.match(/^#\/([^/]*)\/?(.*)$/)??[],n=decodeURIComponent(t);for(let t of document.querySelectorAll(`nav a`))t.classList.toggle(`on`,t.getAttribute(`href`)===`#/${e}`);return document.body.classList.toggle(`wide`,e===`map`),e===`search`&&n?Nn(n):e===`item`&&n?Fn(n):e===`signals`&&q.thread?On():e===`saved`?Un():e===`for-you`?Hn():e===`history`?Wn():e===`wallet`&&G?Gn():e===`map`&&Ht?(Mn??=Ft(Ht,{el:U,show:Q,row:fn,placeOf:X,ctx:W})).render(...t.split(`/`).map(decodeURIComponent)):(H(`#q input`).value=``,kn())}var Jn;Ut.oninput=()=>{clearTimeout(Jn);let e=Ut.value.trim();Jn=setTimeout(()=>{if(!e){location.hash.startsWith(`#/search`)&&(location.hash=`#/`);return}history.replaceState(null,``,`#/search/${encodeURIComponent(e)}`),Nn(e)},250)},H(`#q`).onsubmit=e=>{e.preventDefault();let t=Ut.value.trim();t&&(K.remember(t),location.hash=`#/search/${encodeURIComponent(t)}`)},Sn.addEventListener(`click`,e=>{e.target.closest(`a.title`)&&location.hash.startsWith(`#/search/`)&&K.remember(Ut.value)}),window.onhashchange=qn,Kn(),qn(),W.rows=await Yt,qt=!0,en(),St(W),qn();
@@ -0,0 +1 @@
1
+ import{d as e}from"./roles-DKPW79Ca.js";export{e as rolesFrom};
@@ -0,0 +1 @@
1
+ var e=/^\s*[[(].*[\])]\s*$/,t=/,(?! )/;function n(n){if(Array.isArray(n))return n.map(e=>String(e));let r=String(n??``),i=e.test(r)?r.trim().slice(1,-1):t.test(r)?r:null;return i===null?r===``?[]:[r]:i.split(`,`).map(e=>e.trim().replace(/^['"]|['"]$/g,``)).filter(Boolean)}var r={title:[`title`,`name`,`label`,`headline`,`path`],subtitle:[`subtitle`,`primaryType`,`entityType`,`kind`,`series`,`creator`],text:[`text`,`desc`,`description`,`summary`,`body`,`abstract`]},i=e=>Array.isArray(e)?e.filter(Boolean):e?[e]:[];function a(e=[],t=[]){let n=new Set;for(let e of t)for(let t of Object.keys(e))n.add(t);let a=[],o=[],s=[],c=[],l=[],u=[],d=[],f=[],p=[],m={},h={},g={},_={},v={},y=null,b=null,x=[],S=[],C=null,w=null,T=(e,t)=>{for(let n of i(t))n&&!e.includes(n)&&e.push(n)};for(let t of e){let e=t?.role_map??{};T(a,e.title),T(o,e.subtitle),T(s,e.tags),T(c,e.text),T(l,e.measures),T(u,e.spatial),T(d,e.media),T(f,e.temporal),C??=e.identity??null,w??=e.thread??null,e.refers?.field&&e.refers?.corpus&&e.refers?.to&&(b??={...e.refers}),Object.assign(m,e.labels??{});for(let t of i(e.fields))n.add(t);let r=t?.presentation??{};Object.assign(h,r.types??{}),Object.assign(g,r.fieldLabels??{}),Object.assign(_,r.externalUrl??{}),Object.assign(v,r.actions??{}),T(p,r.facets),y??=r.launch??null;for(let e of i(t?.entity_types))x.push(e);t?.paywall&&S.push({domain:t.name??null,...t.paywall})}let E=(e,t)=>{e.length||(T(e,t.filter(e=>n.has(e))),T(e,t))};return E(a,r.title),E(o,r.subtitle),E(c,r.text),{title:a,subtitle:o,tags:s,text:c,measures:l,spatial:u,media:d,temporal:f,facets:p,identity:C,thread:w,labels:m,types:h,fieldLabels:g,externalUrl:_,actions:v,launch:y,refers:b,entityTypes:x,gates:S,fields:[...n].sort(),declared:e.some(e=>e?.role_map)}}var o=e=>{let t=e?.entityType??e?.kind;return t==null||t===``?null:String(t)};function s(e){let t=String(e);return/s$/i.test(t)?t:/(x|z|ch|sh)$/i.test(t)?`${t}es`:/[^aeiou]y$/i.test(t)?`${t.slice(0,-1)}ies`:`${t}s`}function c(e,t=[]){let n=new Map;for(let t of e.entityTypes)n.set(t.type,(n.get(t.type)??0)+(t.count??0));if(!n.size)for(let e of t){let t=o(e);t&&n.set(t,(n.get(t)??0)+1)}return[...n].map(([t,n])=>{let r=e.types[t]??{};return{type:t,count:n,icon:r.icon??null,accent:r.accent??null,singular:r.singular??t,plural:r.plural??s(r.singular??t)}}).sort((e,t)=>t.count-e.count)}function l(e){return(e.gates??[]).map(e=>({domain:e.domain??null,locked:[...e.locked??[]],free:[...e.free??[]],price:String(e.price??`0`),asset:e.asset??`USDC`,resourceId:e.resourceId??null,workerUrl:e.workerUrl??null,files:(e.files??[]).map(e=>({file:e.file,count:e.count,bytes:e.bytes})),unpublished:!e.resourceId}))}var u=(e,t)=>(t.gates??[]).some(t=>(t.locked??[]).includes(e));async function d(e,t){if(!(t>0))return!1;if(t>=1)return!0;let n=await crypto.subtle.digest(`SHA-256`,new TextEncoder().encode(String(e)));return new DataView(n).getUint32(0)<t*2**32}var f=e=>`${(Number(e.price??0)/1e6).toFixed(e.price%1e3?6:2)} ${e.asset??`USDC`}`,p=(e,t=[])=>{for(let n of t){let t=e?.[n];if(t!=null&&t!==``)return t}return null},m=(e,t)=>String(p(e,t.title)??e?.id??`untitled`),h=(e,t)=>{let n=p(e,t.subtitle);return n==null?null:String(n)};function g(e,t){let n=t.text.map(t=>e?.[t]).filter(e=>e!=null&&e!==``);if(n.length)return n.join(` `);if(t.text.some(e=>u(e,t)))return``;let r=p(e,t.title);return r?String(r):``}function _(e,t,r=[]){let i={id:e.id,title:m(e,t)},a=h(e,t);a&&(i.subtitle=a);for(let a of[...t.tags,...t.measures,...r]){let r=e[a];if(r==null||r===``)continue;let o=t.tags.includes(a)?n(r):null;i[a]=o?o.length>6?[...o.slice(0,6),`…+${o.length-6} more`]:o:typeof r==`string`&&r.length>160?`${r.slice(0,160)}…`:r}return i}function v(e,t){let n=t.externalUrl?.[o(e)];if(!n)return null;let r=!1,i=String(n).replace(/\{(\w+)\}/g,(t,n)=>{let i=e?.[n];return i==null||i===``?(r=!0,``):String(i)});return r?null:i}function y(e,t){return(t?.actions?.[o(e)]??[]).map(n=>{let r=[],i=[],a=String(n.href??``).replace(/\{(\w+)\}/g,(n,a)=>{let o=e?.[a];return o!=null&&o!==``?String(o):((u(a,t)?r:i).push(a),``)});return{kind:n.kind??`link`,label:n.label??n.kind??`open`,href:a,locked:r,missing:i}}).filter(e=>e.locked.length||!e.missing.length)}if(typeof process<`u`&&import.meta.url===`file://${process.argv[1]}`&&process.argv[1].endsWith(`/roles.js`)){let e={role_map:{identity:`placeId`,title:`title`,subtitle:`primaryType`,spatial:`locality`,media:`googleMapsUri`,tags:[`categories`],measures:[`rating`,`userRatingCount`],temporal:null,relations:[],text:[`text`,`editorialSummary`],labels:{title:`Title`},fields:[`address`,`phone`,`website`]},presentation:{types:{Business:{icon:`🏪`,singular:`Place`}},externalUrl:{Business:`{googleMapsUri}`,Event:`{ticketUrl}`}},entity_types:[{type:`Event`,count:654},{type:`Business`,count:263}]},t={id:`r1`,entityType:`Business`,title:`Cira's Red, White & Brew Bar`,primaryType:`Bar & Grill`,text:`Bar & Grill in Saint Germain, WI.`,editorialSummary:``,categories:`['Bar', 'Restaurant']`,rating:`4.7`,userRatingCount:`293`,googleMapsUri:`https://maps.google.com/?cid=140971`},r=a([e],[t]);if(!r.declared)throw Error(`a manifest with a role_map is declared, not sniffed`);if(m(t,r)!==`Cira's Red, White & Brew Bar`)throw Error(`declared title lost`);if(h(t,r)!==`Bar & Grill`)throw Error(`declared subtitle lost`);if(r.thread!==null||a([{role_map:{thread:`part_of`}},e]).thread!==`part_of`)throw Error(`thread is declared, never guessed`);if(g(t,r)!==`Bar & Grill in Saint Germain, WI.`)throw Error(`empty declared text fields must be skipped, not joined as blanks`);{let e={field:`videoPath`,corpus:`archive-films`,to:`path`},t=a([{role_map:{identity:`videoPath`,refers:e}}]).refers;if(t?.corpus!==`archive-films`||t.field!==`videoPath`||t.to!==`path`)throw Error(`a complete reference must survive the manifest`);for(let t of[`field`,`corpus`,`to`]){let n={...e};if(delete n[t],a([{role_map:{refers:n}}]).refers)throw Error(`a reference missing '${t}' must not be honoured`)}if(a([{role_map:{title:`name`}}]).refers!==null)throw Error(`a corpus that declared no reference has none — this is never sniffed`)}if(v(t,r)!==`https://maps.google.com/?cid=140971`)throw Error(`presentation.externalUrl not applied`);if(v({...t,entityType:`Event`},r)!==null)throw Error(`a template with a missing field must be null, not a half-built link`);if(!r.fields.includes(`phone`)||!r.fields.includes(`website`))throw Error(`role_map.fields must reach the facet picker`);if(r.types.Business.icon!==`🏪`)throw Error(`presentation.types lost`);for(let e of[[`Bar`,`Restaurant`],`['Bar', 'Restaurant']`,`Bar,Restaurant`]){let n=_({...t,categories:e},r).categories;if(!Array.isArray(n)||n.join()!==`Bar,Restaurant`)throw Error(`tags must normalize: ${JSON.stringify(e)} → ${JSON.stringify(n)}`)}if(n(`Wilson, Michael G.`).length!==1)throw Error(`a comma followed by a space is prose, not a list`);if(n(``).length!==0)throw Error(`an empty value is no values, not one empty one`);if(Array.isArray(_(t,r).rating))throw Error(`a measure must stay scalar`);let i=_({...t,categories:Array.from({length:50},(e,t)=>`c${t}`)},r);if(i.categories.length!==7)throw Error(`long tag lists must truncate: ${i.categories.length}`);if(!String(i.categories.at(-1)).includes(`+44 more`))throw Error(`truncation must say how much it dropped`);if(_({...t,categories:[`a`,`b`]},r).categories.length!==2)throw Error(`a short list must pass through whole`);let p=_(t,r);if(p.title!==t.title||p.rating!==`4.7`||!p.categories)throw Error(`brief must carry declared tags + measures: ${JSON.stringify(p)}`);if(`googleMapsUri`in p)throw Error(`brief must not dump undeclared fields`);let b=a([{...e,name:`places`,paywall:{free:[`title`,`primaryType`],locked:[`text`,`phone`],price:`50000`,asset:`USDC`,resourceId:`0xdead`,workerUrl:`https://w`,files:[{file:`locked-0000-abc.ndjson.gz`,count:917,bytes:1234}]}}]),[x]=l(b);if(x.locked.join()!==`text,phone`)throw Error(`the locked field list must survive`);if(x.unpublished)throw Error(`a gate with a resourceId is buyable`);if(f(x)!==`0.05 USDC`)throw Error(`price must render as USDC, got ${f(x)}`);if(!u(`phone`,b)||u(`title`,b))throw Error(`isLocked must read the manifest`);if([x]=l(a([{...e,paywall:{locked:[`text`],price:`1`}}])),!x.unpublished)throw Error(`no resourceId means there is nothing to buy yet`);if(l(a([e])).length)throw Error(`an ungated view must report no gates`);if(u(`text`,a([e])))throw Error(`nothing is locked in an ungated view`);for(let[e,t]of[[`row-47`,!0],[`row-169`,!0],[`row-0`,!1],[`row-3`,!1],[`row-19`,!1]])if(await d(e,.01)!==t)throw Error(`inSample must match the bake: ${e}`);if(await d(`row-0`,0)!==!1||await d(`row-0`,1)!==!0)throw Error(`rate 0 gives nothing away, rate 1 gives everything`);let S=0;for(let e=0;e<2e3;e++)await d(`row-${e}`,.01)&&S++;if(S!==19)throw Error(`the bake picks exactly 19 of these 2000 — got ${S}`);let C=a([{...e,name:`places`,role_map:{...e.role_map,text:[`text`]},paywall:{free:[`title`],locked:[`text`],price:`1`}}]);if(g({title:`Cira's Bar`},C)!==``)throw Error(`a locked text role must read empty, not as the title`);if(g({title:`Cira's Bar`,text:`A bar.`},C)!==`A bar.`)throw Error(`a sampled row's real text must still win`);if(g({title:`Cira's Bar`},a([e]))!==`Cira's Bar`)throw Error(`the title fallback still stands when nothing is gated`);let w=c(a([e]));if(w.map(e=>e.type).join()!==`Event,Business`)throw Error(`collections must sort by count: ${w.map(e=>e.type)}`);if(w[1].icon!==`🏪`||w[1].singular!==`Place`)throw Error(`presentation.types must reach the collection`);if(w[1].plural!==`Places`)throw Error(`plural must derive from the declared singular: ${w[1].plural}`);if(w[0].singular!==`Event`||w[0].icon!==`🎫`&&w[0].icon!==null)throw Error(`an undescribed type must still be a collection`);if(w=c(a([{role_map:{title:`title`}}]),[t,t,{id:`e`,entityType:`Event`}]),w[0].type!==`Business`||w[0].count!==2)throw Error(`row counting fallback wrong: ${JSON.stringify(w)}`);for(let[e,t]of[[`subtitles`,`subtitles`],[`series`,`series`],[`video`,`videos`],[`folder`,`folders`],[`box`,`boxes`],[`category`,`categories`],[`day`,`days`]])if(s(e)!==t)throw Error(`pluralize(${e}) = ${s(e)}, want ${t}`);if(o({kind:`video`})!==`video`)throw Error("the older `kind` spelling is still on-chain");if(o({})!==null)throw Error(`a typeless row must be null, not 'undefined'`);let T={name:`videos`,shards:[]},E={id:`v1`,name:`S01E02 - Overture.mp4`,path:`classic_tv/Duet/S01E02.mp4`,kind:`video`,desc:`A forgotten Fox sitcom.`};if(r=a([T],[E]),r.declared)throw Error(`a manifest with no role_map must report itself as sniffed`);if(m(E,r)!==`S01E02 - Overture.mp4`)throw Error(`sniff must prefer name over path: ${m(E,r)}`);if(r.title[0]!==`name`)throw Error(`a field the sample HAS must outrank one it does not`);if(h(E,r)!==`video`)throw Error(`sniff must find a subtitle`);if(!g(E,r).includes(`forgotten Fox`))throw Error(`sniff must find the prose`);if(m({id:`x`},a([],[]))!==`x`)throw Error(`a title must always fall back to the id`);if(g({id:`x`},a([],[]))!==``)throw Error(`no prose and no title must be empty, not 'undefined'`);let D=a([],[]);if(!D.text.includes(`desc`)||!D.title.includes(`name`))throw Error(`a blind sniff must still offer candidates`);if(g(E,D)!==`A forgotten Fox sitcom.`)throw Error(`a row parsed before any sample must still get its prose`);if(m({id:`z`,title:`declared later`},D)!==`declared later`)throw Error(`a blind sniff must resolve per row`);if(r=a([e,{role_map:{title:`name`,text:[`body`]}}]),m({name:`a note`},r)!==`a note`)throw Error(`the second domain's title field must resolve too`);if(m(t,r)!==t.title)throw Error(`the first domain's title must still resolve`);if(!r.text.includes(`text`)||!r.text.includes(`body`))throw Error(`text fields must union across domains`);if(r.title.includes(`label`))throw Error(`a declared title must not be widened by guesses`);{let e=a([{name:`films`,role_map:{identity:`identifier`,title:`name`,text:[`desc`]},paywall:{free:[`identifier`,`name`],locked:[`path`],price:`50000`,asset:`USDC`},presentation:{actions:{video:[{kind:`video`,label:`Play`,href:`https://cdn.test/{path}`},{kind:`link`,label:`On archive.org`,href:`https://archive.org/details/{identifier}`},{kind:`link`,label:`Dead`,href:`https://x.test/{nosuchfield}`}]}}}]),t=y({entityType:`video`,identifier:`the-temp`,name:`temp.mp4`},e);if(t.length!==2)throw Error(`a template naming a field the bundle never had must drop: ${JSON.stringify(t)}`);if(!t.find(e=>e.kind===`video`).locked.includes(`path`))throw Error(`a player whose url is behind the paywall must say which field is locked`);let n=t.find(e=>e.kind===`link`);if(n.href!==`https://archive.org/details/the-temp`)throw Error(`free fields must interpolate: ${n.href}`);if(n.locked.length)throw Error(`a fully resolved action is not locked`);if(y({entityType:`video`,identifier:`the-temp`,name:`temp.mp4`,path:`a/b.mp4`},e).find(e=>e.kind===`video`).href!==`https://cdn.test/a/b.mp4`)throw Error(`an unlocked player must resolve`);if(y({entityType:`video`},a([])).length)throw Error(`no declaration, no actions`)}console.log(`roles.js self-check ok — declared role_map wins, sniffed fallback, fused domains merge, external links, declared actions resolve and name their locked fields, a cross-corpus reference is honoured only when whole and never sniffed, title never undefined`)}export{d as a,p as c,a as d,h as f,n as g,o as h,l as i,s as l,m,_ as n,u as o,g as p,c as r,v as s,y as t,f as u};
@@ -1,14 +1,14 @@
1
1
  <!doctype html>
2
2
  <html lang="en">
3
- <script type="module" crossorigin src="./assets/index-B5YmgwLH.js"></script>
3
+ <script type="module" crossorigin src="./assets/index-CurczxO7.js"></script>
4
4
  <link rel="modulepreload" crossorigin href="./assets/rolldown-runtime-BcKkbAw3.js">
5
5
  <link rel="modulepreload" crossorigin href="./assets/preload-helper-uBIymjUX.js">
6
- <link rel="modulepreload" crossorigin href="./assets/roles-C9V_uDr_.js">
6
+ <link rel="modulepreload" crossorigin href="./assets/roles-DKPW79Ca.js">
7
7
 
8
8
  <meta charset="utf-8">
9
9
  <meta name="viewport" content="width=device-width, initial-scale=1">
10
10
  <title>Fangorn app</title>
11
- <style>:root{--bg:#fbfbf8;--fg:#1a1c19;--muted:#6a6f68;--line:#dfe1db;--accent:#2f5d45;--mark:#e6eedb;--hot:#b4432f;--edge:#b7bab2;--font:system-ui, sans-serif;--font-display:var(--font);--radius:3px;--gutter:16px;--measure:760px;--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light}@media (prefers-color-scheme:dark){:root:not([data-theme=light]){--bg:#121412;--fg:#e4e6e0;--muted:#959a92;--line:#2a2e29;--accent:#8fbf9f;--mark:#2d3b2a;--hot:#d9674f;--edge:#4b5049;--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark}}*{box-sizing:border-box}body{background:var(--bg);color:var(--fg);font:16px/1.5 var(--font);margin:0}a{color:var(--accent)}button,input{font:inherit;color:inherit}button{cursor:pointer;border:1px solid var(--line);border-radius:var(--radius);background:0 0;padding:4px 10px}button:disabled{opacity:.45;cursor:default}:focus-visible{outline:2px solid var(--accent);outline-offset:2px}ul{margin:0;padding:0;list-style:none}h2{text-transform:uppercase;letter-spacing:.08em;color:var(--muted);margin:28px 0 8px;font-size:.75rem;font-weight:600}h2.page,.record h2{font:600 1.3rem/1.3 var(--font-display);text-transform:none;letter-spacing:0;color:var(--fg);margin:4px 0 12px}h3{margin:0;font-size:1rem}small,time,.hint,.empty,.about,#status,.resultsbar,.detail{color:var(--muted)}small,time,.hint{font-size:.8rem}.paid{border:1px solid var(--line);border-radius:var(--radius);gap:6px;margin:12px 0;padding:10px 14px;display:grid}.paid h3{margin:0;font-size:1rem}.paid ul{margin:0;padding-left:18px}.detail{margin:0;font-size:.9rem}mark{background:var(--mark);color:inherit}.bar{z-index:2;background:var(--bg);border-bottom:1px solid var(--line);position:sticky;top:0}.bar>div,main,footer{max-width:var(--measure);padding-inline:var(--gutter);margin:0 auto}.bar>div{flex-wrap:wrap;align-items:center;gap:12px;padding-block:10px;display:flex}#home{color:var(--fg);font:600 1.1rem var(--font-display);white-space:nowrap;text-decoration:none}#q{flex:240px}#q input{border:1px solid var(--line);border-radius:var(--radius);background:0 0;width:100%;padding:8px 10px}nav{gap:14px;font-size:.9rem;display:flex}nav a{color:var(--muted);text-decoration:none}nav a.on,nav a:hover{color:var(--fg)}main{padding-block:16px 48px}.list,article{border-top:1px solid var(--line)}.row{border-bottom:1px solid var(--line);align-items:flex-start;gap:10px;padding:10px 0;display:flex}.main{overflow-wrap:anywhere;flex-direction:column;flex:1;gap:2px;min-width:0;display:flex}.title{color:var(--fg);font-weight:500;text-decoration:none}.title:hover{color:var(--accent)}.resultsbar{flex-wrap:wrap;justify-content:space-between;align-items:center;gap:8px;margin-bottom:8px;font-size:.9rem;display:flex}.sort,.chips,.acts,.votes{flex-wrap:wrap;align-items:center;gap:6px;display:flex}.chips{margin:4px 0 16px}.picker{color:var(--muted);flex-direction:column;gap:4px;margin:4px 0 16px;font-size:.85rem;display:inline-flex}.picker select{font:inherit;color:var(--fg);background:var(--bg);border:1px solid var(--edge);border-radius:var(--radius);min-width:min(320px,100%);padding:6px 10px;font-size:1rem}details.about summary{cursor:pointer;color:var(--fg);font-size:1.05rem}details.about p{color:var(--muted);margin:6px 0 0}.chip{padding:2px 10px;font-size:.85rem}.chip.on,.vote[aria-pressed=true]{border-color:var(--accent);background:var(--mark)}.vote{padding:2px 7px;line-height:1.3}.star{color:var(--muted);border:0;padding:4px 6px;font-size:1.25rem;line-height:1}.star[aria-pressed=true]{color:var(--accent);background:0 0}.more,.linkish{color:var(--accent);border:0;padding:8px 0 0;font-size:.9rem}.act{color:var(--fg);border:1px solid var(--line);border-radius:var(--radius);padding:4px 10px;font-size:.88rem;text-decoration:none}article{padding:12px 0 4px}article header{flex-direction:column;margin-bottom:4px;display:flex}article .list{border-top:0}article .row:last-child{border-bottom:0}.record{border-top:0}.record dl{grid-template-columns:max-content 1fr;gap:2px 14px;margin:12px 0;font-size:.88rem;display:grid}.record dt{color:var(--muted);text-transform:capitalize}.record dd{margin:0}.acts{margin:12px 0 4px}.doc iframe{border:1px solid var(--line);background:#fff;width:100%;height:80vh;margin-top:12px}.taste{margin-bottom:20px}.pills{flex-wrap:wrap;gap:6px;margin:6px 0;display:flex}.pills li{border:1px solid var(--line);border-radius:var(--radius);align-items:center;gap:2px;padding:0 0 0 8px;font-size:.85rem;display:inline-flex}.pills li.no{color:var(--muted);text-decoration:line-through}.pills button{color:var(--muted);border:0;padding:0 8px}.knobs{grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px;margin:12px 0;display:grid}.knob{gap:2px;font-size:.85rem;display:grid}.knob span{justify-content:space-between;font-weight:600;display:flex}.knob input{width:100%;accent-color:var(--accent)}body.wide{--measure:1200px}.mapbar input{border:1px solid var(--line);border-radius:var(--radius);background:0 0;width:100%;margin-bottom:8px;padding:8px 10px}.mapwrap{grid-template-columns:minmax(0,1fr) minmax(0,1fr);align-items:start;gap:16px;display:grid}@media (width<=760px){.mapwrap{grid-template-columns:1fr}}svg.map{width:100%;height:auto;display:block;position:sticky;top:70px}svg.map path{stroke:var(--edge);stroke-width:.8px;cursor:pointer}svg.map path:hover,svg.map path:focus{stroke:var(--accent);stroke-width:2px;outline:none}svg.map path.near{stroke:var(--accent);stroke-width:2px;stroke-dasharray:4 3}svg.map path.on{stroke:var(--fg);stroke-width:2.6px}.h0{fill:var(--bg);background:var(--bg)}.h1{fill:color-mix(in srgb, var(--hot) 18%, var(--bg));background:color-mix(in srgb, var(--hot) 18%, var(--bg))}.h2{fill:color-mix(in srgb, var(--hot) 36%, var(--bg));background:color-mix(in srgb, var(--hot) 36%, var(--bg))}.h3{fill:color-mix(in srgb, var(--hot) 56%, var(--bg));background:color-mix(in srgb, var(--hot) 56%, var(--bg))}.h4{fill:color-mix(in srgb, var(--hot) 78%, var(--bg));background:color-mix(in srgb, var(--hot) 78%, var(--bg))}.h5{fill:var(--hot);background:var(--hot)}.sw{border:1px solid var(--edge);vertical-align:-1px;width:14px;height:12px;display:inline-block}.panel h3{margin:4px 0}.panel .chips{margin:6px 0}.panel .list{margin-top:8px}footer{color:var(--muted);padding-block:0 32px;font-size:.8rem}</style>
11
+ <style>:root{--bg:#fbfbf8;--fg:#1a1c19;--muted:#6a6f68;--line:#dfe1db;--accent:#2f5d45;--mark:#e6eedb;--hot:#b4432f;--edge:#b7bab2;--font:system-ui, sans-serif;--font-display:var(--font);--radius:3px;--gutter:16px;--measure:760px;--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light}@media (prefers-color-scheme:dark){:root:not([data-theme=light]){--bg:#121412;--fg:#e4e6e0;--muted:#959a92;--line:#2a2e29;--accent:#8fbf9f;--mark:#2d3b2a;--hot:#d9674f;--edge:#4b5049;--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark}}*{box-sizing:border-box}body{background:var(--bg);color:var(--fg);font:16px/1.5 var(--font);margin:0}a{color:var(--accent)}button,input{font:inherit;color:inherit}button{cursor:pointer;border:1px solid var(--line);border-radius:var(--radius);background:0 0;padding:4px 10px}button:disabled{opacity:.45;cursor:default}:focus-visible{outline:2px solid var(--accent);outline-offset:2px}ul{margin:0;padding:0;list-style:none}h2{text-transform:uppercase;letter-spacing:.08em;color:var(--muted);margin:28px 0 8px;font-size:.75rem;font-weight:600}h2.page,.record h2{font:600 1.3rem/1.3 var(--font-display);text-transform:none;letter-spacing:0;color:var(--fg);margin:4px 0 12px}h3{margin:0;font-size:1rem}small,time,.hint,.empty,.about,#status,.resultsbar,.detail{color:var(--muted)}small,time,.hint{font-size:.8rem}.paid{border:1px solid var(--line);border-radius:var(--radius);gap:6px;margin:12px 0;padding:10px 14px;display:grid}.paid h3{margin:0;font-size:1rem}.paid ul{margin:0;padding-left:18px}.detail{margin:0;font-size:.9rem}mark{background:var(--mark);color:inherit}.bar{z-index:2;background:var(--bg);border-bottom:1px solid var(--line);position:sticky;top:0}.bar>div,main,footer{max-width:var(--measure);padding-inline:var(--gutter);margin:0 auto}.bar>div{flex-wrap:wrap;align-items:center;gap:12px;padding-block:10px;display:flex}#home{color:var(--fg);font:600 1.1rem var(--font-display);white-space:nowrap;text-decoration:none}#q{flex:240px}#q input{border:1px solid var(--line);border-radius:var(--radius);background:0 0;width:100%;padding:8px 10px}nav{gap:14px;font-size:.9rem;display:flex}nav a{color:var(--muted);text-decoration:none}nav a.on,nav a:hover{color:var(--fg)}main{padding-block:16px 48px}.list,article{border-top:1px solid var(--line)}.row{border-bottom:1px solid var(--line);align-items:flex-start;gap:10px;padding:10px 0;display:flex}.main{overflow-wrap:anywhere;flex-direction:column;flex:1;gap:2px;min-width:0;display:flex}.title{color:var(--fg);font-weight:500;text-decoration:none}.title:hover{color:var(--accent)}.resultsbar{flex-wrap:wrap;justify-content:space-between;align-items:center;gap:8px;margin-bottom:8px;font-size:.9rem;display:flex}.sort,.chips,.acts,.votes{flex-wrap:wrap;align-items:center;gap:6px;display:flex}.chips{margin:4px 0 16px}.picker{color:var(--muted);flex-direction:column;gap:4px;margin:4px 0 16px;font-size:.85rem;display:inline-flex}.picker select{font:inherit;color:var(--fg);background:var(--bg);border:1px solid var(--edge);border-radius:var(--radius);min-width:min(320px,100%);padding:6px 10px;font-size:1rem}details.about summary{cursor:pointer;color:var(--fg);font-size:1.05rem}details.about p{color:var(--muted);margin:6px 0 0}.chip{padding:2px 10px;font-size:.85rem}.chip.on,.vote[aria-pressed=true]{border-color:var(--accent);background:var(--mark)}.vote{padding:2px 7px;line-height:1.3}.star{color:var(--muted);border:0;padding:4px 6px;font-size:1.25rem;line-height:1}.star[aria-pressed=true]{color:var(--accent);background:0 0}.more,.linkish{color:var(--accent);border:0;padding:8px 0 0;font-size:.9rem}.act{color:var(--fg);border:1px solid var(--line);border-radius:var(--radius);padding:4px 10px;font-size:.88rem;text-decoration:none}article{padding:12px 0 4px}article header{flex-direction:column;margin-bottom:4px;display:flex}article .list{border-top:0}article .row:last-child{border-bottom:0}.record{border-top:0}.record dl{grid-template-columns:max-content 1fr;gap:2px 14px;margin:12px 0;font-size:.88rem;display:grid}.record dt{color:var(--muted);text-transform:capitalize}.record dd{margin:0}.acts{margin:12px 0 4px}.signal{padding:14px 0 6px}.signal h3{font:600 1.15rem/1.3 var(--font-display);margin:4px 0 6px}.signal .meta{color:var(--muted);text-transform:uppercase;letter-spacing:.04em;flex-wrap:wrap;align-items:center;gap:4px 10px;font-size:.78rem;display:flex}.stage{border:1px solid var(--accent);color:var(--accent);text-transform:none;letter-spacing:0;border-radius:999px;padding:0 8px}.steps{margin:8px 0 0;padding:0;list-style:none}.steps li{border-top:1px solid var(--line);grid-template-columns:7.5em 1fr;gap:10px;padding:5px 0;font-size:.9rem;display:grid}.steps time{color:var(--muted);font-variant-numeric:tabular-nums}.doc iframe{border:1px solid var(--line);background:#fff;width:100%;height:80vh;margin-top:12px}.taste{margin-bottom:20px}.pills{flex-wrap:wrap;gap:6px;margin:6px 0;display:flex}.pills li{border:1px solid var(--line);border-radius:var(--radius);align-items:center;gap:2px;padding:0 0 0 8px;font-size:.85rem;display:inline-flex}.pills li.no{color:var(--muted);text-decoration:line-through}.pills button{color:var(--muted);border:0;padding:0 8px}.knobs{grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px;margin:12px 0;display:grid}.knob{gap:2px;font-size:.85rem;display:grid}.knob span{justify-content:space-between;font-weight:600;display:flex}.knob input{width:100%;accent-color:var(--accent)}body.wide{--measure:1200px}.mapbar input{border:1px solid var(--line);border-radius:var(--radius);background:0 0;width:100%;margin-bottom:8px;padding:8px 10px}.mapwrap{grid-template-columns:minmax(0,1fr) minmax(0,1fr);align-items:start;gap:16px;display:grid}@media (width<=760px){.mapwrap{grid-template-columns:1fr}}svg.map{width:100%;height:auto;display:block;position:sticky;top:70px}svg.map path{stroke:var(--edge);stroke-width:.8px;cursor:pointer}svg.map path:hover,svg.map path:focus{stroke:var(--accent);stroke-width:2px;outline:none}svg.map path.near{stroke:var(--accent);stroke-width:2px;stroke-dasharray:4 3}svg.map path.on{stroke:var(--fg);stroke-width:2.6px}.h0{fill:var(--bg);background:var(--bg)}.h1{fill:color-mix(in srgb, var(--hot) 18%, var(--bg));background:color-mix(in srgb, var(--hot) 18%, var(--bg))}.h2{fill:color-mix(in srgb, var(--hot) 36%, var(--bg));background:color-mix(in srgb, var(--hot) 36%, var(--bg))}.h3{fill:color-mix(in srgb, var(--hot) 56%, var(--bg));background:color-mix(in srgb, var(--hot) 56%, var(--bg))}.h4{fill:color-mix(in srgb, var(--hot) 78%, var(--bg));background:color-mix(in srgb, var(--hot) 78%, var(--bg))}.h5{fill:var(--hot);background:var(--hot)}.sw{border:1px solid var(--edge);vertical-align:-1px;width:14px;height:12px;display:inline-block}.panel h3{margin:4px 0}.panel .chips{margin:6px 0}.panel .list{margin-top:8px}footer{color:var(--muted);padding-block:0 32px;font-size:.8rem}</style>
12
12
  <link rel="stylesheet" href="./theme.css">
13
13
  <header class="bar"><div>
14
14
  <a id="home" href="#/"><span id="name">Fangorn app</span></a>
@@ -12,7 +12,7 @@
12
12
  // Pure functions, so `node tools.js` checks them with no browser and no network.
13
13
 
14
14
  import { bestPassage, cosine, lexScore, norm, zFloor } from "../core/rank.js";
15
- import { briefOf, collections, linkOf, subtitleOf, titleOf, typeOf, values } from "../core/roles.js";
15
+ import { briefOf, collections, linkOf, pick, subtitleOf, titleOf, typeOf, values } from "../core/roles.js";
16
16
 
17
17
  /** Fields that are plumbing rather than content — never worth faceting or
18
18
  * showing as "what this corpus holds". */
@@ -214,6 +214,50 @@ export function neighbors(rows, id, roles, { limit = 10, fields } = {}) {
214
214
  };
215
215
  }
216
216
 
217
+ /**
218
+ * One thing followed across rows: a matter a committee took up and then the council, as a
219
+ * thread whose steps are in order. Rows group by the declared `thread` role (view.js wrote
220
+ * each row's target onto it); the thread's head is the target row itself, when this corpus
221
+ * holds it. The thread that moved last comes first.
222
+ *
223
+ * A step is one occasion: rows sharing a date and a subtitle (an agenda and its minutes)
224
+ * count once, keeping the one that says the most. `where` narrows the steps, `id` asks for
225
+ * one thread, and `min` (default 2) is how many steps make a thread: a matter taken up once
226
+ * so far is a record, not news.
227
+ *
228
+ * ponytail: the head is found by value (any field of a row that is not itself a step), since
229
+ * a fused view's identity role names one type's field. Index it if corpora get huge.
230
+ */
231
+ export function threads(rows, roles, { id, where, min = 2, limit = 20, offset = 0, fields = [] } = {}) {
232
+ const f = roles.thread;
233
+ if (!f) return { total: 0, offset, threads: [], error: "this corpus declares no thread relation" };
234
+ const dateOf = (r) => String(pick(r, roles.temporal) ?? "").slice(0, 10);
235
+ const groups = new Map();
236
+ for (const r of rows) {
237
+ const k = r[f] == null ? "" : String(r[f]);
238
+ if (!k || (id != null && k !== String(id)) || !matches(r, where)) continue;
239
+ const occ = `${dateOf(r)}|${subtitleOf(r, roles) ?? ""}`;
240
+ const g = groups.get(k) ?? groups.set(k, new Map()).get(k);
241
+ const had = g.get(occ);
242
+ if (!had || String(r.text ?? "").length > String(had.text ?? "").length) g.set(occ, r);
243
+ }
244
+ const heads = new Map();
245
+ for (const r of rows) {
246
+ if (r[f] != null) continue;
247
+ for (const v of Object.values(r)) if (typeof v === "string" && groups.has(v) && !heads.has(v)) heads.set(v, r);
248
+ }
249
+ const all = [...groups].map(([k, g]) => {
250
+ const steps = [...g.values()].sort((a, b) => dateOf(a).localeCompare(dateOf(b)));
251
+ const head = heads.get(k), last = steps.at(-1);
252
+ return {
253
+ id: k, title: titleOf(head ?? last, roles), url: linkOf(head ?? last, roles), first: dateOf(steps[0]), date: dateOf(last),
254
+ head: head ? brief(head, roles, fields) : null,
255
+ steps: steps.map((r) => ({ ...brief(r, roles, fields), date: dateOf(r) })),
256
+ };
257
+ }).filter((t) => t.steps.length >= min || id != null).sort((a, b) => b.date.localeCompare(a.date) || a.id.localeCompare(b.id));
258
+ return { total: all.length, offset, threads: all.slice(offset, offset + limit) };
259
+ }
260
+
217
261
  /** One row, whole. Deliberately the only verb that returns everything — the
218
262
  * others preview, so an agent chooses when to spend its context. */
219
263
  export function getRow(rows, id, roles) {
@@ -377,5 +421,28 @@ if (typeof process !== "undefined" && import.meta.url === `file://${process.argv
377
421
  }
378
422
  }
379
423
 
380
- console.log("tools.js self-check ok — coverage not schema, semantic/lexical modes + z-floor, list-ish facets + where, neighbours, whole-row fetch, previews truncate");
424
+ // threads: one matter across meetings, an agenda and its minutes as one step, headed by
425
+ // the matter's own row, the latest-moving first.
426
+ {
427
+ const rl = { title: ["heading", "title"], subtitle: ["meeting"], temporal: ["date"], tags: [], measures: [], thread: "part_of", externalUrl: {} };
428
+ const pool = [
429
+ { id: "1", heading: "Rezone Oak St", meeting: "Plan Commission", date: "2026-01-05", part_of: "p:7", text: "on the agenda" },
430
+ { id: "2", heading: "Rezone Oak St", meeting: "Plan Commission", date: "2026-01-05", part_of: "p:7", text: "recommended for approval 5-0" },
431
+ { id: "3", heading: "Rezone Oak St", meeting: "Village Board", date: "2026-01-20", part_of: "p:7", text: "adopted" },
432
+ { id: "4", heading: "Pool bid", meeting: "Village Board", date: "2026-03-01", part_of: "p:9" },
433
+ { id: "5", heading: "Pool bid", meeting: "Finance", date: "2026-02-20", part_of: "p:9", place: "A" },
434
+ { id: "6", heading: "Once", meeting: "Village Board", date: "2026-04-01", part_of: "p:11" },
435
+ { id: "m7", title: "Rezoning of Oak Street", matter_id: "p:7" },
436
+ ];
437
+ const t = threads(pool, rl);
438
+ if (t.threads.map((x) => x.id).join() !== "p:9,p:7") throw new Error(`latest-moving first, one step is not a thread: ${t.threads.map((x) => x.id)}`);
439
+ const oak = t.threads[1];
440
+ if (oak.steps.map((s) => s.id).join() !== "2,3" || oak.title !== "Rezoning of Oak Street" || oak.head?.id !== "m7")
441
+ throw new Error(`an agenda and its minutes are one step, the head titles the thread: ${JSON.stringify(oak)}`);
442
+ if (threads(pool, rl, { id: "p:11" }).threads[0]?.steps.length !== 1) throw new Error("asked for by id, any thread comes back");
443
+ if (threads(pool, rl, { where: { place: "A" }, min: 1 }).total !== 1) throw new Error("where narrows the steps");
444
+ if (!threads(pool, { ...rl, thread: null }).error) throw new Error("no thread role must say so");
445
+ }
446
+
447
+ console.log("tools.js self-check ok — coverage not schema, semantic/lexical modes + z-floor, list-ish facets + where, neighbours, whole-row fetch, previews truncate, threads");
381
448
  }
@@ -18,7 +18,7 @@
18
18
 
19
19
  import { configure, loadShard, trimView } from "../core/shard.js";
20
20
  import { rolesFrom, textOf } from "../core/roles.js";
21
- import { browse, facet, getRow, neighbors, search } from "./tools.js";
21
+ import { browse, facet, getRow, neighbors, search, threads } from "./tools.js";
22
22
  import { lexScore, rankDomains } from "../core/rank.js";
23
23
  import { existsSync } from "node:fs";
24
24
  import { EMBED_MODEL, embedQueryDirect, warmDirect } from "../core/embed.js";
@@ -150,6 +150,10 @@ export function viewTools(app, { fetchCatalog = fetch } = {}) {
150
150
  const [v] = await pick([view]);
151
151
  return { view: v.name, ...browse(await load(v), rolesOf(v.view), { limit, offset, sort, where: w }) };
152
152
  },
153
+ threads: async ({ view, id, limit = 20, offset = 0, where: w }) => {
154
+ const [v] = await pick([view]);
155
+ return { view: v.name, ...threads(await load(v), rolesOf(v.view), { id, limit, offset, where: w }) };
156
+ },
153
157
  };
154
158
 
155
159
  const n = app.name;
@@ -166,6 +170,8 @@ export function viewTools(app, { fetchCatalog = fetch } = {}) {
166
170
  inputSchema: { type: "object", properties: { field: str("Field name, e.g. from get"), views: VIEWS, limit: num("Max values (default 20)"), where: WHERE }, required: ["field"] } },
167
171
  { name: "browse", description: `List one view of ${n} without a query, with filters, sorting and paging.`,
168
172
  inputSchema: { type: "object", properties: { view: str("View name from describe"), limit: num("default 20"), offset: num("default 0"), sort: str("Field to sort by"), where: WHERE }, required: ["view"] } },
173
+ { name: "threads", description: `One thing followed across records of a ${n} view (a matter from committee to council): steps in date order, the latest-moving first. Views that declare no thread say so.`,
174
+ inputSchema: { type: "object", properties: { view: str("View name from describe"), id: str("One thread, by a step's thread field"), limit: num("default 20"), offset: num("default 0"), where: WHERE }, required: ["view"] } },
169
175
  ];
170
176
  // Records the app sells over x402 (its card's `paid`). Paid from this server's wallet,
171
177
  // never above FANGORN_MCP_MAX_PRICE, and checked against the sha256 the app published
package/src/core/roles.js CHANGED
@@ -71,7 +71,7 @@ export function rolesFrom(manifests = [], sample = []) {
71
71
  let launch = null, refers = null;
72
72
  const entityTypes = [];
73
73
  const gates = [];
74
- let identity = null;
74
+ let identity = null, thread = null;
75
75
 
76
76
  const push = (into, v) => { for (const x of arr(v)) if (x && !into.includes(x)) into.push(x); };
77
77
 
@@ -80,6 +80,10 @@ export function rolesFrom(manifests = [], sample = []) {
80
80
  push(title, rm.title); push(subtitle, rm.subtitle); push(tags, rm.tags);
81
81
  push(text, rm.text); push(measures, rm.measures); push(spatial, rm.spatial); push(media, rm.media); push(temporal, rm.temporal);
82
82
  identity ??= rm.identity ?? null;
83
+ // The relation that follows one thing across rows: an agenda item's `part_of` names
84
+ // its matter, so the items taken up at a committee and then the council are one thread.
85
+ // view.js writes the target's identity onto the row; a reader groups by it.
86
+ thread ??= rm.thread ?? null;
83
87
  // Where a row in THIS corpus points, in someone else's.
84
88
  //
85
89
  // The 21,131 subtitle rows in the archive bundle each carry the `path`
@@ -137,7 +141,7 @@ export function rolesFrom(manifests = [], sample = []) {
137
141
  guess(text, GUESS.text);
138
142
 
139
143
  return {
140
- title, subtitle, tags, text, measures, spatial, media, temporal, facets, identity,
144
+ title, subtitle, tags, text, measures, spatial, media, temporal, facets, identity, thread,
141
145
  labels, types, fieldLabels, externalUrl, actions, launch, refers, entityTypes,
142
146
  // What this view sells, if anything. A list because a fused view can pull
143
147
  // domains from several publishers and they price independently.
@@ -393,6 +397,7 @@ if (typeof process !== "undefined" && import.meta.url === `file://${process.argv
393
397
  if (!r.declared) throw new Error("a manifest with a role_map is declared, not sniffed");
394
398
  if (titleOf(row, r) !== "Cira's Red, White & Brew Bar") throw new Error("declared title lost");
395
399
  if (subtitleOf(row, r) !== "Bar & Grill") throw new Error("declared subtitle lost");
400
+ if (r.thread !== null || rolesFrom([{ role_map: { thread: "part_of" } }, places]).thread !== "part_of") throw new Error("thread is declared, never guessed");
396
401
  if (textOf(row, r) !== "Bar & Grill in Saint Germain, WI.") throw new Error("empty declared text fields must be skipped, not joined as blanks");
397
402
  // A declared reference into another publisher's corpus. All three parts or
398
403
  // none: a half-written join would silently point at nothing, and the caller
@@ -363,7 +363,7 @@ export async function ship(configPath, { crawl = true, deploy = true, dry = fals
363
363
  * `.ship/vectors.ndjson` keeps it cheap, so two recipes over the same data embed only the
364
364
  * text that differs.
365
365
  *
366
- * ponytail: reads every `volume_<n>_*.json` but edges in a source's --output-dir; the
366
+ * ponytail: reads every `volume_<n>_*.json` in a source's --output-dir; the
367
367
  * harness publishes only the stems its source declares, so a stale file left by an older
368
368
  * shape of a source is built here and not on chain. Clear the stage dir if that bites.
369
369
  */
@@ -386,15 +386,22 @@ export async function buildLocal(configPath, out, { crawl = false, only = null,
386
386
  const dir = resolve(cwd, expandEnv(s.command[at + 1]));
387
387
  const volume = s.command.includes("--volume") ? s.command[s.command.indexOf("--volume") + 1] : "1";
388
388
  const ns = (chain[`${owner}/${s.namespace}`] ??= { vertices: [], edges: [] });
389
+ const cidOf = new Map();
389
390
  for (const f of existsSync(dir) ? readdirSync(dir).sort() : []) {
390
391
  const m = new RegExp(`^volume_${volume}_(.+)\\.json$`).exec(f);
391
392
  if (!m || m[1] === "edges") continue;
392
393
  // The harness publishes {id: name, tag: entity, payload: fields}; the chain
393
394
  // addresses it by the payload's hash, and so does this.
394
- for (const n of JSON.parse(readFileSync(join(dir, f), "utf8"))) ns.vertices.push({
395
- cid: createHash("sha256").update(JSON.stringify(stable(n.fields))).digest("hex"),
396
- schemaId: n.fields?.entityType ?? m[1][0].toUpperCase() + m[1].slice(1), payload: n.fields });
395
+ for (const n of JSON.parse(readFileSync(join(dir, f), "utf8"))) {
396
+ const cid = createHash("sha256").update(JSON.stringify(stable(n.fields))).digest("hex");
397
+ cidOf.set(n.name, cid);
398
+ ns.vertices.push({ cid, schemaId: n.fields?.entityType ?? m[1][0].toUpperCase() + m[1].slice(1), payload: n.fields });
399
+ }
397
400
  }
401
+ // Edges name vertices by the harness's local ids; the chain returns them by CID.
402
+ const edges = join(dir, `volume_${volume}_edges.json`);
403
+ for (const e of existsSync(edges) ? JSON.parse(readFileSync(edges, "utf8")) : [])
404
+ if (cidOf.has(e.from) && cidOf.has(e.to)) ns.edges.push({ relation: e.rel, sourceCid: cidOf.get(e.from), targetCid: cidOf.get(e.to) });
398
405
  }
399
406
  // An empty stage is a crawl that did not happen (or a cache that was not restored), not
400
407
  // an app with no records; a view of it would grade as a failure for the wrong reason.
@@ -26,8 +26,9 @@
26
26
  // its `embedder` and this refuses to append to a view another encoder built.
27
27
  //
28
28
  // ponytail: flat records. quickbeam folds a record's graph neighbours into its text
29
- // and can fuse publishers by shared identity; that is the hosted tier's job. Edges
30
- // are not shipped here either (quickbeam's edges.json) until a reader needs them.
29
+ // and can fuse publishers by shared identity; that is the hosted tier's job. Edges are
30
+ // not shipped as edges (quickbeam's edges.json): a relation the schema declares is written
31
+ // onto its source row instead (`linkRelations`), which is all a serverless reader needs.
31
32
 
32
33
  import { gunzipSync, gzipSync } from "node:zlib";
33
34
  import { createHash } from "node:crypto";
@@ -38,7 +39,7 @@ import { coverage as fitCoverage } from "../market/reactions.js";
38
39
  export const SCHEMA_NAMESPACE = "fangorn.schema";
39
40
  export const TYPE_TAG = "fangorn.type.v1";
40
41
  export const EMBEDDER = { runtime: "transformers.js", model: EMBED_MODEL, dtype: EMBED_DTYPE };
41
- const SINGULAR = ["identity", "title", "subtitle", "temporal", "spatial", "media"];
42
+ const SINGULAR = ["identity", "title", "subtitle", "temporal", "spatial", "media", "thread"];
42
43
  const MULTI = ["tags", "measures", "relations", "text"];
43
44
  const ZERO = "0x0000000000000000000000000000000000000000";
44
45
  // Refit coverage once a domain has grown this many times over what it was fit on.
@@ -120,6 +121,23 @@ export function domainSpec(schema, tags) {
120
121
  return { description: decls.map((d) => d.description).filter(Boolean).join(" "), role_map, presentation };
121
122
  }
122
123
 
124
+ /**
125
+ * Each declared relation, onto the row it starts from: an agenda item with a `part_of` edge
126
+ * to a matter carries `part_of: <the matter's identity>`, so a reader groups rows by it
127
+ * without walking the graph. Undeclared edges are left alone (the schema is the contract).
128
+ * `records` maps CID → fields and is changed in place.
129
+ * ponytail: one target per relation and row (the last edge wins); a list when a relation
130
+ * needs many.
131
+ */
132
+ export function linkRelations(records, edges, schema) {
133
+ for (const e of edges ?? []) {
134
+ const from = records.get(e.sourceCid), to = records.get(e.targetCid);
135
+ if (!from || !to || !schema.relations.some((r) => r.rel === e.relation && r.from === from.entityType && r.to === to.entityType)) continue;
136
+ const id = schema.types[to.entityType]?.role_map?.identity;
137
+ from[e.relation] = String((id && to[id]) ?? e.targetCid);
138
+ }
139
+ }
140
+
123
141
  // ── a client that only reads ──────────────────────────────────────────────────
124
142
 
125
143
  /** An SDK client for reading commits: a throwaway key (nothing signs) and no upload
@@ -234,6 +252,7 @@ async function publishDomain({ fangorn, schema, publisher, ns, domain, dir, reba
234
252
  const { contents } = await fangorn.readNamespace(publisher, ns);
235
253
  const records = new Map((contents.vertices ?? []).filter((v) => v.payload && typeof v.payload === "object")
236
254
  .map((v) => [v.cid, { ...v.payload, entityType: v.schemaId }]));
255
+ linkRelations(records, contents.edges, schema);
237
256
 
238
257
  if (rebake) rmSync(dir, { recursive: true, force: true }); // old shards would stay reachable
239
258
  const had = loadDomain(dir);
@@ -248,6 +267,11 @@ async function publishDomain({ fangorn, schema, publisher, ns, domain, dir, reba
248
267
  const live = (id) => had.rows.has(id) && !dead.has(id);
249
268
  const fresh = [...records.keys()].filter((id) => !live(id));
250
269
  const gone = [...had.rows.keys()].filter((id) => live(id) && !records.has(id));
270
+ // A row already served whose links changed (its matter came later, or moved): new fields,
271
+ // the same vector, so no embedding, only a rewrite.
272
+ const relinked = [...had.rows.keys()].filter((id) => live(id) && records.has(id)
273
+ && JSON.stringify(had.rows.get(id).fields) !== JSON.stringify(records.get(id)));
274
+ for (const id of relinked) had.rows.get(id).fields = records.get(id);
251
275
 
252
276
  // Roles per type: declared exactly, else guessed (and said so).
253
277
  const byType = new Map();
@@ -271,7 +295,7 @@ async function publishDomain({ fangorn, schema, publisher, ns, domain, dir, reba
271
295
  if (Math.floor((n + ids.length) / 1000) > Math.floor(n / 1000)) log(`${domain}: embedded ${n + ids.length}/${fresh.length}`);
272
296
  n += ids.length;
273
297
  }
274
- if (!fresh.length && !gone.length && had.manifest && !rebake) {
298
+ if (!fresh.length && !gone.length && !relinked.length && had.manifest && !rebake) {
275
299
  applySpec(dir, had.manifest, schema, had.rows, dead); // a schema change alone still lands
276
300
  return { added: 0, removed: 0 };
277
301
  }
@@ -297,11 +321,11 @@ async function publishDomain({ fangorn, schema, publisher, ns, domain, dir, reba
297
321
  }
298
322
  for (const id of gone) dead.add(id);
299
323
  // A tombstone hides a row from readers, but its shard still serves the bytes. A record
300
- // retracted for what it said (a name, an address) has to be gone, so any removal
301
- // rewrites the live rows into one shard and the old files are deleted. No re-embedding:
302
- // the vectors are already in the rows.
324
+ // retracted for what it said (a name, an address) has to be gone, so any removal (or a
325
+ // relink, whose rows' fields changed) rewrites the live rows into one shard and the old
326
+ // files are deleted. No re-embedding: the vectors are already in the rows.
303
327
  // ponytail: rewrites the whole domain on every removal; compact past a threshold if domains get big.
304
- if (dead.size) {
328
+ if (dead.size || relinked.length) {
305
329
  for (const s of manifest.shards) rmSync(`${dir}/shards/${s.file}`, { force: true });
306
330
  manifest.shards = [];
307
331
  for (const id of dead) had.rows.delete(id);
@@ -311,8 +335,8 @@ async function publishDomain({ fangorn, schema, publisher, ns, domain, dir, reba
311
335
  manifest.tombstones = [...dead].sort();
312
336
  manifest.created_at = Math.floor(Date.now() / 1000);
313
337
  applySpec(dir, manifest, schema, had.rows, dead, rolesOf);
314
- log(`${domain}: +${fresh.length} embedded, -${gone.length} removed, ${manifest.count} live`);
315
- return { added: fresh.length, removed: gone.length };
338
+ log(`${domain}: +${fresh.length} embedded, -${gone.length} removed, ${relinked.length} relinked, ${manifest.count} live`);
339
+ return { added: fresh.length, removed: gone.length, ...(relinked.length ? { relinked: relinked.length } : {}) };
316
340
  }
317
341
 
318
342
  /** Counts, types, roles, description, presentation and coverage from what the domain now holds. */
@@ -457,5 +481,31 @@ if (typeof process !== "undefined" && import.meta.url === `file://${process.argv
457
481
  { added: 2, removed: 0 }, "--rebake starts the domain over");
458
482
  eq(readdirSync(`${out}/cdn/domains/${d}/shards`).length, 1, "and leaves no orphaned shards");
459
483
  rmSync(out, { recursive: true, force: true });
460
- console.log("view.js self-check ok — domain names and embedded text match quickbeam's, the schema is applied, a second run embeds nothing, additions ship as delta shards, removals compact the domain, the real reader sees exactly the live records by the publisher's id, and another encoder's view is refused");
484
+
485
+ // Relations: a declared edge lands on its source row as the target's identity; an
486
+ // undeclared one does not; a link that arrives later rewrites the row without embedding it.
487
+ {
488
+ const T = { tag: "civic.item.v1" }, M = { tag: "civic.matter.v1" };
489
+ const rel = { [`${OWNER}/${SCHEMA_NAMESPACE}`]: { vertices: [
490
+ { cid: "ti", schemaId: TYPE_TAG, payload: { ...T, role_map: { identity: "item_id", title: "heading", thread: "part_of" } } },
491
+ { cid: "tm", schemaId: TYPE_TAG, payload: { ...M, role_map: { identity: "matter_id", title: "title" } } }],
492
+ edges: [{ relation: "part_of", sourceCid: "ti", targetCid: "tm" }] },
493
+ [`${PUB}/civic`]: { vertices: [
494
+ { cid: "i1", schemaId: T.tag, payload: { item_id: "a#1", heading: "Rezone, committee" } },
495
+ { cid: "i2", schemaId: T.tag, payload: { item_id: "b#4", heading: "Rezone, council" } },
496
+ { cid: "m1", schemaId: M.tag, payload: { matter_id: "plover:7", title: "Rezone" } }],
497
+ edges: [{ relation: "part_of", sourceCid: "i1", targetCid: "m1" }, { relation: "cites", sourceCid: "i2", targetCid: "m1" }] } };
498
+ const o = mkdtempSync(`${tmpdir()}/view-`), dr = domainFor(APP, PUB, "civic");
499
+ const go = () => publishView({ fangorn: localFangorn(rel, { appId: APP, owner: OWNER }), app: APP, namespaces: ["civic"], out: o, embed, log: () => {} });
500
+ const parts = () => Object.fromEntries([...loadDomain(`${o}/cdn/domains/${dr}`).rows.values()].map((r) => [r.fields.heading ?? r.fields.title, r.fields.part_of ?? r.fields.cites ?? null]));
501
+ await go();
502
+ eq(parts(), { "Rezone, committee": "plover:7", "Rezone, council": null, Rezone: null }, "a declared relation carries the target's identity; an undeclared one is not written");
503
+ eq(readJson(`${o}/cdn/domains/${dr}/manifest`).role_map.thread, "part_of", "the thread role reaches the manifest");
504
+ rel[`${PUB}/civic`].edges.push({ relation: "part_of", sourceCid: "i2", targetCid: "m1" });
505
+ const before = embedded.length;
506
+ eq((await go())[dr], { added: 0, removed: 0, relinked: 1 }, "a link that comes later relinks the row");
507
+ eq([parts()["Rezone, council"], embedded.length - before], ["plover:7", 0], "without embedding it again");
508
+ rmSync(o, { recursive: true, force: true });
509
+ }
510
+ console.log("view.js self-check ok — domain names and embedded text match quickbeam's, the schema is applied, a second run embeds nothing, additions ship as delta shards, removals compact the domain, the real reader sees exactly the live records by the publisher's id, and another encoder's view is refused, and declared relations land on their source rows");
461
511
  }
@@ -1,8 +0,0 @@
1
- const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./transformers.web-CVP3pJOS.js","./rolldown-runtime-BcKkbAw3.js","./preload-helper-uBIymjUX.js","./__vite-browser-external-CitKSmYk.js","./roles-YDRjv_PY.js","./roles-C9V_uDr_.js","./wallet-DMLc-ukR.js","./defineChain-CiuCXUZW.js","./chain-DM3oH6Px.js","./utils-BkR7EQjg.js","./formatter-CVbp4SlJ.js","./baseSepolia-DX-qGsik.js","./serializeAuthorizationList-CX8hGL56.js","./sha2-BU_QyRTx.js"])))=>i.map(i=>d[i]);
2
- import{s as e}from"./rolldown-runtime-BcKkbAw3.js";import{t}from"./preload-helper-uBIymjUX.js";import{c as n,d as r,f as i,g as a,h as o,m as s,n as c,p as l,r as u,s as d}from"./roles-C9V_uDr_.js";(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),t.credentials=e.crossOrigin===`use-credentials`?`include`:e.crossOrigin===`anonymous`?`omit`:`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var f=`nomic-ai/nomic-embed-text-v1.5`,p=256,m={}.WESTMARCH_EMBED_DTYPE??`q8`,h=null,g=()=>h??=(async()=>{let{pipeline:e}=await t(async()=>{let{pipeline:e}=await import(`./transformers.web-CVP3pJOS.js`);return{pipeline:e}},__vite__mapDeps([0,1,2]),import.meta.url),n={}.WESTMARCH_EMBED_DEVICE;return e(`feature-extraction`,f,{dtype:m,...n&&{device:n}})})();function _(e,t=p){let n=e.length,r=0;for(let t=0;t<n;t++)r+=e[t];r/=n;let i=0;for(let t=0;t<n;t++)i+=(e[t]-r)**2;let a=Math.sqrt(i/n+1e-5),o=Array(Math.min(t,n));for(let t=0;t<o.length;t++)o[t]=(e[t]-r)/a;let s=0;for(let e of o)s+=e*e;if(s=Math.sqrt(s),s)for(let e=0;e<o.length;e++)o[e]/=s;return o}async function v(e){return _((await(await g())(`search_query: ${e}`,{pooling:`mean`,normalize:!1})).data)}var y=()=>typeof window<`u`&&typeof Worker<`u`,b=null,x=0,S=new Map;function ee(){return b||(b=new Worker(new URL(new URL(`embed.worker-ugGhEqCV.js`,import.meta.url).href,``+import.meta.url),{type:`module`}),b.onmessage=({data:e})=>{let t=S.get(e.id);t&&(S.delete(e.id),e.error?t.reject(Error(e.error)):t.resolve(e.vec))},b.onerror=e=>{for(let t of S.values())t.reject(Error(`embed worker: ${e?.message??`failed to start`}`));S.clear(),b=null},b)}var te=(e,t)=>new Promise((n,r)=>{let i=++x;S.set(i,{resolve:n,reject:r}),ee().postMessage({id:i,kind:e,text:t})}),ne=e=>y()?te(`query`,e):v(e);function C(e){let t=new Uint8Array(e.length);for(let n=0;n<e.length;n++){let r=Math.round(Math.max(-1,Math.min(1,e[n]))*127);t[n]=r<0?r+256:r}let n=``;for(let e of t)n+=String.fromCharCode(e);return btoa(n)}function w(e){try{let t=atob(e),n=new(globalThis.Float16Array??Float32Array)(t.length);for(let e=0;e<t.length;e++){let r=t.charCodeAt(e);n[e]=(r>127?r-256:r)/127}return n.length?n:null}catch{return null}}function re(e){let t=new Uint8Array(Math.ceil(e.length/8));for(let n=0;n<e.length;n++)e[n]>=0&&(t[n>>3]|=128>>(n&7));let n=``;for(let e of t)n+=String.fromCharCode(e);return btoa(n)}function ie(e){try{let t=atob(e.padEnd(Math.ceil(e.length/4)*4,`=`)),n=new Float32Array(t.length*8);for(let e=0;e<n.length;e++)n[e]=t.charCodeAt(e>>3)>>7-(e&7)&1?1:-1;return n.length?n:null}catch{return null}}if(typeof process<`u`&&import.meta.url===`file://${process.argv[1]}`&&process.argv[1].endsWith(`/embed.js`)){let e=_(Array.from({length:768},(e,t)=>Math.sin(t))),t=0;for(let n of e)t+=n*n;if(Math.abs(Math.sqrt(t)-1)>1e-6)throw Error(`matryoshka must L2-normalize`);if(e.length!==p)throw Error(`matryoshka must truncate to ${p}`);let n=w(C(e));if(n.length!==e.length)throw Error(`pack/unpack changed the dimension`);let r=0,i=0;for(let t=0;t<e.length;t++)r+=e[t]*n[t],i+=n[t]*n[t];let a=r/Math.sqrt(i);if(a<.999)throw Error(`int8 round-trip cosine ${a} — too lossy to rank with`);let o=w(C([-1,-.5,0,.5,1]));if(o[0]>-.99||o[1]>-.49||o[1]<-.51)throw Error(`negative components corrupted: ${[...o]}`);if(w(`!!!not base64!!!`)!==null)throw Error(`malformed vector must return null, not throw`);let s=re(e);if(s.length>48)throw Error(`sign bits must stay link-sized, got ${s.length} chars`);let c=ie(s);if(c.length!==e.length)throw Error(`sign round-trip changed the dimension`);for(let t=0;t<e.length;t++)if(Math.sign(c[t])!==(e[t]<0?-1:1))throw Error(`sign flipped at ${t}`);if(re(c)!==s)throw Error(`sign packing must be idempotent`);if(ie(`!!!not base64!!!`)!==null)throw Error(`malformed bits must return null, not throw`);console.log(`embed.js self-check ok — matryoshka norm, int8 round-trip, negatives, malformed input, sign bits`)}var T=(e,t,n)=>{let r=0;for(let n=0;n<t.length&&n<e.vector.length;n++)r+=e.vector[n]*t[n];return r/(e.norm*n)},E=e=>{let t=0;for(let n of e)t+=n*n;return Math.sqrt(t)||1};function ae(e,t=1){if(e.length<3)return-1/0;let n=e.reduce((e,t)=>e+t,0)/e.length,r=Math.sqrt(e.reduce((e,t)=>e+(t-n)**2,0)/e.length);return r>1e-6?n+t*r:-1/0}function oe(e,{limit:t=10,lambda:n=.7,key:r=`s`}={}){if(n>=1||e.length<=1)return e.slice(0,t);let i=e.slice(0,Math.max(t*6,30)),a=[];for(;a.length<t&&i.length;){let e=0,t=-1/0;for(let o=0;o<i.length;o++){let s=0;for(let e of a){if(!i[o].row?.vector||!e.row?.vector)continue;let t=T(i[o].row,e.row.vector,e.row.norm);t>s&&(s=t)}let c=n*i[o][r]-(1-n)*s;c>t&&(t=c,e=o)}a.push(i.splice(e,1)[0])}return a}var se=e=>e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`),ce=e=>RegExp(`\\b${se(e)}\\b`);function D(e,t){let n=String(e.text??``).toLowerCase();if(!n)return 0;let r=t.split(/\s+/).filter(Boolean);if(!r.length)return 0;let i=0;for(let e of r)ce(e).test(n)&&i++;if(!i)return 0;let a=n.indexOf(t);return Math.min(1,.5*(i/r.length)+(ce(t).test(n)?.35:0)+(a>=0?.15*(1-a/n.length):0))}function le(e,t){let n=e[0],r=0;for(let i of e){let e=D({text:i.text},t);e>r&&(r=e,n=i)}return n}function ue(e=[],t){let n=E(t);return e.map(e=>{let r=e.coverage;if(!r?.vectors?.length)return{...e,affinity:null};let i=-1;for(let e of r.vectors){let r=0,a=0;for(let n=0;n<e.length&&n<t.length;n++)r+=e[n]*t[n],a+=e[n]*e[n];let o=r/((Math.sqrt(a)||1)*n);o>i&&(i=o)}return{...e,affinity:i}}).sort((e,t)=>(t.affinity??-1/0)-(e.affinity??-1/0))}if(typeof process<`u`&&import.meta.url===`file://${process.argv[1]}`&&process.argv[1].endsWith(`/rank.js`)){if(D({text:`intoxicated education`},`cat`)!==0)throw Error(`substring matched — word boundaries lost`);if(D({text:`a cat sat`},`cat`)<=0)throw Error(`whole word must match`);let e=D({text:`cold war submarines`},`cold war`),t=D({text:`cold nights, the war ended`},`cold war`);if(!(e>t))throw Error(`phrase bonus lost: ${e} vs ${t}`);if(D({text:`what is c++`},`c++`)===void 0)throw Error(`must not throw on regex metachars`);if(Math.abs(T({vector:[1,0,0,0],norm:1},[1,0],1)-1)>1e-9)throw Error(`cosine must truncate to the query`);if(ae([.5,.5,.5,.5])!==-1/0)throw Error(`no spread must admit everything, not nothing`);if(ae([.4,.4])!==-1/0)throw Error(`too few rows to have a distribution`);let n=ae([.4,.41,.42,.9]);if(!(.9>=n&&.42<n))throw Error(`floor did not separate the outlier: ${n}`);let r=[{text:`opening titles`},{text:`the submarine dives`}];if(le(r,`submarine`).text!==`the submarine dives`)throw Error(`wrong passage quoted`);if(le(r,`nothing here`).text!==`opening titles`)throw Error(`must fall back to the first passage`);{let e=(e,t)=>({vector:[e,t],norm:Math.sqrt(e*e+t*t)||1}),t=[{row:{id:`ep1`,...e(1,0)},s:.9},{row:{id:`ep2`,...e(1,.01)},s:.89},{row:{id:`ep3`,...e(1,.02)},s:.88},{row:{id:`other`,...e(0,1)},s:.7}],n=oe(t,{limit:2,lambda:1}).map(e=>e.row.id);if(n.join()!==`ep1,ep2`)throw Error(`lambda 1 must be the old behaviour: ${n}`);let r=oe(t,{limit:2,lambda:.7}).map(e=>e.row.id);if(r.join()!==`ep1,other`)throw Error(`MMR must not return the same episode twice: ${r}`);if(oe(t,{limit:4,lambda:.5})[0].row.id!==`ep1`)throw Error(`the top hit must survive`);if(oe([{row:{id:`a`},s:1}],{limit:3}).length!==1)throw Error(`vectorless rows must pass through`)}let i=ue([{name:`old`,coverage:null},{name:`far`,coverage:{vectors:[[0,1]]}},{name:`near`,coverage:{vectors:[[1,0]]}}],[1,0]);if(i.map(e=>e.name).join(`,`)!==`near,far,old`)throw Error(`domains misranked: ${i.map(e=>e.name)}`);if(i[2].affinity!==null)throw Error(`an unmeasured domain must be null, not 0`);console.log(`rank.js self-check ok — word boundaries, matryoshka cosine, z-floor, MMR drops near-duplicates without moving the top hit, passage pick, domain affinity`)}var de=null,fe=null,O=null,pe=new Map,me=new Map,he=new Map,ge=e=>[e.name,e.path,e.desc].filter(Boolean).join(` `),_e=null,ve=null;function k({resolveView:e,rowText:t,onManifests:n}={}){e&&(_e=e),t&&(ge=t),n&&(ve=n)}var ye=()=>{pe.clear(),me.clear(),he.clear(),de=null,fe=null,O=null};function be(e){fe=e?Se(e):null}var xe=()=>fe,Se=e=>String(e).replace(/\/+$/,``).replace(/\/(stream|cdn)$/,``);function Ce(){return _e?de??=Promise.resolve().then(()=>_e()).then(e=>e?Se(e):null).catch(e=>{throw de=null,e}):Promise.resolve(null)}function we(e){let t=(Array.isArray(e)?e:String(e??``).split(`,`)).map(e=>String(e).trim()).filter(Boolean);O=t.length?t:null}var Te=e=>!O||O.some(t=>e===t||e.split(`/`).pop()===t||e.startsWith(t)),Ee=`no quickbeam view configured — pass a view base, or wire one with configure({ resolveView })`;async function A(e,t){let n=e??fe??await Ce();if(!n)throw Error(Ee);let r=me.get(n);r||me.set(n,r=new Set),t&&r.add(t);let i=pe.get(n);if(i)return i;let a=Pe(n,e=>{for(let t of r)try{t(e)}catch{}}).finally(()=>me.delete(n)).catch(e=>{throw pe.delete(n),e});return pe.set(n,a),a}async function De(e){let t=await fetch(e);if(!t.ok)throw Error(`shard: HTTP ${t.status} for ${e}`);return Oe(new Uint8Array(await t.arrayBuffer()))}var Oe=async e=>e[0]===31&&e[1]===139?await new Response(new Blob([e]).stream().pipeThrough(new DecompressionStream(`gzip`))).text():new TextDecoder().decode(e);function ke(e,t){let n=e.fields??{},r=typeof e.v==`string`?w(e.v):Array.isArray(e.embedding)?e.embedding:typeof n.embed?.vec==`string`?w(n.embed.vec):null,i=0;if(r)for(let e of r)i+=e*e;return{id:e.track_id,owner:e.owner,...n,text:ge(n,t),vector:r,norm:Math.sqrt(i)||1}}async function Ae(e){let t=0;for(let n of e)t+=n.length;let n=new Uint8Array(t),r=0;for(let t of e)n.set(t,r),r+=t.length;return[...new Uint8Array(await crypto.subtle.digest(`SHA-256`,n))].map(e=>e.toString(16).padStart(2,`0`)).join(``)}async function je(e,t,n,r){if(!t)return;if(!r){console.warn(`shard: cannot verify ${e} — the transport decoded it (Content-Encoding), and the manifest digest is over the gzipped bytes`);return}let i=await Ae(n);if(i!==t)throw Error(`shard: ${e} does not match the digest in its manifest (expected ${t.slice(0,12)}…, got ${i.slice(0,12)}…)`)}async function Me(e,t,n){let r=await fetch(e);if(!r.ok)throw Error(`shard: HTTP ${r.status} for ${e}`);if(!r.body){let i=new Uint8Array(await r.arrayBuffer());await je(e,n,[i],i[0]===31&&i[1]===139);for(let e of(await Oe(i)).split(`
3
- `))t(e);return}let i=r.body.getReader(),a=await i.read(),o=a.value??new Uint8Array,s=n?[]:null,c=new ReadableStream({start(e){o.length&&(s?.push(o),e.enqueue(o)),a.done&&e.close()},async pull(e){let{done:t,value:n}=await i.read();t?e.close():(s?.push(n),e.enqueue(n))},cancel(e){return i.cancel(e)}}),l=o[0]===31&&o[1]===139,u=l?c.pipeThrough(new DecompressionStream(`gzip`)):c,d=new TextDecoder,f=``;for(let r=u.getReader();;){let{done:i,value:a}=await r.read();f+=i?d.decode():d.decode(a,{stream:!0});let o=f.split(`
4
- `);f=i?``:o.pop();for(let e of o)t(e);if(i){f&&t(f),await je(e,n,s,l);return}}}var Ne=250;async function Pe(e,t){let n=`${e}/cdn`,{domains:r=[]}=JSON.parse(await De(`${n}/catalog`)),i=r.filter(e=>Te(e.name));O&&i.length!==r.length&&console.info(`shard: pulling ${i.length} of ${r.length} domains (allowlist: ${O.join(`,`)})`),O&&!i.length&&console.warn(`shard: allowlist ${O.join(`,`)} matched none of ${r.map(e=>e.name).join(`, `)} — nothing will load`),i.length||console.warn(`shard: quickbeam view ${n} has no baked domains yet — nothing to read until the watcher bakes one.`);let a=new Map,o=new Set,s=await Promise.all(i.map(async e=>{let t=`${n}/domains/${encodeURIComponent(e.name)}`,r=JSON.parse(await De(`${t}/manifest`));for(let e of r.tombstones??[])o.add(e);return{at:t,manifest:r,shards:r.shards??[]}})),c=s.map(e=>e.manifest);he.set(e,c);try{ve?.(c,e)}catch(e){console.warn(`shard: onManifests threw, continuing with the default rowText —`,e)}let l=Date.now(),u=e=>{!t||!e&&Date.now()-l<Ne||(t([...a.values()]),l=Date.now())};return await Promise.all(s.map(async({at:t,shards:n})=>{for(let r of n)await Me(`${t}/shards/${r.file}`,t=>{if(!t.trim())return;let n=ke(JSON.parse(t),e);o.has(n.id)||a.set(n.id,n),u()},r.sha256),u(!0)})),[...a.values()]}function Fe(e,t){let n=null,r=!1;return(async()=>{let i=t??fe??await Ce();if(!i||r)return;n=new EventSource(`${i}/stream`);let a=()=>{ye(),e()};n.addEventListener(`added`,a),n.addEventListener(`change`,a)})().catch(()=>{}),()=>{r=!0,n?.close()}}async function Ie(e,{url:t}={}){let n=t??fe??await Ce();if(!n)throw Error(Ee);let{domains:r=[]}=JSON.parse(await De(`${n}/cdn/catalog`));return ue(r,e)}if(typeof process<`u`&&import.meta.url===`file://${process.argv[1]}`&&process.argv[1].endsWith(`/shard.js`)){let{gzipSync:n}=await t(async()=>{let{gzipSync:t}=await import(`./__vite-browser-external-CitKSmYk.js`).then(t=>e(t.default,1));return{gzipSync:t}},__vite__mapDeps([3,1]),import.meta.url),r=console.warn,i=console.info;console.warn=console.info=()=>{};let a=(e,t,n={},r)=>({track_id:`${e}:${t}`,owner:e,...r?{embedding:r}:{},fields:{entityType:`file`,name:t.split(`/`).pop(),path:t,...n}}),o=e=>e.map(e=>JSON.stringify(e)).join(`
5
- `),s=`https://registry.test/q/qb_test`,c=e=>{let t=Buffer.from(e),n=Math.max(1,Math.floor(t.length/2));return{ok:!0,headers:new Headers,arrayBuffer:async()=>t,body:new ReadableStream({start(e){e.enqueue(new Uint8Array(t.subarray(0,n))),e.enqueue(new Uint8Array(t.subarray(n))),e.close()}})}},l=async e=>[...new Uint8Array(await crypto.subtle.digest(`SHA-256`,e))].map(e=>e.toString(16).padStart(2,`0`)).join(``),u=async(e,{tombstones:t={},gzipped:r=!0,keep:i=!1,coverage:a={},digest:u=!1,corrupt:d=!1}={})=>{let f=new Map;for(let[r,i]of Object.entries(e)){let e=i.map((e,t)=>`shard-000${t}-${r}.ndjson.gz`),a=new Map(e.map((e,t)=>[e,o(i[t])])),s=u?await Promise.all(e.map(async e=>l(n(Buffer.from(d?a.get(e)+`
6
- `:a.get(e)))))):e.map(()=>void 0);f.set(r,{manifest:JSON.stringify({name:r,shards:e.map((e,t)=>({file:e,...s[t]?{sha256:s[t]}:{}})),tombstones:t[r]??[]}),bodies:a})}globalThis.fetch=async e=>{let{pathname:t}=new URL(String(e),`https://app.test`);if(t.includes(`/stream/`))return{ok:!1,status:404};if(t.endsWith(`/cdn/catalog`))return c(JSON.stringify({domains:[...f.keys()].map(e=>({name:e,...a[e]?{coverage:a[e]}:{}}))}));let i=t.match(/\/cdn\/domains\/([^/]+)\/manifest$/);if(i)return f.has(i[1])?c(f.get(i[1]).manifest):{ok:!1,status:404};if(i=t.match(/\/cdn\/domains\/([^/]+)\/shards\/([^/]+)$/),i){let e=f.get(i[1])?.bodies.get(i[2]);return e===void 0?{ok:!1,status:404}:c(r?n(Buffer.from(e)):Buffer.from(e))}return{ok:!1,status:404}},i||ye(),k({resolveView:()=>`${s}/stream`})},d=async(e,t={})=>u({d0:e},{...t,tombstones:{d0:t.tombstones??[]}}),f=e=>e.map(e=>e.path).sort().join(`,`);if(await d([[a(`0xa`,`a.md`)]]),f(await A())!==`a.md`)throw Error(`resolveView never reached the loader`);if(await d([[a(`0xa`,`plain.md`)]],{gzipped:!1}),f(await A())!==`plain.md`)throw Error(`an already-decoded body must still parse`);if(await d([[a(`0xa`,`signed.md`)]],{digest:!0}),f(await A())!==`signed.md`)throw Error(`a shard matching its digest must load`);await d([[a(`0xa`,`tampered.md`)]],{digest:!0,corrupt:!0});let p=null;try{await A()}catch(e){p=e}if(!p)throw Error(`a shard that does not match its digest must fail the load`);if(!/does not match the digest/.test(p.message))throw Error(`wrong error: ${p.message}`);p=null;try{await A()}catch(e){p=e}if(!p)throw Error(`a failed verification must not leave the rows in the cache`);let m=``;if(console.warn=e=>{m+=e},await d([[a(`0xa`,`unverifiable.md`)]],{digest:!0,gzipped:!1}),f(await A())!==`unverifiable.md`)throw Error(`an unverifiable body must still load`);if(!/cannot verify/.test(m))throw Error(`an unverifiable shard must say so, not pass silently`);if(console.warn=()=>{},await u({aaa:[[a(`0xa`,`a.md`,{desc:`alpha`}),a(`0xa`,`gone.md`,{desc:`retracted`})]],bbb:[[a(`0xb`,`b.md`,{desc:`beta`})]]},{tombstones:{aaa:[`0xa:gone.md`]}}),f(await A())!==`a.md,b.md`)throw Error(`domains must merge and tombstones retract`);for(let e of[`bbb`,[`bbb`],`bb`])if(await u({aaa:[[a(`0xa`,`a.md`)]],bbb:[[a(`0xb`,`b.md`)]]}),we(e),f(await A())!==`b.md`)throw Error(`allowlist ${JSON.stringify(e)} did not narrow the load`);if(await u({aaa:[[a(`0xa`,`a.md`)]]}),we(null),f(await A())!==`a.md`)throw Error(`a null allowlist must pull everything`);await d([[a(`0xa`,`a.md`),a(`0xa`,`gone.md`)],[a(`0xa`,`b.md`)]],{tombstones:[`0xa:gone.md`]});{let e=[],t=await A(void 0,t=>e.push(t.length));if(e.length!==2||e[0]!==1||e[1]!==2)throw Error(`must emit per shard, got ${e}`);if(t.length!==2)throw Error(`the final rows must still be whole`);if(e.some(e=>e>2))throw Error(`a tombstoned row must never surface in a partial emit`)}await d([[a(`0xa`,`a.md`)],[a(`0xa`,`b.md`)]]);{let e=[],t=[],n=A(void 0,t=>e.push(t.length)),r=A(void 0,e=>t.push(e.length));if(await Promise.all([n,r]),!e.length)throw Error(`the starting caller must still receive partials`);if(!t.length)throw Error(`a caller that joins an in-flight load must receive partials too`)}if(ye(),k({resolveView:()=>`${s}/stream`}),globalThis.fetch=async()=>({ok:!1,status:500}),await A().then(()=>{throw Error(`a 500 must reject`)},()=>{}),await d([[a(`0xa`,`a.md`)]],{keep:!0}),f(await A())!==`a.md`)throw Error(`a failed load poisoned the cache — the next read must retry`);{let e=[];await u({d0:[[a(`0xa`,`a.md`,{blurb:`declared prose`})]]}),k({onManifests:t=>e.push(`manifests:${t[0]?.name}`),rowText:t=>(e.push(`rowText`),t.blurb??``)});let t=await A();if(e[0]!==`manifests:d0`)throw Error(`onManifests must fire first, got ${e}`);if(!e.includes(`rowText`))throw Error(`rowText never ran`);if(t[0].text!==`declared prose`)throw Error(`the manifest-derived rowText did not reach toRow`);if(ye(),k({resolveView:()=>`${s}/stream`,onManifests:()=>{throw Error(`boom`)}}),(await A()).length!==1)throw Error(`a throwing onManifests must not fail the load`);k({onManifests:()=>{}})}if(await d([[a(`0xa`,`ep1.md`,{cues:[{text:`the submarine dives`}]})]]),(await A())[0].text.includes(`submarine`))throw Error(`the default rowText must not know about cues`);if(k({rowText:e=>e.cues?.length?e.cues.map(e=>e.text).join(` `):[e.name,e.path,e.desc].filter(Boolean).join(` `)}),ye(),k({resolveView:()=>`${s}/stream`}),!(await A())[0].text.includes(`submarine`))throw Error(`a configured rowText must reach toRow`);{let e=new Set;if(k({rowText:(t,n)=>(e.add(n),t.name??``),onManifests:(t,n)=>{e.add(`m:${n}`)}}),ye(),k({resolveView:()=>`${s}/stream`}),await A(),!e.has(s))throw Error(`rowText must receive its trimmed view base, saw ${[...e]}`);if(!e.has(`m:${s}`))throw Error(`onManifests must receive its view`);k({rowText:e=>[e.name,e.path,e.desc].filter(Boolean).join(` `),onManifests:()=>{}})}await d([[a(`0xa`,`v.md`,{},[3,4]),a(`0xa`,`packed.md`,{embed:{vec:`fwA=`}}),a(`0xa`,`none.md`)]]);{let e=new Map((await A()).map(e=>[e.path,e]));if(Math.abs(e.get(`v.md`).norm-5)>1e-9)throw Error(`norm must be precomputed from the plain float array`);if(!e.get(`packed.md`).vector)throw Error(`the base64 int8 form a publisher commits must unpack`);if(e.get(`none.md`).vector!==null)throw Error(`a row with no vector must be null, and rank lexically`);if(e.get(`none.md`).norm!==1)throw Error(`a zero norm would divide search by zero`)}{await u({d0:[[{track_id:`p1`,owner:`0xa`,v:`AH+B`,fields:{entityType:`file`,name:`packed.md`,path:`packed.md`}},{track_id:`p2`,owner:`0xa`,embedding:[0,1,-1],fields:{entityType:`file`,name:`old.md`,path:`old.md`}}]]});let e=new Map((await A()).map(e=>[e.path,e])),t=e.get(`packed.md`).vector,n=e.get(`old.md`).vector;if(!t)throw Error("the bake's `v` key must unpack");for(let e=0;e<3;e++)if(Math.abs(t[e]-n[e])>1/127)throw Error(`packed and float forms disagree at ${e}: ${t[e]} vs ${n[e]}`);if(Math.abs(e.get(`packed.md`).norm-e.get(`old.md`).norm)>.02)throw Error(`norm must match across wire forms`)}await u({near:[[a(`0xa`,`a.md`)]],far:[[a(`0xb`,`b.md`)]],old:[[a(`0xc`,`c.md`)]]},{coverage:{near:{vectors:[[1,0]]},far:{vectors:[[0,1]]}}});{let e=await Ie([1,0]);if(e.map(e=>e.name).join(`,`)!==`near,far,old`)throw Error(`suggestDomains misranked: ${e.map(e=>e.name)}`)}await d([[a(`0xa`,`before.md`)]]);{let e=new Map,t=``,n=!1;globalThis.EventSource=class{constructor(e){t=e}addEventListener(t,n){e.set(t,n)}close(){n=!0}};let r=0,i=Fe(()=>{r++});if(await new Promise(e=>setTimeout(e,0)),t!==`${s}/stream`)throw Error(`watchShard must open the view's SSE stream, got ${t}`);if(e.has(`snapshot`))throw Error(`snapshot is the connect-time census, not a change`);if(f(await A())!==`before.md`)throw Error(`watchShard fixture never loaded`);if(await d([[a(`0xa`,`after.md`)]],{keep:!0}),e.get(`change`)(),r!==1)throw Error("a `change` event must reach the caller");if(f(await A())!==`after.md`)throw Error("a `change` must drop the cache so the next read re-pulls");if(i(),!n)throw Error(`the unsubscribe must close the stream`)}if(await u({d0:[[a(`0xa`,`home.md`)]],other:[[a(`0xb`,`guest.md`)]]}),await A(),be(`${s}/cdn`),xe()!==s)throw Error(`setView must trim to the base every route hangs off`);if(be(null),xe()!==null)throw Error(`setView(null) must go back to the app default`);console.warn=r,console.info=i,console.log(`shard.js self-check ok — resolveView, gzip sniff, shard digests, domain merge + tombstones + allowlist, streaming, late subscriber, cache poisoning, rowText, vectors, suggestDomains, watchShard`)}var Le=new Set([`text`,`vector`,`norm`,`id`,`owner`,`embed`]),Re=(e,t,n=[])=>{let r=c(e,t,n),i=d(e,t);return i?{...r,url:i}:r};function ze(e){let t=new Map,n=0,r=0;for(let i of e){i.vector&&(n++,r||=i.vector.length);for(let e of Object.keys(i))Le.has(e)||i[e]!=null&&i[e]!==``&&t.set(e,(t.get(e)??0)+1)}return{rows:e.length,withVectors:n,vectorDim:r,owners:[...new Set(e.map(e=>e.owner).filter(Boolean))],fields:[...t].sort((e,t)=>t[1]-e[1]).map(([t,n])=>({name:t,on:n,pct:Math.round(100*n/(e.length||1))}))}}var Be=.15,Ve=new WeakMap,He=(e,t)=>{let n=Ve.get(e);return n===void 0&&(n=[s(e,t),i(e,t)??``,...(t.tags??[]).flatMap(t=>a(e[t]??``)),e.text??``].join(` `),Ve.set(e,n)),n};function j(e,t,n,{qv:r=null,limit:i=10,fields:a,where:o,lexBoost:s=Be}={}){let c=t.trim().toLowerCase();if(!c)return[];let l=r?E(r):1;o&&(e=e.filter(e=>Ue(e,o)));let u=e.map(e=>r&&e.vector?{r:e,score:T(e,r,l)+(s?s*D({text:He(e,n)},c):0),mode:`semantic`}:{r:e,score:D({text:He(e,n)},c),mode:`lexical`}),d=u.filter(e=>e.mode===`semantic`).map(e=>e.score),f=Math.min(ae(d),d.length?Math.max(...d):1/0);return u.filter(e=>e.mode===`semantic`?e.score>=f:e.score>0).sort((e,t)=>t.score-e.score).slice(0,i).map(({r:e,score:t,mode:r})=>({...Re(e,n,a),score:Number(t.toFixed(4)),mode:r}))}var Ue=(e,t)=>!t||Object.entries(t).every(([t,n])=>a(e[t]??``).some(e=>e.toLowerCase()===String(n).toLowerCase()));function We(e,t,{limit:n=20,where:r}={}){let i=new Map,o=0;for(let n of e){if(!Ue(n,r))continue;let e=n[t];if(e==null||e===``){o++;continue}for(let t of a(e))i.set(t,(i.get(t)??0)+1)}let s=[...i].sort((e,t)=>t[1]-e[1]);return{field:t,distinct:i.size,missing:o,top:s.slice(0,n).map(([e,t])=>({value:e,count:t}))}}function M(e,t,{type:n,where:r,sort:i,desc:a=!0,limit:s=20,offset:c=0,fields:l}={}){let d=e.filter(e=>(!n||o(e)?.toLowerCase()===String(n).toLowerCase())&&Ue(e,r)),f=d.length;if(i){let e=e=>e==null||e===``?null:Number(e);d=[...d].sort((t,n)=>{let[r,o]=[t[i],n[i]];if((r==null||r===``)!=(o==null||o===``))return r==null||r===``?1:-1;let[s,c]=[e(r),e(o)],l=Number.isFinite(s)&&Number.isFinite(c)?s-c:String(r??``).localeCompare(String(o??``));return a?-l:l})}return{total:f,offset:c,returned:Math.min(s,Math.max(0,f-c)),collections:n?void 0:u(t,e).map(e=>({type:e.type,plural:e.plural,count:e.count})),rows:d.slice(c,c+s).map(e=>Re(e,t,l))}}function Ge(e,t,n,{limit:r=10,fields:i}={}){let a=e.find(e=>e.id===t);if(!a?.vector)return{seed:a?Re(a,n,i):null,near:[]};let o=a.norm;return{seed:Re(a,n,i),near:e.filter(e=>e.id!==t&&e.vector).map(e=>({r:e,score:T(e,a.vector,o)})).sort((e,t)=>t.score-e.score).slice(0,r).map(({r:e,score:t})=>({...Re(e,n,i),score:Number(t.toFixed(4))}))}}function Ke(e,t,n){if(!t)return null;let r=e.find(e=>e.id===t)??(n?.identity?e.find(e=>String(e[n.identity])===String(t)):null)??e.find(e=>e.path===t||e.name===t||s(e,n)===t);if(!r)return null;let{vector:i,norm:a,...o}=r;return{...o,hasVector:!!i,vectorDim:i?.length??0}}if(typeof process<`u`&&import.meta.url===`file://${process.argv[1]}`&&process.argv[1].endsWith(`/tools.js`)){let{rolesFrom:e}=await t(async()=>{let{rolesFrom:e}=await import(`./roles-YDRjv_PY.js`);return{rolesFrom:e}},__vite__mapDeps([4,5]),import.meta.url),n=(e,t,n)=>({id:e,owner:`0xa`,...t,text:[t.name,t.desc].filter(Boolean).join(` `),vector:n??null,norm:n?E(n):1}),r=[n(`1`,{name:`Duet S01E02`,series:`Duet`,year:`1987`,subject:`['Sitcom', 'Comedy']`,desc:`a forgotten fox sitcom`},[1,0,0]),n(`2`,{name:`Duet S01E03`,series:`Duet`,year:`1987`,subject:`['Sitcom']`,desc:`more of the same sitcom`},[.99,.1,0]),n(`3`,{name:`Atomic Cafe`,series:``,year:`1982`,subject:`['Documentary', 'Cold War']`,desc:`nuclear test footage`},[0,1,0]),n(`4`,{name:`untitled reel`,year:`1954`})],i=e([],r);if(i.title[0]!==`name`)throw Error(`fixture roles wrong: ${i.title}`);let o=ze(r);if(o.rows!==4||o.withVectors!==3||o.vectorDim!==3)throw Error(`describe lost the corpus shape`);let s=o.fields.find(e=>e.name===`series`);if(s.on!==2||s.pct!==50)throw Error(`empty strings must not count as coverage: ${JSON.stringify(s)}`);if(o.fields.some(e=>Le.has(e.name)))throw Error(`plumbing fields must not be offered as content`);if(o.owners.join()!==`0xa`)throw Error(`owners lost`);let c=j(r,`sitcom`,i,{qv:[1,0,0],limit:10});if(c[0].id!==`1`||c[0].title!==`Duet S01E02`)throw Error(`semantic: wrong top hit ${c[0]?.id}`);if(c[0].mode!==`semantic`)throw Error(`mode must be reported, not inferred`);if(c.some(e=>e.id===`3`))throw Error(`the z-floor must drop the unrelated row: ${JSON.stringify(c)}`);if(!j([r[0],r[1],r[2]],`sitcom`,i,{qv:[1,0,0]}).length)throw Error(`an exact match must never be filtered out by its own floor`);if(c=j(r,`nuclear`,i,{limit:10}),c[0]?.id!==`3`||c[0].mode!==`lexical`)throw Error(`must degrade to lexical, and say so`);if(j(r,` `,i).length)throw Error(`an empty query must return nothing, not everything`);if(j(r,`untitled reel`,i)[0]?.id!==`4`)throw Error(`a vectorless row must still be searchable lexically`);let l=We(r,`subject`);if(l.top[0].value!==`Sitcom`||l.top[0].count!==2)throw Error(`list-ish values must split: ${JSON.stringify(l.top)}`);if(l.missing!==1)throw Error(`a field absent from a row must be counted missing, not skipped silently`);if(We(r,`year`).distinct!==3)throw Error(`plain values must not be split`);if(a(`Sitcom,Comedy,Fox`).length!==3)throw Error(`a machine-joined list must split`);if(a(`Wilson, Michael G.`).length!==1)throw Error(`a comma followed by a space is prose, not a list`);if(a(`1987`).length!==1)throw Error(`a plain value must stay whole`);let u=We(r,`year`,{where:{subject:`Sitcom`}});if(u.distinct!==1||u.top[0].value!==`1987`)throw Error(`where must narrow the facet: ${JSON.stringify(u.top)}`);if(We(r,`year`,{where:{subject:`sitcom`}}).distinct!==1)throw Error(`where must match case-insensitively`);if(We(r,`year`,{where:{subject:`Sit`}}).distinct!==0)throw Error(`where must match whole values, not substrings`);let d=M(r,i,{limit:2});if(d.total!==4||d.rows.length!==2)throw Error(`browse must page: ${JSON.stringify(d)}`);if(d.collections.length!==0)throw Error(`a typeless corpus must report zero collections, not invent one`);if(M(r,i,{offset:3}).rows.length!==1)throw Error(`offset must page to the tail`);if(M(r,i,{offset:99}).rows.length!==0)throw Error(`an offset past the end must be empty, not wrapped`);let f=r.map(e=>({...e,entityType:e.id===`3`?`Film`:`Episode`})),p=M(f,i,{limit:1}).collections;if(p.map(e=>`${e.type}:${e.count}`).join()!==`Episode:3,Film:1`)throw Error(`collections must count and sort: ${JSON.stringify(p)}`);if(M(f,i,{type:`film`}).total!==1)throw Error(`type must match case-insensitively`);if(M(f,i,{type:`film`}).collections!==void 0)throw Error(`a typed browse is already inside a collection`);if(M(f,i,{type:`Episode`,where:{year:`1987`}}).total!==2)throw Error(`browse must honour where`);let m=[{id:`a`,rating:`9`},{id:`b`,rating:`10`},{id:`c`}],h=M(m,i,{sort:`rating`}).rows.map(e=>e.id);if(h.join()!==`b,a,c`)throw Error(`numeric sort + missing-last wrong: ${h}`);if(M(m,i,{sort:`rating`,desc:!1}).rows[0].id!==`a`)throw Error(`ascending sort ignored`);let g=Ge(r,`1`,i);if(g.near[0].id!==`2`)throw Error(`nearest neighbour missed the near-identical row`);if(g.near.some(e=>e.id===`1`))throw Error(`a row must not be its own neighbour`);if(g.near.some(e=>e.id===`4`))throw Error(`a vectorless row cannot be a neighbour`);if(Ge(r,`4`,i).near.length)throw Error(`a vectorless seed must return nothing, not throw`);if(Ge(r,`nope`,i).seed!==null)throw Error(`an unknown seed must be null, not undefined`);let _=Ke(r,`3`,i);if(_.name!==`Atomic Cafe`||!_.hasVector||_.vectorDim!==3)throw Error(`getRow lost the record`);if(`vector`in _||`norm`in _)throw Error(`the raw vector must never reach a tool result`);if(Ke(r,`nope`,i)!==null)throw Error(`an unknown id must be null`);let v=Re(n(`x`,{name:`n`,categories:`d`.repeat(500)}),e([{role_map:{title:`name`,tags:[`categories`]}}]));if(v.categories.length>200)throw Error(`brief must truncate a long field`);if(v.title!==`n`)throw Error(`brief must title from the declared field`);{let e=[{id:`a`,name:`cold war thriller`,year:1965,vector:[1,0],norm:1},{id:`b`,name:`cold war comedy`,year:1999,vector:[1,0],norm:1},{id:`c`,name:`beach party`,year:1964,vector:[0,1],norm:1},{id:`d`,name:`surf movie`,year:1966,vector:[0,1],norm:1}],t={title:[`name`],subtitle:[],tags:[],text:[`name`],measures:[],fields:[`year`]};if(!j(e,`cold war`,t,{qv:[1,0],limit:10}).some(e=>e.title===`cold war comedy`))throw Error(`unfiltered search must see every era`);let n=j(e,`cold war`,t,{qv:[1,0],limit:10,where:{year:1965}});if(n.length!==1||n[0].title!==`cold war thriller`)throw Error(`where must narrow a semantic search: ${JSON.stringify(n)}`);if(j(e,`cold war`,t,{qv:[0,1],limit:10,where:{year:1964}})[0]?.title!==`beach party`)throw Error(`the floor must be measured over the filtered rows`)}{let e=[{id:`a`,name:`cold war thriller`,vector:[1,0],norm:1}],t={title:[`name`],subtitle:[],tags:[],text:[`name`],measures:[],fields:[]},n=j(e,`cold war`,t,{qv:[1,0],limit:5});if(!Array.isArray(n))throw Error(`search must return a flat array, not {hits}`);if(n[0].score===void 0||n[0].mode===void 0)throw Error(`search rows carry score+mode`);for(let n of[void 0,null,``])if(Ke(e,n,t)!==null)throw Error(`getRow(${JSON.stringify(n)}) must be null`)}console.log(`tools.js self-check ok — coverage not schema, semantic/lexical modes + z-floor, list-ish facets + where, neighbours, whole-row fetch, previews truncate`)}var qe=e=>new Float32Array(e),Je=e=>new Float32Array(e),Ye=(e,t)=>{let n=new Float32Array(e.length);for(let r=0;r<e.length;r++)n[r]=e[r]+t[r];return n},Xe=(e,t)=>{let n=new Float32Array(e.length);for(let r=0;r<e.length;r++)n[r]=e[r]-t[r];return n},N=(e,t)=>{let n=new Float32Array(e.length);for(let r=0;r<e.length;r++)n[r]=e[r]*t;return n},Ze=(e,t)=>{let n=0;for(let r=0;r<e.length;r++)n+=e[r]*t[r];return n},Qe=e=>Math.sqrt(Ze(e,e)),$e=(e,t)=>Qe(Xe(e,t)),et=e=>{if(e.length===0)throw Error(`centroid: empty input`);let t=e[0].length,n=new Float32Array(t);for(let r of e)for(let e=0;e<t;e++)n[e]+=r[e];for(let r=0;r<t;r++)n[r]/=e.length;return n},tt=(e,t)=>{let n=Qe(t);if(n<1e-10)return Je(e);let r=N(t,1/n),i=Ze(e,r);return i<=0?Je(e):Xe(e,N(r,i))},P={d:768,alpha:.1,beta:.3,lambda_max:.4,sigma_base:.5,sigma_max:1,rho:.8,epsilon:1e-6,prior_k:1,pass_window:20,gamma_base:.15,gamma_reg:2,temp_base:1,temp_max:3,alpha_facet:.3,facet_decay:.05,pass_facet_pen:.2,beta_group:.7,group_logistic_c:3,rho_scalar:.8,scalar_sigma:.5,tau_facet:1,tau_group:2,tau_scalar:.5,facet_floor:.01,delta_pass:1,theta_B:3,fatigue_threshold:.3},nt=(e,t)=>{let n=1-t;return Object.fromEntries(Object.entries(e).map(([e,t])=>[e,Object.fromEntries(Object.entries(t).map(([e,t])=>[e,t*n]))]))},rt=(e,t,n)=>{if(!t)return e;let r={...e};for(let[e,i]of Object.entries(t)){if(!i.length)continue;let t={...r[e]??{}};for(let e of i)t[e]=Math.max((t[e]??0)+n,0);r[e]=t}return r},it=e=>typeof e==`number`&&e>0&&Number.isFinite(e),at=e=>{if(e.length===0)return{passCentroid:null,passRadius:0};let t=et(e),n=0;for(let r of e){let e=$e(r,t);e>n&&(n=e)}return{passCentroid:t,passRadius:n}},ot=(e={})=>{let{sigma_base:t=P.sigma_base,d:n=P.d}=e;return{mu:qe(n),v:qe(n),sigma:t,passes:[],passCentroid:null,passRadius:0,t:0,entropy:.2,facets:{},groups:{},scalarPref:null,neg:{},blacklist:new Set,muted:new Set}},st=(e,t={})=>{let{lambda_max:n=P.lambda_max}=t,r=Qe(e.v);if(r<1e-10)return Je(e.mu);let i=N(e.v,1/r),a=n*Math.tanh(r);return Ye(e.mu,N(i,a))},ct=(e,t,n=null,r={})=>{let{alpha:i=P.alpha,beta:a=P.beta,sigma_base:o=P.sigma_base,rho:s=P.rho,prior_k:c=P.prior_k,alpha_facet:l=P.alpha_facet,facet_decay:u=P.facet_decay,beta_group:d=P.beta_group,rho_scalar:f=P.rho_scalar}=r,p=t.embedding,m=e.mu,h=e.t+1,g=i/(1-(1-i)**h),_=Ye(m,N(Xe(p,m),g));if(n!==null){let e=c/h;_=Ye(_,N(Xe(n,_),Math.min(e,1)))}let v=e.t===0?e.v:Ye(N(e.v,a),N(Xe(p,m),1-a)),y=s*e.sigma+(1-s)*o,b=rt(nt(e.facets,u),t.facets,l),x=t.group===void 0?e.groups:{...e.groups,[t.group]:d*(e.groups[t.group]??0)+(1-d)},S=it(t.scalar)?e.scalarPref===null?Math.log(t.scalar):f*e.scalarPref+(1-f)*Math.log(t.scalar):e.scalarPref;return{...e,mu:_,v,sigma:y,t:h,entropy:Math.max(e.entropy*.95,.05),facets:b,groups:x,scalarPref:S}},lt=(e,t,n={})=>{let{sigma_max:r=P.sigma_max,gamma_base:i=P.gamma_base,pass_window:a=P.pass_window,pass_facet_pen:o=P.pass_facet_pen,beta_group:s=P.beta_group,delta_pass:c=P.delta_pass,theta_B:l=P.theta_B,epsilon:u=P.epsilon,fatigue_threshold:d=P.fatigue_threshold}=n,f=t.embedding,p=e.mu,m=e.entropy,h=t.group,g=e.neg,_=e.blacklist,v=e.groups,y=e.muted;if(h!==void 0){let t=e.groups[h]??0;t>d?(y=new Set(e.muted),y.add(h)):(g={...e.neg,[h]:(e.neg[h]??0)+c},_=new Set(e.blacklist),g[h]>l&&_.add(h),v={...e.groups,[h]:s*t+(1-s)*-1})}let b=rt(e.facets,t.facets,-o),x=tt(e.v,Xe(f,p)),S=Xe(p,f),ee=Qe(S),te=i*(1+m),ne=e.t>0&&ee>=u?Ye(p,N(S,te/ee)):p,C=[...e.passes,Je(f)];C.length>a&&C.shift();let{passCentroid:w,passRadius:re}=at(C);return{...e,mu:ne,v:x,sigma:Math.min(e.sigma*1.1,r),passes:C,passCentroid:w,passRadius:re,entropy:Math.min(m*1.1+.02,1),facets:b,groups:v,neg:g,blacklist:_,muted:y}},ut=e=>{if(!e)return null;let t=E(e),n=new Float32Array(e.length);for(let r=0;r<e.length;r++)n[r]=e[r]/t;return n};function F(e=[],t=[],{drift:n=.35}={}){let r=e.filter(e=>e?.vector?.length);if(!r.length)return null;let i={d:r[0].vector.length,alpha:1-2**(-1/8)},a=e=>({embedding:Float32Array.from(e.vector)}),o=ot(i);for(let e of r)o=ct(o,a(e),null,i);r.length<4&&(o={...o,v:new Float32Array(i.d)});let s=t.filter(e=>e?.vector?.length);for(let e of s)o=lt(o,a(e),i);return{mu:ut(o.mu),v:E(o.v)>1e-6&&r.length>=4?ut(o.v):null,q:ut(st(o,{lambda_max:n})),no:ut(o.passCentroid),drift:n,from:r.map(e=>e.title??e.id).slice(-12),rejected:s.map(e=>e.title??e.id).slice(-12),n:r.length}}var dt=.6,ft=(e,t)=>{if(!e?.vector||!t)return null;let n=mt(t),r=e.norm?e:{vector:e.vector,norm:E(e.vector)},i=T(r,t.q,n.q);return t.no?i-dt*Math.max(0,T(r,t.no,n.no)):i},pt=new WeakMap;function mt(e){let t=pt.get(e);return(!t||t.qv!==e.q||t.nov!==e.no)&&pt.set(e,t={qv:e.q,nov:e.no,q:E(e.q),no:e.no?E(e.no):1}),t}function I(e,t,{limit:n=10,exclude:r=new Set,lambda:i=.7}={}){return t?oe(e.filter(e=>e.vector&&!r.has(e.id)).map(e=>({row:e,s:ft(e,t)})).sort((e,t)=>t.s-e.s),{limit:n,lambda:i}).map(({row:e,s:t})=>({row:e,score:Number(t.toFixed(4))})):[]}var ht={lookahead:[.35,`how far past what you like, toward where your recent likes are heading (the kernel's drift)`],variety:[.3,`how different the picks are from each other`],surprise:[0,`how much chance reorders the list; 0 is strict best-first`],reach:[0,`how far out from the closest matches to start: 0 is nearest, 1 skips the nearest ~400`]},gt=(e,t)=>Math.min(1,Math.max(0,Number.isFinite(Number(e))?Number(e):t));function L(e,t=[],n=[],{limit:r=20,seed:i=1,exclude:a=new Set,...o}={}){let s=Object.fromEntries(Object.entries(ht).map(([e,[t]])=>[e,gt(o[e],t)])),c=F(t,n,{drift:s.lookahead});if(!c)return{taste:null,knobs:{...s,seed:i},picks:[]};let l=new Set([...a,...t.map(e=>e.id),...n.map(e=>e.id)]),u=e.filter(e=>e.vector&&!l.has(e.id)),d=Math.round(s.reach*Math.min(400,u.length/4)),f=d+300,p=[];for(let e of u){let t=ft(e,c);if(p.length===f&&!(t>p[f-1].s))continue;let n=p.length;for(;n>0&&p[n-1].s<t;)n--;p.splice(n,0,{row:e,s:t}),p.length>f&&p.pop()}let m=p.slice(d);if(s.surprise){let e=Number(i)>>>0||1,t=()=>{e=e+1831565813>>>0;let t=Math.imul(e^e>>>15,1|e);return t^=t+Math.imul(t^t>>>7,61|t),((t^t>>>14)>>>0)/4294967296};m=m.map(e=>({...e,s:e.s+.04*s.surprise*-Math.log(-Math.log(t()*.999998+1e-6))})).sort((e,t)=>t.s-e.s)}return{taste:{from:c.from,rejected:c.rejected,heading:!!c.v,n:c.n},knobs:{...s,seed:i},picks:oe(m,{limit:r,lambda:1-.6*s.variety}).map(({row:e,s:t})=>({row:e,score:Number(t.toFixed(4))}))}}var R=e=>e&&{v:1,model:`nomic-256`,drift:e.drift,n:e.n,q:C(Array.from(e.q)),mu:C(Array.from(e.mu)),...e.v?{d:C(Array.from(e.v))}:{},...e.no?{no:C(Array.from(e.no))}:{},from:e.from,rejected:e.rejected};function _t(e){if(!e?.q)return null;let t=w(e.q),n=w(e.mu??e.q);return t?{q:t,mu:n??t,v:e.d?w(e.d):null,no:e.no?w(e.no):null,drift:e.drift??.35,from:e.from??[],rejected:e.rejected??[],n:e.n??0}:null}var vt=`(remembered`;function yt(e,{carried:t=null,likes:n=[],dislikes:r=[]}={}){if(!e)return e;let i=t??{from:[],rejected:[],n:0},a=e=>e.filter(e=>!String(e?.id??``).startsWith(vt)).map(e=>e.title??e.id),o=a(n);return{...e,v:e.v??i.v??null,from:[...i.from??[],...o].slice(-12),rejected:[...i.rejected??[],...a(r)].slice(-12),n:(i.n??0)+o.length}}if(typeof process<`u`&&import.meta.url===`file://${process.argv[1]}`&&process.argv[1].endsWith(`/taste.js`)){{let e=I([{id:`a`,vector:[1,0],norm:1},{id:`b`,vector:[0,1],norm:1}],{q:[1,0]});if(!e.length)throw Error(`recommend must rank rows with vectors`);if(e[0].row===void 0)throw Error(`recommend rows carry .row`);if(typeof e[0].score!=`number`)throw Error(`recommend rows carry .score (not .s)`)}let e=(e,t,n=e)=>({id:e,title:n,vector:Float32Array.from(t),norm:E(t)}),t=[1,0,0],n=[0,1,0],r=[0,0,1],i=(e,t,n)=>e.map((e,r)=>e*(1-n)+t[r]*n),a=F([e(`a`,t),e(`b`,i(t,r,.2)),e(`c`,i(t,r,.1))]);if(!a)throw Error(`three likes must make a taste`);if(a.v!==null)throw Error(`under four picks there is no older half — direction must not be invented`);if(T({vector:a.q,norm:E(a.q)},Float32Array.from(t),1)<.9)throw Error(`q must sit near what was liked`);let o=[e(`x`,n,`a comedy`),e(`y`,i(t,r,.15),`a bleak documentary`),e(`z`,r,`a documentary`)];if(I(o,a)[0].row.title!==`a bleak documentary`)throw Error(`taste must rank an unseen pool`);let s=[e(`1`,t),e(`2`,i(t,n,.2)),e(`3`,i(t,n,.4)),e(`4`,i(t,n,.6))];if(a=F(s),a.v===null)throw Error(`four picks must yield a direction`);let c=T({vector:a.q,norm:E(a.q)},Float32Array.from(n),1),l=T({vector:a.mu,norm:E(a.mu)},Float32Array.from(n),1);if(!(c>l))throw Error(`q must lean further toward where it is heading than mu: ${c} vs ${l}`);let u=F(s,[],{drift:0});if(T({vector:u.q,norm:E(u.q)},u.mu,E(u.mu))<.999)throw Error(`drift 0 must be mu`);let d=[e(`p`,i(t,n,.7))],f=[e(`m`,t,`bleak`),e(`n`,n,`comic`)];if(I(f,F(d))[0].row.title!==`comic`)throw Error(`sanity: the taste leans comic`);if(I(f,F(d,[e(`q`,n)]))[0].row.title!==`bleak`)throw Error(`rejecting comic must flip the ranking to bleak`);let p=e(`a`,t,`the one they liked`);if(I([p,e(`other`,i(t,r,.1),`similar`)],F([p]),{exclude:new Set([`a`])}).some(e=>e.row.id===`a`))throw Error(`must not recommend what the taste was built from`);a=F(s);let m=R(a),h=JSON.stringify(m).length,g=_t(m);if(!g)throw Error(`a taste must survive the round trip`);let _=I(o,a).map(e=>e.row.id).join(),v=I(o,g).map(e=>e.row.id).join();if(_!==v)throw Error(`ranking changed across the wire: ${_} vs ${v}`);if(!m.from.length)throw Error(`a taste must say what it was built from, or it cannot be corrected`);if(!a.v)throw Error(`this fixture is meant to have a heading`);if(!g.v)throw Error(`a heading must survive the wire — q leans on it, so an export without it lies`);if(T({vector:g.v,norm:E(g.v)},a.v,E(a.v))<.98)throw Error(`…and must survive it intact`);if(_t({})!==null||_t(null)!==null)throw Error(`a malformed taste must be null, not a crash`);if(F([])!==null)throw Error(`no likes is no taste, not an empty one`);if(I(o,null).length)throw Error(`no taste recommends nothing`);if(ft({vector:null},a)!==null)throw Error(`a vectorless row scores null, not NaN`);{let e=(e,t)=>{let n=t*Math.PI/180;return{id:e,vector:[Math.cos(n),Math.sin(n)],norm:1}},t=[e(`run1`,0),e(`run2`,.8),e(`run3`,1.6),e(`run4`,2.4),e(`other`,60)],n=F([{id:`seed`,...e(`seed`,30)}]),r=I(t,n,{limit:3,lambda:1}).map(e=>e.row.id);if(r.some(e=>e===`other`))throw Error(`relevance alone must fill up with the run: ${r}`);let i=I(t,n,{limit:3}).map(e=>e.row.id);if(!i.includes(`other`))throw Error(`MMR must spend a slot on something else: ${i}`);if(i[0]!==r[0])throw Error(`the best match must still come first: ${i[0]} vs ${r[0]}`)}{let a=R(F([e(`a`,t,`The Golem`),e(`b`,n,`Nosferatu`),e(`c`,r,`Metropolis`)],[e(`z`,r,`Wrestling`)]));if(a.n!==3||a.from.length!==3)throw Error(`a fresh export carries what it was built from`);let o=_t(a),s=[{id:`(remembered)`,title:`what you liked before · ${o.n} picks`,vector:o.q}],c=o.no?[{id:`(remembered-no)`,title:`what you passed on before`,vector:o.no}]:[];if(R(F(s,c)).n!==1)throw Error(`this is the loss the next assertion is about`);let l=R(yt(F(s,c),{carried:o,likes:s,dislikes:c}));if(l.n!==3)throw Error(`a restored taste must not report itself as one pick: ${l.n}`);if(!l.from.includes(`Nosferatu`))throw Error(`…and must still name what it was built from`);if(l.from.some(e=>e.startsWith(`(remembered`)))throw Error(`the synthetic placeholder is not a title`);if(!l.rejected.includes(`Wrestling`))throw Error(`rejections are provenance too`);let u=[...s,e(`d`,n,`Sunrise`)],d=R(yt(F(u,c),{carried:o,likes:u,dislikes:c}));if(d.n!==4)throw Error(`a new pick must count once: ${d.n}`);if(d.from.filter(e=>e===`Nosferatu`).length!==1)throw Error(`…and must not duplicate the carried ones`);{let r=_t(R(F([e(`p`,t,`1`),e(`q`,i(t,n,.3),`2`),e(`r`,i(t,n,.6),`3`),e(`s`,n,`4`)]))),a=[{id:`(remembered)`,title:`before`,vector:r.q}];if(!yt(F(a),{carried:r,likes:a}).v)throw Error(`a restored session must keep the heading its own q was built with`);if(F(a).v)throw Error(`…which the kernel alone cannot supply from one entry`)}let f=R(yt(F(s,c),{carried:_t(l),likes:s,dislikes:c}));if(f.n!==3||f.from.join()!==l.from.join())throw Error(`a second reload must change nothing`)}{let e=Array.from({length:40},(e,t)=>{let n=t*Math.PI/80;return{id:`g${t}`,title:`g${t}`,vector:[Math.cos(n),Math.sin(n)],norm:1}}),t=[e[0]],n=[e[39]],r=e=>e.picks.map(e=>e.row.id).join(),i=L(e,t,n,{limit:5});if(i.picks[0].row.id!==`g1`)throw Error(`nearest first by default: ${r(i)}`);if(i.picks.some(e=>e.row.id===`g0`||e.row.id===`g39`))throw Error(`discover must not return what was voted on`);if(L(e,t,[],{limit:5,reach:1}).picks[0].row.id===`g1`)throw Error(`reach must skip the nearest`);if(r(L(e,t,[],{limit:5,surprise:1,seed:7}))!==r(L(e,t,[],{limit:5,surprise:1,seed:7})))throw Error(`the same seed must give the same list`);if(L(e,[],[],{}).picks.length)throw Error(`no likes, no picks`)}console.log(`taste.js self-check ok — discover's knobs, mu/v/q, drift leans forward, rejection steers, portable in ${h}B, ranks a pool it never saw, MMR keeps a run of near-duplicates from filling the list, provenance and heading survive the restore collapse and do not drift on reload`)}var z=e=>({content:[{type:`text`,text:JSON.stringify(e)}]}),bt=(e,t=[])=>({type:`object`,properties:e,required:t});function xt(e){let t=document.modelContext;t?.registerTool&&(t.registerTool({name:`describe`,description:`What this app holds: its fields and how often each is present. Call it first.`,inputSchema:bt({}),execute:async()=>z(ze(e.rows))}),t.registerTool({name:`search`,description:"Search by meaning: a question in plain words. Optional equality filters in `where`.",inputSchema:bt({query:{type:`string`},limit:{type:`number`},where:{type:`object`}},[`query`]),execute:async({query:t,limit:n=10,where:r})=>z(j(e.rows,t,e.roles,{qv:await e.queryVector(t),limit:n,where:r}))}),t.registerTool({name:`get`,description:`One record in full, by the id search returns.`,inputSchema:bt({id:{type:`string`}},[`id`]),execute:async({id:t})=>z(Ke(e.rows,t,e.roles)??{error:`no record ${t}`})}),t.registerTool({name:`count`,description:`Count the distinct values of a field: the shape of the data before reading it.`,inputSchema:bt({field:{type:`string`},where:{type:`object`},limit:{type:`number`}},[`field`]),execute:async({field:t,where:n,limit:r=20})=>z(We(e.rows,t,{where:n,limit:r}))}),t.registerTool({name:`session`,description:`What the person on this page saved and searched this session, and the taste (a vector over this app's records) their choices make.`,inputSchema:bt({}),execute:async()=>z(e.session?.()??{saved:[],searches:[],taste:null})}),t.registerTool({name:`rate`,description:`Record that the person liked or disliked a record (or clear it), by the id search returns. Builds the taste discover ranks with.`,inputSchema:bt({id:{type:`string`},verdict:{type:`string`,enum:[`like`,`dislike`,`clear`]}},[`id`,`verdict`]),execute:async({id:t,verdict:n})=>z(e.rate?.(t,n)??{error:`this page keeps no taste`})}),t.registerTool({name:`discover`,description:`Records for a taste: a kernel over what was liked (newest last) and disliked, not a text search. Pass likes and dislikes (ids from search), or omit them to use the taste on this page. Knobs are 0–1.`,inputSchema:bt({likes:{type:`array`,items:{type:`string`}},dislikes:{type:`array`,items:{type:`string`}},...Object.fromEntries(Object.entries(ht).map(([e,[t,n]])=>[e,{type:`number`,minimum:0,maximum:1,default:t,description:n}])),seed:{type:`integer`,description:`Same seed, same shuffle`},limit:{type:`integer`,minimum:1,maximum:50,default:10}}),execute:async({likes:t,dislikes:n,limit:r=10,...i})=>{let a=t=>t.map(t=>e.rows.find(n=>n.id===Ke(e.rows,t,e.roles)?.id)).filter(e=>e?.vector),o=t?{likes:a(t),dislikes:a(n??[])}:e.votes?.()??{likes:[],dislikes:[]};if(!o.likes.length)return z({error:`like at least one record first (ids from search, or rate)`});let s=L(e.rows,o.likes,o.dislikes,{...i,limit:Math.min(50,r)});return z({...s,picks:s.picks.map(({row:t,score:n})=>({...Ke(e.rows,t.id,e.roles),score:n}))})}}))}function St(e,t){let n=t=>new Set(e.slice(0,5e3).flatMap(e=>a(e[t]??``))).size;return t.facets?.[0]??t.tags.find(e=>{let t=n(e);return t>=2&&t<=12})??null}function Ct(e,t,{facet:r=null,only:a=null,today:o=new Date().toISOString().slice(0,10)}={}){let c=new Map;for(let o of e){let e=String(n(o,t.temporal)??``).slice(0,10);if(!e)continue;let l=r?String(o[r]??``):``;if(a&&l!==a)continue;let u=i(o,t)??``,d=`${l}|${u}|${e}`,f=c.get(d);f||c.set(d,f={key:d,date:e,place:l,label:u,byTitle:new Map});let p=s(o,t).toLowerCase(),m=f.byTitle.get(p);(!m||B(m,t).length<B(o,t).length)&&f.byTitle.set(p,o)}let l=[...c.values()].map(({byTitle:e,...t})=>({...t,items:[...e.values()]}));return{upcoming:l.filter(e=>e.date>o).sort((e,t)=>e.date.localeCompare(t.date)||e.place.localeCompare(t.place)),past:l.filter(e=>e.date<=o).sort((e,t)=>t.date.localeCompare(e.date)||e.place.localeCompare(t.place))}}function B(e,t){let n=s(e,t),r=String(e.text??``),i=r.startsWith(n.replace(/\.$/,``))?r.slice(n.replace(/\.$/,``).length).replace(/^[.\s]+/,``):r;return i===n?``:i.trim()}if(typeof process<`u`&&process.argv[1]?.endsWith(`/feed.js`)){let{rolesFrom:e}=await t(async()=>{let{rolesFrom:e}=await import(`./roles-YDRjv_PY.js`);return{rolesFrom:e}},__vite__mapDeps([4,5]),import.meta.url),n=e([{role_map:{title:`heading`,subtitle:`meeting`,temporal:`date`,tags:[`city`,`body`],text:[`text`]},presentation:{facets:[`city`]}}]),r=(e,t,n,r=n)=>({city:e,date:t,heading:n,meeting:`Board · ${t}`,body:`Board`,text:r}),i=[r(`A`,`2026-01-01`,`Budget`),r(`A`,`2026-01-01`,`Budget`,`Budget. Approved 5-0.`),r(`A`,`2026-01-01`,`Roads`),r(`B`,`2026-02-01`,`Parks`),r(`A`,`2026-03-01`,`Zoning`),{heading:`undated`}],a=(e,t,n)=>{if(JSON.stringify(e)!==JSON.stringify(t))throw Error(`${n}: ${JSON.stringify(e)}`)};a(St(i,n),`city`,`the declared facet wins`);let{upcoming:o,past:s}=Ct(i,n,{facet:`city`,today:`2026-02-15`});a(o.map(e=>e.key),[`A|Board · 2026-03-01|2026-03-01`],`a future date is coming up`),a(s.map(e=>`${e.place}${e.date}`),[`B2026-02-01`,`A2026-01-01`],`past, latest first`),a(s[1].items.map(e=>B(e,n)),[`Approved 5-0.`,``],`one item per title, the one that says what happened`),a(Ct(i,n,{facet:`city`,only:`B`,today:`2026-02-15`}).past.length,1,`narrowed to one place`),console.log(`feed.js self-check ok — declared facet, upcoming vs past, one item per title keeping the decision, narrowed by place`)}var wt=(e,t,n)=>{try{return JSON.parse(e?.getItem(t)??`null`)??n}catch{return n}},V=(e,t,n)=>{try{e?.setItem(t,JSON.stringify(n))}catch{}};function Tt({local:e,session:t,app:n=`app`}={}){let r={saved:`${n}:saved`,passed:`${n}:passed`,history:`${n}:history`};return{saved:()=>wt(e,r.saved,[]),passed:()=>wt(e,r.passed,[]),history:()=>wt(t,r.history,[]),isSaved(e){return this.saved().some(t=>t.key===e)},toggleSave(t){let n=this.saved(),i=n.findIndex(e=>e.key===t.key);return i>=0?n.splice(i,1):n.push({...t,savedAt:new Date().toISOString()}),V(e,r.saved,n),V(e,r.passed,this.passed().filter(e=>e.key!==t.key)),i<0},pass(t){V(e,r.passed,[...this.passed().filter(e=>e.key!==t.key),t].slice(-50)),V(e,r.saved,this.saved().filter(e=>e.key!==t.key))},unvote(t){V(e,r.saved,this.saved().filter(e=>e.key!==t)),V(e,r.passed,this.passed().filter(e=>e.key!==t))},remember(e){let n=e.trim();n&&V(t,r.history,[{q:n,at:new Date().toISOString()},...this.history().filter(e=>e.q!==n)].slice(0,50))},clear(n){V(n===`history`?t:e,r[n],[])},votes(){let e=e=>({id:e.key,title:e.title,vector:e.v?w(e.v):null});return{likes:this.saved().map(e),dislikes:this.passed().map(e)}},taste(){let{likes:e,dislikes:t}=this.votes();return F(e,t)},bundle({name:e=n,url:t=``}={}){return{app:e,url:t,exported:new Date().toISOString(),saved:this.saved().map(({v:e,...t})=>t),searches:this.history(),taste:R(this.taste())}}}}var Et=(e,{key:t,title:n,subtitle:r,place:i,date:a,link:o,detail:s})=>({key:t,title:n,subtitle:r,place:i,date:a,url:o??null,detail:s??``,...e.vector?{v:C(Array.from(e.vector))}:{}}),Dt=e=>`"${String(e??``).replace(/"/g,`""`)}"`,Ot=e=>[`title,place,subtitle,date,url,detail`,...e.map(e=>[e.title,e.place,e.subtitle,e.date,e.url,e.detail].map(Dt).join(`,`))].join(`
7
- `),kt=(e,t=`Saved`)=>[`# ${t}`,``,...e.map(e=>`- **${e.url?`[${e.title}](${e.url})`:e.title}** — ${[e.place,e.subtitle].filter(Boolean).join(` · `)}${e.detail?`\n ${e.detail}`:``}`)].join(`
8
- `);if(typeof process<`u`&&process.argv[1]?.endsWith(`/store.js`)){let e=()=>{let e=new Map;return{getItem:t=>e.get(t)??null,setItem:(t,n)=>e.set(t,n)}},t=Tt({local:e(),session:e(),app:`t`}),n=e=>({vector:Array.from({length:8},(t,n)=>n===e?1:.1)}),r=Et(n(0),{key:`a`,title:`Budget`,place:`X`,date:`2026-01-01`}),i=Et(n(1),{key:`b`,title:`Roads, "east"`,place:`Y`}),a=(e,t,n)=>{if(JSON.stringify(e)!==JSON.stringify(t))throw Error(`${n}: ${JSON.stringify(e)}`)};if(a(t.taste(),null,`no taste before a save`),t.toggleSave(r),t.toggleSave(i),a(t.saved().map(e=>e.key),[`a`,`b`],`saved in order`),!t.taste()?.q)throw Error(`saves make a taste`);t.pass(i),a([t.saved().map(e=>e.key),t.passed().map(e=>e.key)],[[`a`],[`b`]],`passing on an item unsaves it`),a(t.taste().rejected,[`Roads, "east"`],`and it steers the taste away`),t.remember(`snow`),t.remember(`budget`),t.remember(`snow`),a(t.history().map(e=>e.q),[`snow`,`budget`],`history: newest first, no repeats`);let o=t.bundle({name:`T`});if(o.saved[0].v||!o.taste?.q||o.searches.length!==2)throw Error(`the bundle carries choices and taste, not raw vectors`);if(!Ot([i]).includes(`"Roads, ""east"""`))throw Error(`CSV quoting`);if(Tt({local:{getItem(){throw Error(`blocked`)},setItem(){throw Error(`blocked`)}}}).saved().length!==0)throw Error(`blocked storage reads as empty`);console.log(`store.js self-check ok — saves and passes steer a taste, history dedupes, the bundle carries no raw vectors, CSV quotes, blocked storage is empty not fatal`)}var At=`http://www.w3.org/2000/svg`,jt=(e,t={})=>{let n=document.createElementNS(At,e);for(let[e,r]of Object.entries(t))n.setAttribute(e,r);return n},Mt=e=>e.toLocaleString(),Nt=new Set([`state`]);function Pt(e,{el:t,show:n,row:r,placeOf:i,ctx:s}){let c=s.roles,l=Object.keys(e.paths),u=null,d=null,f=null,p={q:``,hits:null};function m(){if(u?.n===s.rows.length)return u;let t=s.rows.map(t=>{let n=a(t[e.field]??``).filter(t=>e.paths[t]);return n.length?n:[e.places?.[i(t)]].filter(Boolean)}),n=new Map;for(let e of s.rows){let t=o(e);n.set(t,(n.get(t)??0)+1)}let r=[...n].sort((e,t)=>t[1]-e[1])[0]?.[0],c=new Map(l.map(e=>[e,[]]));s.rows.forEach((e,n)=>{for(let e of t[n])c.get(e)?.push(n)});let d=0,f=null,p=0,m=new Map;s.rows.forEach((e,n)=>{if(!e.vector||o(e)!==r||!t[n].length)return;d||=e.vector.length,f??=new Float64Array(d);let i=1/(e.norm||Math.hypot(...e.vector)||1);for(let r of t[n]){let t=m.get(r)??{v:new Float64Array(d),n:0};for(let n=0;n<d;n++)t.v[n]+=e.vector[n]*i;t.n++,m.set(r,t)}for(let t=0;t<d;t++)f[t]+=e.vector[t]*i;p++});let h=new Map;for(let[e,t]of m){if(t.n<20)continue;let n=t.v.map((e,n)=>e/t.n-f[n]/p),r=Math.hypot(...n)||1;h.set(e,n.map(e=>e/r))}return u={n:s.rows.length,regionsOf:t,by:c,main:r,types:n,centroid:h},u}function h(e){let{centroid:t}=m(),n=t.get(e);return n?[...t].filter(([t])=>t!==e).map(([e,t])=>({region:e,score:t.reduce((e,t,r)=>e+t*n[r],0)})).sort((e,t)=>t.score-e.score).slice(0,5):[]}let g=(e,t=2)=>{let n=c.types?.[e];return(t===1?n?.singular:n?.plural)??e??`Records`},_=e=>c.types?.[e]?.icon??``;function v(e){let{regionsOf:t}=m(),n=new Map,r=e??s.rows.map((e,t)=>t);for(let e of r)if(!(f&&o(s.rows[e])!==f))for(let r of t[e])n.set(r,(n.get(r)??0)+1);return n}async function y(e){if(!e)return null;if(p.q===e&&p.hits)return p.hits;let t=s.queryVector(e),n=await Promise.race([t,new Promise(e=>setTimeout(()=>e(void 0),400))]);return n===void 0&&t.then(t=>{t&&p.q===e&&!p.meaning&&(p={q:e,hits:b(e,t),meaning:!0},S(e,d))}),p={q:e,hits:b(e,n??null),meaning:n!==void 0},p.hits}function b(e,t){let n=new Map(s.rows.map((e,t)=>[e.id,t]));return j(s.rows,e,c,{qv:t,limit:600}).map(e=>n.get(e.id)).filter(e=>e!=null)}let x=(e,t=d)=>{location.hash=`#/map/${encodeURIComponent(e)}${t?`/${encodeURIComponent(t)}`:``}`};async function S(r=``,i=null){d=i&&e.paths[i]?i:null;let a=await y(r),c=v(a),u=Math.max(1,...c.values()),b=e=>e?Math.min(5,1+Math.floor((a?e/u:Math.log10(e)/Math.log10(u))*4.999)):0,te=d?new Set(h(d).map(e=>e.region)):new Set,ne=jt(`svg`,{viewBox:`0 0 ${e.width} ${e.height}`,role:`img`,"aria-label":a?`Map: where “${r}” comes up`:`Map: where records are`,class:`map`});for(let t of l){let n=jt(`path`,{d:e.paths[t],class:`h${b(c.get(t)??0)}${t===d?` on`:``}${te.has(t)?` near`:``}`,tabindex:0,"aria-label":`${t}: ${Mt(c.get(t)??0)}`});n.append(jt(`title`)),n.firstChild.textContent=`${t} · ${Mt(c.get(t)??0)} ${a?`matching`:(f?g(f):`records`).toLowerCase()}`;let i=()=>x(r,d===t?null:t);n.addEventListener(`click`,i),n.addEventListener(`keydown`,e=>{(e.key===`Enter`||e.key===` `)&&(e.preventDefault(),i())}),ne.append(n)}let C=t(`input`,{type:`search`,value:r,placeholder:`Search the map: data center, PFAS, solar rezoning…`,"aria-label":`Search the map`});C.addEventListener(`keydown`,e=>{e.key===`Enter`&&x(C.value.trim())});let w=a?new Map:m().types;for(let e of a??[]){let t=o(s.rows[e]);w.set(t,(w.get(t)??0)+1)}let re=w.size>1||a?t(`div`,{className:`chips`},[[null,a?.length??s.rows.length],...w].map(([e,n])=>t(`button`,{type:`button`,className:e===f?`chip on`:`chip`,onclick:()=>{f=e,S(r,d)}},`${e?`${_(e)} ${g(e,n)}`:`All`} (${Mt(n)})`))):null,ie=l.filter(e=>c.get(e)).length,T=a?`“${r}”: ${Mt([...c.values()].reduce((e,t)=>e+t,0))} matches in ${ie} of ${l.length} regions. Darker is more.${p.meaning?``:` (Matching words; search by meaning is loading.)`}`:`${f?g(f):`Records`} in ${ie} of ${l.length} regions. Select a region, or search.`;n(t(`div`,{className:`mapbar`},C),re,t(`p`,{className:`hint`},T),t(`div`,{className:`mapwrap`},ne,t(`aside`,{className:`panel`},ee(d,r,a))),t(`p`,{className:`hint`},`Legend: `,[0,1,2,3,4,5].map(e=>t(`span`,{className:`sw h${e}`})),a?` none → most matches`:` none → most records`))}function ee(n,i,l){if(!n)return[t(`h3`,{},`Select a region`),t(`p`,{className:`hint`},`Its records by kind and tag, the ones that match, and the regions most like it.`)];let{by:u}=m(),d=new Set(u.get(n)),p=(l?l.filter(e=>d.has(e)):[...d]).filter(e=>!f||o(s.rows[e])===f).map(e=>s.rows[e]),v=new Map;for(let e of p){let t=o(e);v.set(t,(v.get(t)??0)+1)}let y=(c.tags??[]).filter(t=>t!==e.field&&!Nt.has(t)).map(e=>{let t=new Map;for(let n of p)for(let r of a(n[e]??``))t.set(r,(t.get(r)??0)+1);return[e,[...t].sort((e,t)=>t[1]-e[1]).slice(0,6)]}).filter(([,e])=>e.length>1),b=e=>String(e[c.temporal?.[0]]??``),S=(l?p:p.slice().sort((e,t)=>b(t).localeCompare(b(e)))).slice(0,25),ee=h(n);return[t(`h3`,{},n,` `,t(`button`,{type:`button`,className:`linkish`,"aria-label":`Close`,onclick:()=>x(i,null)},`✕`)),t(`p`,{className:`hint`},[...v].map(([e,t])=>`${_(e)} ${Mt(t)} ${g(e,t).toLowerCase()}`).join(` · `)||`Nothing here`+(l?` matches.`:` yet.`)),ee.length?t(`div`,{className:`chips`},t(`small`,{title:`Regions whose records are closest in meaning; outlined on the map`},`Reads most like: `),ee.map(({region:e})=>t(`button`,{type:`button`,className:`chip`,onclick:()=>x(i,e)},e))):null,y.map(([e,n])=>t(`div`,{className:`chips`},t(`small`,{},`${e.replace(/_/g,` `)}: `),n.map(([e,n])=>t(`button`,{type:`button`,className:`chip`,title:`Search the map for “${e}”`,onclick:()=>x(e)},`${e} (${n})`)))),S.length?t(`ul`,{className:`list`},S.map(e=>r(e))):null,p.length>S.length?t(`p`,{className:`hint`},`${Mt(p.length-S.length)} more. `,t(`a`,{href:`#/search/${encodeURIComponent(i||n)}`},`Open in search`)):null]}return{render:S}}var H=e=>document.querySelector(e),U=(e,t={},...n)=>{let r=Object.assign(document.createElement(e),t);return r.append(...n.flat(1/0).filter(e=>e!=null&&e!==!1)),r},Ft=(e,t=220)=>e.length>t?`${e.slice(0,e.lastIndexOf(` `,t))} …`:e,It=e=>e?new Date(`${String(e).slice(0,10)}T12:00:00`).toLocaleDateString(void 0,{weekday:`short`,month:`short`,day:`numeric`,year:`numeric`}):``,W={rows:[],roles:r([]),queryVector:e=>ne(e).catch(()=>null)},Lt,Rt=new Promise(e=>{Lt=e});k({onManifests:e=>{W.roles=r(e),Lt()},rowText:e=>l(e,W.roles)});var zt=await fetch(`./.well-known/agent-card.json`).then(e=>e.json()).catch(()=>({})),G=(zt.capabilities?.extensions??[]).map(e=>e.params?.paid).find(Boolean)??null,Bt=zt.name??`Fangorn app`;document.title=Bt,G&&(H(`#walletlink`).hidden=!1),H(`#name`).textContent=Bt,fetch(`./nav.json`).then(e=>e.json()).then(e=>H(`nav`).append(...e.map(e=>U(`a`,{href:e.href},e.label)))).catch(()=>{});var Vt=await fetch(`./map.json`).then(e=>(e.headers.get(`content-type`)??``).includes(`json`)?e.json():null).catch(()=>null);Vt&&H(`nav`).prepend(U(`a`,{href:`#/map`},`Map`));var Ht=H(`#q input`);Ht.disabled=!1;var Ut=Bt.toLowerCase().replace(/\W+/g,`-`),K=Tt({local:globalThis.localStorage,session:globalThis.sessionStorage,app:Ut});W.session=()=>K.bundle({name:Bt,url:location.origin});var Wt=new URL(`view`,location.href).href,Gt=await fetch(`${Wt}/cdn/catalog`).then(e=>e.json()).then(e=>e.domains.reduce((e,t)=>e+(t.count??0),0)).catch(()=>0),Kt=!1,qt=0,Jt=A(Wt,e=>{W.rows=e,$t(),Date.now()-qt>1500&&/^(#\/?)?$/.test(location.hash)&&(qt=Date.now(),Fn())});await Rt;var q=W.roles,Yt=e=>String((q.identity&&e[q.identity])??e.id),Xt=new Map,Zt=new Map,J=null,Qt=[];function $t(){Xt=new Map(W.rows.map(e=>[Yt(e),e])),Zt=new Map(W.rows.map(e=>[e.id,e])),J??=St(W.rows,q),Qt=J?[...W.rows.reduce((e,t)=>{let n=Y(t);return n&&e.set(n,(e.get(n)??0)+1),e},new Map)].sort((e,t)=>e[0].localeCompare(t[0])):[];let e=W.rows.length.toLocaleString();Ht.placeholder=Kt&&!W.rows.length?`No records yet`:Kt?`Search ${e} records${Qt.length>1?` in ${Qt.length} places`:``}…`:`Loading ${e}${Gt?` of ${Gt.toLocaleString()}`:``} records… (search what is here)`}var Y=e=>J?a(e[J]??``)[0]??``:``,en=e=>String(e[q.temporal?.[0]]??``).slice(0,10),tn=e=>d(e,q),nn=e=>Et(e,{key:Yt(e),title:s(e,q),subtitle:i(e,q),place:Y(e),date:en(e),link:tn(e),detail:B(e,q)}),rn=e=>/[a-z]/.test(e)||!/[A-Z]{5}/.test(e)?e:e.toLowerCase().replace(/\b[a-z]{1,3}\b/g,(e,t)=>/^(and|or|of|the|to|for|in|on|at|by|a|an|as|is|be|no|from)$/.test(e)?e:e.toUpperCase()).replace(/^\W*\w/,e=>e.toUpperCase()).replace(/([.:;!?]\s+|\u2013\s+|-\s+)(\w)/g,(e,t,n)=>t+n.toUpperCase()),an=e=>rn(s(e,q)),on=e=>`#/item/${encodeURIComponent(Yt(e))}`,sn=(e,t)=>{if(!t.length)return[e];let n=RegExp(`(${t.map(e=>e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)).join(`|`)})`,`ig`);return String(e).split(n).map((e,t)=>t%2?U(`mark`,{},e):e)},cn=e=>K.isSaved(e)?`like`:K.passed().some(t=>t.key===e)?`dislike`:null;W.rate=(e,t)=>{if(![`like`,`dislike`,`clear`].includes(t))return{error:`verdict is like, dislike or clear`};let n=Zt.get(e)??Xt.get(String(e))??Zt.get(Ke(W.rows,e,q)?.id);if(!n)return{error:`no record ${e}`};let r=Yt(n);return K.unvote(r),t===`like`&&K.toggleSave(nn(n)),t===`dislike`&&K.pass(nn(n)),un(),Pn(),/^#\/for-you/.test(location.hash)&&An(),{likes:K.saved().map(e=>e.title),dislikes:K.passed().map(e=>e.title)}},W.votes=()=>{let e=e=>{let t=Xt.get(e.id);return t?{id:t.id,title:e.title,vector:t.vector}:e},{likes:t,dislikes:n}=K.votes();return{likes:t.map(e).filter(e=>e.vector),dislikes:n.map(e).filter(e=>e.vector)}};var ln=e=>{let t=U(`button`,{type:`button`,className:`vote star`,title:`Save`,ariaLabel:`Save: ${s(e,q)}`,onclick:t=>{t.preventDefault(),W.rate(e.id,cn(Yt(e))===`like`?`clear`:`like`)}});return Object.assign(t.dataset,{key:Yt(e),vote:`like`}),U(`span`,{className:`votes`},t)};function un(){for(let e of document.querySelectorAll(`button.vote[data-vote]`)){let t=cn(e.dataset.key)===e.dataset.vote;e.setAttribute(`aria-pressed`,String(t)),e.textContent=t?`★`:`☆`,e.title=t?`Saved`:`Save`}}var X=(e,{terms:t=[],also:n=0,meta:r=!0}={})=>{let a=B(e,q);return U(`li`,{className:`row`},U(`div`,{className:`main`},U(`a`,{href:on(e),className:`title`},sn(an(e),t)),r?U(`small`,{},[Y(e),i(e,q)].filter(Boolean).join(` · `)):null,a?U(`p`,{className:`detail`},sn(Ft(a),t)):null,n?U(`small`,{className:`also`},`and ${n} more record${n>1?`s`:``} of the same item`):null),ln(e))},Z=null,dn=()=>Qt.length>1?U(`label`,{className:`picker`},U(`span`,{},`${J?J[0].toUpperCase()+J.slice(1):`Place`}`),U(`select`,{id:`place`,onchange:e=>{Z=e.target.value||null,Fn()}},[[null,W.rows.length],...Qt].map(([e,t])=>U(`option`,{value:e??``,selected:e===Z},`${e??`All`} (${t.toLocaleString()})`)))):null,fn=e=>{let t=e.search(/[:.;]\s/);return t<0||t>160?U(`p`,{className:`about`},e):U(`details`,{className:`about`},U(`summary`,{},e.slice(0,t+1).replace(/:$/,`.`)),U(`p`,{},e.slice(t+2)))},pn=e=>!Z||Y(e)===Z,mn=H(`#view`),Q=(...e)=>{mn.replaceChildren(...e.flat().filter(Boolean)),un(),window.scrollTo(0,0)};function hn(e){let t=U(`ul`,{},e.items.slice(0,5).map(e=>X(e,{meta:!1}))),n=e.items.length>5?U(`button`,{type:`button`,className:`more`,onclick:n=>{t.append(...e.items.slice(5).map(e=>X(e,{meta:!1}))),n.target.remove()}},`${e.items.length-5} more`):null;return U(`article`,{},U(`header`,{},U(`time`,{dateTime:e.date},It(e.date)),U(`h3`,{},[e.place,e.label.replace(/\s*·\s*\d{4}-\d\d-\d\d$/,``)].filter(Boolean).join(` — `))),t,n)}var gn=12;function _n(){let{upcoming:e,past:t}=Ct(W.rows,q,{facet:J,only:Z}),{likes:n,dislikes:r}=W.votes(),i=L(W.rows.filter(pn),n,r,{...$,limit:6});Q(zt.description?fn(zt.description):null,dn(),i.picks.length?U(`section`,{},U(`h2`,{},`For you`),U(`p`,{className:`hint`},`From the ${i.taste.n} item${i.taste.n>1?`s`:``} you liked. `,U(`a`,{href:`#/for-you`},`Tune`)),U(`ul`,{className:`list`},i.picks.map(({row:e})=>X(e)))):null,e.length?U(`section`,{},U(`h2`,{},`Coming up`),e.slice(0,8).map(hn)):null,t.length?U(`section`,{},U(`h2`,{},`Recently`),t.slice(0,gn).map(hn),t.length>gn?U(`button`,{type:`button`,className:`more`,onclick:()=>{gn+=12,_n()}},`Show more`):null):null,!e.length&&!t.length?U(`p`,{className:`empty`},Kt?W.rows.length?`Nothing dated here yet. Search above.`:`This app is registered and being built. Its records will appear here once they are published.`:`Loading the records…`):null)}var vn=`relevance`,yn=0,bn=null;async function xn(e){document.activeElement!==H(`#q input`)&&(H(`#q input`).value=e);let t=++yn,n=W.queryVector(e),r=await Promise.race([n,new Promise(e=>setTimeout(()=>e(void 0),400))]);t===yn&&(r===void 0&&n.then(n=>{n&&t===yn&&Sn(e,n)}),Sn(e,r??null,r===void 0))}function Sn(e,t,n=!1){let r=j(W.rows,e,q,{qv:t,limit:80,...Z&&J?{where:{[J]:Z}}:{}}),i=new Map;for(let e of r){let t=Zt.get(e.id);if(!t)continue;let n=`${Y(t)}|${s(t,q).toLowerCase()}`,r=i.get(n);r?(r.also++,B(t,q).length>B(r.r,q).length&&(r.r=t)):i.set(n,{r:t,also:0})}let a=[...i.values()];vn===`newest`&&(a=a.sort((e,t)=>en(t.r).localeCompare(en(e.r))));let o=e.toLowerCase().split(/\W+/).filter(e=>e.length>2),c=U(`div`,{className:`sort`},`Sort: `,[`relevance`,`newest`].map(r=>U(`button`,{type:`button`,className:r===vn?`chip on`:`chip`,onclick:()=>{vn=r,Sn(e,t,n)}},r)));Q(dn(),U(`div`,{className:`resultsbar`},U(`span`,{},`${a.length} result${a.length===1?``:`s`} for “${e}”${Z?` in ${Z}`:``}`,n?U(`small`,{},` · matching words; search by meaning is loading`):null),c),a.length?U(`ul`,{className:`list`},a.slice(0,40).map(({r:e,also:t})=>X(e,{terms:o,also:t}))):U(`p`,{className:`empty`},`No matches. Try other words, or All places.`))}function Cn(e){let t=Xt.get(e);if(!t)return Q(U(`p`,{className:`empty`},`That record is not in this app any more. `,U(`a`,{href:`#/`},`Home`)));let n=tn(t),r=B(t,q),a=new Set([`id`,`owner`,`text`,`vector`,`norm`,`embed`,`entityType`,q.identity,...q.title,...q.subtitle,...q.media,...q.temporal??[]]),o=Object.entries(t).filter(([e,t])=>!a.has(e)&&!/(_id|_sha256)$/.test(e)&&(typeof t==`string`||typeof t==`number`)&&String(t).length<120),c=U(`div`,{className:`doc`}),l=n?U(`button`,{type:`button`,className:`act`,onclick:()=>{if(c.firstChild){c.replaceChildren(),l.textContent=`View document`;return}c.append(U(`iframe`,{src:`./doc?u=${encodeURIComponent(n)}${Number(t.page)>1?`#page=${t.page}`:``}`,title:`Source document`,loading:`lazy`})),l.textContent=`Hide document`}},`View document`):null,u=Ct(W.rows.filter(e=>Y(e)===Y(t)&&en(e)===en(t)&&i(e,q)===i(t,q)),q,{facet:J}),d=[...u.upcoming,...u.past].flatMap(e=>e.items).filter(e=>s(e,q)!==s(t,q)),f=Ge(W.rows,t.id,q,{limit:12}).near.map(e=>Zt.get(e.id)).filter(e=>e&&s(e,q)!==s(t,q)).slice(0,6);Q(U(`p`,{},U(`a`,{href:`#/`,onclick:e=>{history.length>1&&(e.preventDefault(),history.back())}},`← Back`)),U(`article`,{className:`record`},U(`small`,{},[Y(t),i(t,q)].filter(Boolean).join(` · `)),U(`h2`,{},an(t)),r?U(`p`,{},r):U(`p`,{className:`hint`},`No decision is recorded under this item (it may be on an agenda, before the meeting).`),o.length?U(`dl`,{},o.map(([e,t])=>[U(`dt`,{},e.replace(/_/g,` `)),U(`dd`,{},String(t))])):null,U(`div`,{className:`acts`},ln(t),l,n?U(`a`,{href:n,target:`_blank`,rel:`noopener`,className:`act`},`Source ↗`):null),G&&t.paid_sha256?En(t):null,c),d.length?U(`section`,{},U(`h2`,{},`Same meeting`),U(`ul`,{className:`list`},d.slice(0,12).map(e=>X(e,{meta:!1})))):null,f.length?U(`section`,{},U(`h2`,{},`Similar`),U(`ul`,{className:`list`},f.map(e=>X(e)))):null)}var wn=G?`${Number(G.price)/10**(G.decimals??6)} ${G.symbol??`USDC`}`:``,Tn={get(e){try{return JSON.parse(localStorage.getItem(`${Ut}:paid:${e}`))}catch{return null}},set(e,t){try{localStorage.setItem(`${Ut}:paid:${e}`,JSON.stringify(t))}catch{}}};function En(e){let n=e[q.identity]??e.id,r=U(`section`,{className:`paid`}),i=e=>{let t=e.record;r.replaceChildren(U(`h3`,{},`Decision record`),t.summary?U(`p`,{},t.summary):null,U(`dl`,{},[[`Action`,t.action],[`Outcome`,t.outcome],[`Vote`,t.vote&&`${t.vote.for}–${t.vote.against}`]].filter(([,e])=>e).map(([e,t])=>[U(`dt`,{},e),U(`dd`,{},t)])),t.amounts?.length?U(`ul`,{},t.amounts.map(e=>U(`li`,{},typeof e==`string`?e:[e.amount,e.for?` · ${e.for}`:``]))):null,t.organizations?.length?U(`p`,{},`Organizations: `,t.organizations.map(e=>typeof e==`string`?e:`${e.name}${e.role===`other`?``:` (${e.role})`}`).join(`, `)):null,U(`small`,{},e.verified?`✓ Matches the record this app published`:e.verified===!1?`✗ Does not match the record this app published`:``,e.receipt?.transaction?[` · `,U(`span`,{},`paid, tx ${e.receipt.transaction.slice(0,10)}…`)]:``))},a=Tn.get(n);if(a)return i(a),r;let o=U(`small`,{}),s=U(`button`,{type:`button`,className:`act`},`Read the decision record · ${wn}`);return s.onclick=async()=>{s.disabled=!0,o.textContent=`Opening your wallet…`;try{let r=await(await t(async()=>{let{wallet:e}=await import(`./wallet-DMLc-ukR.js`);return{wallet:e}},__vite__mapDeps([6,1,2,7,8,9,10,11,12,13]),import.meta.url)).wallet(G);await r.signIn(),o.textContent=`Paying…`;let a=await r.buy(n,e.paid_sha256);if(a.error)throw Error(a.error);Tn.set(n,a),i(a)}catch(e){s.disabled=!1,o.replaceChildren(`Couldn't buy it: ${e.message}. `,U(`a`,{href:`#/wallet`},`Your wallet`))}},r.append(U(`p`,{className:`hint`},`What was decided, as fields: action, outcome, vote, amounts and who they are for, organizations. Agents buy the same record over x402.`),U(`div`,{className:`acts`},s,o)),r}function Dn(e,t,n){let r=U(`a`,{href:URL.createObjectURL(new Blob([t],{type:n})),download:e});document.body.append(r),r.click(),r.remove()}var On={get(){try{return JSON.parse(localStorage.getItem(`${Ut}:knobs`))??{}}catch{return{}}},set(e){try{localStorage.setItem(`${Ut}:knobs`,JSON.stringify(e))}catch{}}},$={...Object.fromEntries(Object.entries(ht).map(([e,[t]])=>[e,t])),...On.get(),seed:1},kn={lookahead:[`Lookahead`,`what you like now`,`where you're heading`],variety:[`Variety`,`close to each other`,`all different`],surprise:[`Surprise`,`best first`,`shuffled`],reach:[`Reach`,`nearest`,`further out`]};function An(){let{likes:e,dislikes:t}=W.votes(),n=L(W.rows.filter(pn),e,t,{...$,limit:30}),r=e=>{let[t,n,r]=kn[e],i=U(`output`,{},`${Math.round($[e]*100)}%`),a=U(`input`,{type:`range`,min:0,max:1,step:.05,value:$[e],id:`k-${e}`,title:ht[e][1]});return a.oninput=()=>{i.textContent=`${Math.round(a.value*100)}%`},a.onchange=()=>{$[e]=Number(a.value),On.set({...$,seed:void 0}),An(),document.getElementById(`k-${e}`)?.focus()},U(`label`,{className:`knob`,htmlFor:`k-${e}`},U(`span`,{},t,i),a,U(`small`,{},`${n} ↔ ${r}`))},i=(e,t)=>U(`li`,{className:t?`no`:``},e.title,U(`button`,{type:`button`,ariaLabel:`Forget ${e.title}`,onclick:()=>W.rate(e.id,`clear`)},`×`));Q(U(`h2`,{className:`page`},`For you`),dn(),U(`section`,{className:`taste`},e.length||t.length?U(`ul`,{className:`pills`},e.slice(-30).map(e=>i(e)),t.slice(-10).map(e=>i(e,!0))):U(`p`,{className:`hint`},`Save records with ☆ and this fills with more like them. Newer saves count more; it all stays in this browser.`),n.taste?U(`p`,{className:`hint`},n.taste.heading?`Your recent likes point somewhere new; Lookahead follows them.`:`Like 4 or more and Lookahead can follow where your taste is heading.`):null,U(`div`,{className:`knobs`},Object.keys(ht).map(r)),U(`button`,{type:`button`,className:`act`,disabled:!$.surprise,title:$.surprise?``:`Turn up Surprise to reroll`,onclick:()=>{$.seed++,An()}},`Reroll`)),n.picks.length?U(`ul`,{className:`list`},n.picks.map(({row:e})=>X(e))):null)}function jn(){let e=K.saved(),t=K.taste(),n=Bt.toLowerCase().replace(/\W+/g,`-`),r=()=>JSON.stringify(K.bundle({name:Bt,url:location.origin}),null,1),i=U(`button`,{type:`button`,className:`act`,onclick:async()=>{try{await navigator.clipboard.writeText(r()),i.textContent=`Copied`}catch{Dn(`${n}-session.json`,r(),`application/json`)}}},`Copy for an agent`);Q(U(`h2`,{className:`page`},`Saved (${e.length})`),e.length?U(`div`,{className:`acts`},U(`button`,{type:`button`,className:`act`,onclick:()=>Dn(`${n}-saved.json`,r(),`application/json`)},`Export JSON`),U(`button`,{type:`button`,className:`act`,onclick:()=>Dn(`${n}-saved.csv`,Ot(e),`text/csv`)},`CSV`),U(`button`,{type:`button`,className:`act`,onclick:()=>Dn(`${n}-saved.md`,kt(e,`${Bt}: saved`),`text/markdown`)},`Markdown`),i):U(`p`,{className:`empty`},`Nothing saved yet. Tap ☆ on a record; what you save stays on this device.`),t?U(`p`,{className:`hint`},`"For you" is built from these ${t.n}${t.rejected.length?`, and away from ${t.rejected.length} you passed on`:``}. It lives in this browser; the export carries it, so an agent can use it too. `,U(`button`,{type:`button`,className:`linkish`,onclick:()=>{K.clear(`passed`),jn(),Pn()}},`Forget what I passed on`)):null,U(`ul`,{className:`list`},e.slice().reverse().map(e=>{let t=Xt.get(e.key);return U(`li`,{className:`row`},U(`div`,{className:`main`},U(`a`,{href:t?on(t):e.url??`#/`,className:`title`},e.title),U(`small`,{},[e.place,e.subtitle].filter(Boolean).join(` · `)),e.detail?U(`p`,{className:`detail`},Ft(e.detail)):null),U(`button`,{type:`button`,className:`vote`,ariaLabel:`Forget ${e.title}`,title:`Forget`,onclick:()=>{K.unvote(e.key),jn(),Pn()}},`×`))})))}function Mn(){let e=K.history();Q(U(`h2`,{className:`page`},`Searches this session`),e.length?U(`ul`,{className:`list`},e.map(({q:e,at:t})=>U(`li`,{className:`row`},U(`div`,{className:`main`},U(`a`,{href:`#/search/${encodeURIComponent(e)}`,className:`title`},e),U(`small`,{},new Date(t).toLocaleTimeString()))))):U(`p`,{className:`empty`},`No searches yet.`),e.length?U(`button`,{type:`button`,className:`act`,onclick:()=>{K.clear(`history`),Mn()}},`Clear`):null)}async function Nn(){Q(U(`p`,{className:`empty`},`Opening your wallet…`));let e;try{e=await(await t(async()=>{let{wallet:e}=await import(`./wallet-DMLc-ukR.js`);return{wallet:e}},__vite__mapDeps([6,1,2,7,8,9,10,11,12,13]),import.meta.url)).wallet(G)}catch(e){return Q(U(`p`,{className:`empty`},`The wallet is unavailable: ${e.message}`))}let n=async()=>{if(!location.hash.startsWith(`#/wallet`))return;if(!e.authenticated)return Q(U(`h2`,{},`Wallet`),U(`p`,{},`Sign in to buy decision records (${wn} each). An email or Google sign-in gives you a wallet here, the same one in every Fangorn app; nothing to install.`),U(`div`,{className:`acts`},U(`button`,{type:`button`,className:`act`,onclick:()=>e.signIn()},`Sign in`)));let t=e.address?await e.balance().catch(()=>null):null;Q(U(`h2`,{},`Wallet`),U(`dl`,{},U(`dt`,{},`Address`),U(`dd`,{},e.address??`making your wallet…`),U(`dt`,{},`Balance`),U(`dd`,{},t==null?`…`:`${t.toFixed(2)} ${G.symbol??`USDC`} on ${e.chain.name}`)),U(`div`,{className:`acts`},U(`button`,{type:`button`,className:`act`,onclick:()=>e.addFunds()},e.faucet?`Get test funds (faucet)`:`Add funds`),U(`button`,{type:`button`,className:`act`,onclick:()=>e.logout()},`Sign out`)),e.faucet?U(`p`,{className:`hint`},`This app runs on a test network, where funds are free: use the faucet with your address above.`):null)};e.onChange(()=>{n()})}function Pn(){let e=K.saved().length;H(`#nsaved`).textContent=e?` (${e})`:``}function Fn(){let[,e=``,t=``]=location.hash.match(/^#\/([^/]*)\/?(.*)$/)??[],n=decodeURIComponent(t);for(let t of document.querySelectorAll(`nav a`))t.classList.toggle(`on`,t.getAttribute(`href`)===`#/${e}`);return document.body.classList.toggle(`wide`,e===`map`),e===`search`&&n?xn(n):e===`item`&&n?Cn(n):e===`saved`?jn():e===`for-you`?An():e===`history`?Mn():e===`wallet`&&G?Nn():e===`map`&&Vt?(bn??=Pt(Vt,{el:U,show:Q,row:X,placeOf:Y,ctx:W})).render(...t.split(`/`).map(decodeURIComponent)):(H(`#q input`).value=``,_n())}var In;Ht.oninput=()=>{clearTimeout(In);let e=Ht.value.trim();In=setTimeout(()=>{if(!e){location.hash.startsWith(`#/search`)&&(location.hash=`#/`);return}history.replaceState(null,``,`#/search/${encodeURIComponent(e)}`),xn(e)},250)},H(`#q`).onsubmit=e=>{e.preventDefault();let t=Ht.value.trim();t&&(K.remember(t),location.hash=`#/search/${encodeURIComponent(t)}`)},mn.addEventListener(`click`,e=>{e.target.closest(`a.title`)&&location.hash.startsWith(`#/search/`)&&K.remember(Ht.value)}),window.onhashchange=Fn,Pn(),Fn(),W.rows=await Jt,Kt=!0,$t(),xt(W),Fn();
@@ -1 +0,0 @@
1
- var e=/^\s*[[(].*[\])]\s*$/,t=/,(?! )/;function n(n){if(Array.isArray(n))return n.map(e=>String(e));let r=String(n??``),i=e.test(r)?r.trim().slice(1,-1):t.test(r)?r:null;return i===null?r===``?[]:[r]:i.split(`,`).map(e=>e.trim().replace(/^['"]|['"]$/g,``)).filter(Boolean)}var r={title:[`title`,`name`,`label`,`headline`,`path`],subtitle:[`subtitle`,`primaryType`,`entityType`,`kind`,`series`,`creator`],text:[`text`,`desc`,`description`,`summary`,`body`,`abstract`]},i=e=>Array.isArray(e)?e.filter(Boolean):e?[e]:[];function a(e=[],t=[]){let n=new Set;for(let e of t)for(let t of Object.keys(e))n.add(t);let a=[],o=[],s=[],c=[],l=[],u=[],d=[],f=[],p=[],m={},h={},g={},_={},v={},y=null,b=null,x=[],S=[],C=null,w=(e,t)=>{for(let n of i(t))n&&!e.includes(n)&&e.push(n)};for(let t of e){let e=t?.role_map??{};w(a,e.title),w(o,e.subtitle),w(s,e.tags),w(c,e.text),w(l,e.measures),w(u,e.spatial),w(d,e.media),w(f,e.temporal),C??=e.identity??null,e.refers?.field&&e.refers?.corpus&&e.refers?.to&&(b??={...e.refers}),Object.assign(m,e.labels??{});for(let t of i(e.fields))n.add(t);let r=t?.presentation??{};Object.assign(h,r.types??{}),Object.assign(g,r.fieldLabels??{}),Object.assign(_,r.externalUrl??{}),Object.assign(v,r.actions??{}),w(p,r.facets),y??=r.launch??null;for(let e of i(t?.entity_types))x.push(e);t?.paywall&&S.push({domain:t.name??null,...t.paywall})}let T=(e,t)=>{e.length||(w(e,t.filter(e=>n.has(e))),w(e,t))};return T(a,r.title),T(o,r.subtitle),T(c,r.text),{title:a,subtitle:o,tags:s,text:c,measures:l,spatial:u,media:d,temporal:f,facets:p,identity:C,labels:m,types:h,fieldLabels:g,externalUrl:_,actions:v,launch:y,refers:b,entityTypes:x,gates:S,fields:[...n].sort(),declared:e.some(e=>e?.role_map)}}var o=e=>{let t=e?.entityType??e?.kind;return t==null||t===``?null:String(t)};function s(e){let t=String(e);return/s$/i.test(t)?t:/(x|z|ch|sh)$/i.test(t)?`${t}es`:/[^aeiou]y$/i.test(t)?`${t.slice(0,-1)}ies`:`${t}s`}function c(e,t=[]){let n=new Map;for(let t of e.entityTypes)n.set(t.type,(n.get(t.type)??0)+(t.count??0));if(!n.size)for(let e of t){let t=o(e);t&&n.set(t,(n.get(t)??0)+1)}return[...n].map(([t,n])=>{let r=e.types[t]??{};return{type:t,count:n,icon:r.icon??null,accent:r.accent??null,singular:r.singular??t,plural:r.plural??s(r.singular??t)}}).sort((e,t)=>t.count-e.count)}function l(e){return(e.gates??[]).map(e=>({domain:e.domain??null,locked:[...e.locked??[]],free:[...e.free??[]],price:String(e.price??`0`),asset:e.asset??`USDC`,resourceId:e.resourceId??null,workerUrl:e.workerUrl??null,files:(e.files??[]).map(e=>({file:e.file,count:e.count,bytes:e.bytes})),unpublished:!e.resourceId}))}var u=(e,t)=>(t.gates??[]).some(t=>(t.locked??[]).includes(e));async function d(e,t){if(!(t>0))return!1;if(t>=1)return!0;let n=await crypto.subtle.digest(`SHA-256`,new TextEncoder().encode(String(e)));return new DataView(n).getUint32(0)<t*2**32}var f=e=>`${(Number(e.price??0)/1e6).toFixed(e.price%1e3?6:2)} ${e.asset??`USDC`}`,p=(e,t=[])=>{for(let n of t){let t=e?.[n];if(t!=null&&t!==``)return t}return null},m=(e,t)=>String(p(e,t.title)??e?.id??`untitled`),h=(e,t)=>{let n=p(e,t.subtitle);return n==null?null:String(n)};function g(e,t){let n=t.text.map(t=>e?.[t]).filter(e=>e!=null&&e!==``);if(n.length)return n.join(` `);if(t.text.some(e=>u(e,t)))return``;let r=p(e,t.title);return r?String(r):``}function _(e,t,r=[]){let i={id:e.id,title:m(e,t)},a=h(e,t);a&&(i.subtitle=a);for(let a of[...t.tags,...t.measures,...r]){let r=e[a];if(r==null||r===``)continue;let o=t.tags.includes(a)?n(r):null;i[a]=o?o.length>6?[...o.slice(0,6),`…+${o.length-6} more`]:o:typeof r==`string`&&r.length>160?`${r.slice(0,160)}…`:r}return i}function v(e,t){let n=t.externalUrl?.[o(e)];if(!n)return null;let r=!1,i=String(n).replace(/\{(\w+)\}/g,(t,n)=>{let i=e?.[n];return i==null||i===``?(r=!0,``):String(i)});return r?null:i}function y(e,t){return(t?.actions?.[o(e)]??[]).map(n=>{let r=[],i=[],a=String(n.href??``).replace(/\{(\w+)\}/g,(n,a)=>{let o=e?.[a];return o!=null&&o!==``?String(o):((u(a,t)?r:i).push(a),``)});return{kind:n.kind??`link`,label:n.label??n.kind??`open`,href:a,locked:r,missing:i}}).filter(e=>e.locked.length||!e.missing.length)}if(typeof process<`u`&&import.meta.url===`file://${process.argv[1]}`&&process.argv[1].endsWith(`/roles.js`)){let e={role_map:{identity:`placeId`,title:`title`,subtitle:`primaryType`,spatial:`locality`,media:`googleMapsUri`,tags:[`categories`],measures:[`rating`,`userRatingCount`],temporal:null,relations:[],text:[`text`,`editorialSummary`],labels:{title:`Title`},fields:[`address`,`phone`,`website`]},presentation:{types:{Business:{icon:`🏪`,singular:`Place`}},externalUrl:{Business:`{googleMapsUri}`,Event:`{ticketUrl}`}},entity_types:[{type:`Event`,count:654},{type:`Business`,count:263}]},t={id:`r1`,entityType:`Business`,title:`Cira's Red, White & Brew Bar`,primaryType:`Bar & Grill`,text:`Bar & Grill in Saint Germain, WI.`,editorialSummary:``,categories:`['Bar', 'Restaurant']`,rating:`4.7`,userRatingCount:`293`,googleMapsUri:`https://maps.google.com/?cid=140971`},r=a([e],[t]);if(!r.declared)throw Error(`a manifest with a role_map is declared, not sniffed`);if(m(t,r)!==`Cira's Red, White & Brew Bar`)throw Error(`declared title lost`);if(h(t,r)!==`Bar & Grill`)throw Error(`declared subtitle lost`);if(g(t,r)!==`Bar & Grill in Saint Germain, WI.`)throw Error(`empty declared text fields must be skipped, not joined as blanks`);{let e={field:`videoPath`,corpus:`archive-films`,to:`path`},t=a([{role_map:{identity:`videoPath`,refers:e}}]).refers;if(t?.corpus!==`archive-films`||t.field!==`videoPath`||t.to!==`path`)throw Error(`a complete reference must survive the manifest`);for(let t of[`field`,`corpus`,`to`]){let n={...e};if(delete n[t],a([{role_map:{refers:n}}]).refers)throw Error(`a reference missing '${t}' must not be honoured`)}if(a([{role_map:{title:`name`}}]).refers!==null)throw Error(`a corpus that declared no reference has none — this is never sniffed`)}if(v(t,r)!==`https://maps.google.com/?cid=140971`)throw Error(`presentation.externalUrl not applied`);if(v({...t,entityType:`Event`},r)!==null)throw Error(`a template with a missing field must be null, not a half-built link`);if(!r.fields.includes(`phone`)||!r.fields.includes(`website`))throw Error(`role_map.fields must reach the facet picker`);if(r.types.Business.icon!==`🏪`)throw Error(`presentation.types lost`);for(let e of[[`Bar`,`Restaurant`],`['Bar', 'Restaurant']`,`Bar,Restaurant`]){let n=_({...t,categories:e},r).categories;if(!Array.isArray(n)||n.join()!==`Bar,Restaurant`)throw Error(`tags must normalize: ${JSON.stringify(e)} → ${JSON.stringify(n)}`)}if(n(`Wilson, Michael G.`).length!==1)throw Error(`a comma followed by a space is prose, not a list`);if(n(``).length!==0)throw Error(`an empty value is no values, not one empty one`);if(Array.isArray(_(t,r).rating))throw Error(`a measure must stay scalar`);let i=_({...t,categories:Array.from({length:50},(e,t)=>`c${t}`)},r);if(i.categories.length!==7)throw Error(`long tag lists must truncate: ${i.categories.length}`);if(!String(i.categories.at(-1)).includes(`+44 more`))throw Error(`truncation must say how much it dropped`);if(_({...t,categories:[`a`,`b`]},r).categories.length!==2)throw Error(`a short list must pass through whole`);let p=_(t,r);if(p.title!==t.title||p.rating!==`4.7`||!p.categories)throw Error(`brief must carry declared tags + measures: ${JSON.stringify(p)}`);if(`googleMapsUri`in p)throw Error(`brief must not dump undeclared fields`);let b=a([{...e,name:`places`,paywall:{free:[`title`,`primaryType`],locked:[`text`,`phone`],price:`50000`,asset:`USDC`,resourceId:`0xdead`,workerUrl:`https://w`,files:[{file:`locked-0000-abc.ndjson.gz`,count:917,bytes:1234}]}}]),[x]=l(b);if(x.locked.join()!==`text,phone`)throw Error(`the locked field list must survive`);if(x.unpublished)throw Error(`a gate with a resourceId is buyable`);if(f(x)!==`0.05 USDC`)throw Error(`price must render as USDC, got ${f(x)}`);if(!u(`phone`,b)||u(`title`,b))throw Error(`isLocked must read the manifest`);if([x]=l(a([{...e,paywall:{locked:[`text`],price:`1`}}])),!x.unpublished)throw Error(`no resourceId means there is nothing to buy yet`);if(l(a([e])).length)throw Error(`an ungated view must report no gates`);if(u(`text`,a([e])))throw Error(`nothing is locked in an ungated view`);for(let[e,t]of[[`row-47`,!0],[`row-169`,!0],[`row-0`,!1],[`row-3`,!1],[`row-19`,!1]])if(await d(e,.01)!==t)throw Error(`inSample must match the bake: ${e}`);if(await d(`row-0`,0)!==!1||await d(`row-0`,1)!==!0)throw Error(`rate 0 gives nothing away, rate 1 gives everything`);let S=0;for(let e=0;e<2e3;e++)await d(`row-${e}`,.01)&&S++;if(S!==19)throw Error(`the bake picks exactly 19 of these 2000 — got ${S}`);let C=a([{...e,name:`places`,role_map:{...e.role_map,text:[`text`]},paywall:{free:[`title`],locked:[`text`],price:`1`}}]);if(g({title:`Cira's Bar`},C)!==``)throw Error(`a locked text role must read empty, not as the title`);if(g({title:`Cira's Bar`,text:`A bar.`},C)!==`A bar.`)throw Error(`a sampled row's real text must still win`);if(g({title:`Cira's Bar`},a([e]))!==`Cira's Bar`)throw Error(`the title fallback still stands when nothing is gated`);let w=c(a([e]));if(w.map(e=>e.type).join()!==`Event,Business`)throw Error(`collections must sort by count: ${w.map(e=>e.type)}`);if(w[1].icon!==`🏪`||w[1].singular!==`Place`)throw Error(`presentation.types must reach the collection`);if(w[1].plural!==`Places`)throw Error(`plural must derive from the declared singular: ${w[1].plural}`);if(w[0].singular!==`Event`||w[0].icon!==`🎫`&&w[0].icon!==null)throw Error(`an undescribed type must still be a collection`);if(w=c(a([{role_map:{title:`title`}}]),[t,t,{id:`e`,entityType:`Event`}]),w[0].type!==`Business`||w[0].count!==2)throw Error(`row counting fallback wrong: ${JSON.stringify(w)}`);for(let[e,t]of[[`subtitles`,`subtitles`],[`series`,`series`],[`video`,`videos`],[`folder`,`folders`],[`box`,`boxes`],[`category`,`categories`],[`day`,`days`]])if(s(e)!==t)throw Error(`pluralize(${e}) = ${s(e)}, want ${t}`);if(o({kind:`video`})!==`video`)throw Error("the older `kind` spelling is still on-chain");if(o({})!==null)throw Error(`a typeless row must be null, not 'undefined'`);let T={name:`videos`,shards:[]},E={id:`v1`,name:`S01E02 - Overture.mp4`,path:`classic_tv/Duet/S01E02.mp4`,kind:`video`,desc:`A forgotten Fox sitcom.`};if(r=a([T],[E]),r.declared)throw Error(`a manifest with no role_map must report itself as sniffed`);if(m(E,r)!==`S01E02 - Overture.mp4`)throw Error(`sniff must prefer name over path: ${m(E,r)}`);if(r.title[0]!==`name`)throw Error(`a field the sample HAS must outrank one it does not`);if(h(E,r)!==`video`)throw Error(`sniff must find a subtitle`);if(!g(E,r).includes(`forgotten Fox`))throw Error(`sniff must find the prose`);if(m({id:`x`},a([],[]))!==`x`)throw Error(`a title must always fall back to the id`);if(g({id:`x`},a([],[]))!==``)throw Error(`no prose and no title must be empty, not 'undefined'`);let D=a([],[]);if(!D.text.includes(`desc`)||!D.title.includes(`name`))throw Error(`a blind sniff must still offer candidates`);if(g(E,D)!==`A forgotten Fox sitcom.`)throw Error(`a row parsed before any sample must still get its prose`);if(m({id:`z`,title:`declared later`},D)!==`declared later`)throw Error(`a blind sniff must resolve per row`);if(r=a([e,{role_map:{title:`name`,text:[`body`]}}]),m({name:`a note`},r)!==`a note`)throw Error(`the second domain's title field must resolve too`);if(m(t,r)!==t.title)throw Error(`the first domain's title must still resolve`);if(!r.text.includes(`text`)||!r.text.includes(`body`))throw Error(`text fields must union across domains`);if(r.title.includes(`label`))throw Error(`a declared title must not be widened by guesses`);{let e=a([{name:`films`,role_map:{identity:`identifier`,title:`name`,text:[`desc`]},paywall:{free:[`identifier`,`name`],locked:[`path`],price:`50000`,asset:`USDC`},presentation:{actions:{video:[{kind:`video`,label:`Play`,href:`https://cdn.test/{path}`},{kind:`link`,label:`On archive.org`,href:`https://archive.org/details/{identifier}`},{kind:`link`,label:`Dead`,href:`https://x.test/{nosuchfield}`}]}}}]),t=y({entityType:`video`,identifier:`the-temp`,name:`temp.mp4`},e);if(t.length!==2)throw Error(`a template naming a field the bundle never had must drop: ${JSON.stringify(t)}`);if(!t.find(e=>e.kind===`video`).locked.includes(`path`))throw Error(`a player whose url is behind the paywall must say which field is locked`);let n=t.find(e=>e.kind===`link`);if(n.href!==`https://archive.org/details/the-temp`)throw Error(`free fields must interpolate: ${n.href}`);if(n.locked.length)throw Error(`a fully resolved action is not locked`);if(y({entityType:`video`,identifier:`the-temp`,name:`temp.mp4`,path:`a/b.mp4`},e).find(e=>e.kind===`video`).href!==`https://cdn.test/a/b.mp4`)throw Error(`an unlocked player must resolve`);if(y({entityType:`video`},a([])).length)throw Error(`no declaration, no actions`)}console.log(`roles.js self-check ok — declared role_map wins, sniffed fallback, fused domains merge, external links, declared actions resolve and name their locked fields, a cross-corpus reference is honoured only when whole and never sniffed, title never undefined`)}export{d as a,p as c,a as d,h as f,n as g,o as h,l as i,s as l,m,_ as n,u as o,g as p,c as r,v as s,y as t,f as u};
@@ -1 +0,0 @@
1
- import{d as e}from"./roles-C9V_uDr_.js";export{e as rolesFrom};