Humuhumu33 commited on
Commit
e999be0
·
verified ·
1 Parent(s): 0f2f71a

κ-store: 48 shell blobs (aggregate fb89925ddfec)

Browse files
Files changed (28) hide show
  1. .gitattributes +3 -0
  2. b/007141010ac01ca81bb4bf74ad758709586b2899c25d7b8f8cf412abf1998c62 +1 -0
  3. b/00cfa4960b319ba1ba7291cddb20dea130a407ce8b42426218854f4f68f4dcd7 +3 -0
  4. b/29f6cbec51d5d103e676c312ab27151e2aa98e38f05532b3ea4adec6df0b36bb +1 -0
  5. b/310c5b0dfd9e59f3fce276da94235913365a0e8570c2ce5d9290213ca0f3067d +378 -0
  6. b/331a35dfd2b4774673ed92da26b0c78fecd336a70ecc8ff890256e835f7ccfd9 +130 -0
  7. b/3370b22070b56218a57df6406e2e4ba7cc171e942d4d1f2ae4297ea2c4c08d9c +3 -0
  8. b/438bd84d968d05dadd9f0ac1fd49a00c9d24f88a4ac45a9f46df236e7af2961e +1 -0
  9. b/5deebbd5a4fd2e15c9dc97480f2ab2eabdc1b092a8be69d0ce0eb6af503cf943 +0 -0
  10. b/6d4dc944c87c9a8ca7dfa0c65244c010722defe6e010b8a43a4782e2366aa82e +1 -0
  11. b/6e815d50e2ba13e8011d0b43218ef9ae5be3fe466eabe4bdfecf4bdb73942946 +208 -0
  12. b/716223384b22303f78bbd63c947ab539dc9a2fd796104d9a2192b063df89b00c +24 -0
  13. b/7247a61220c39a750bc27806475dd592f15e763f3b128c54e2c0580039aec673 +41 -0
  14. b/7d883ae6cf3e4495a3377e83d4cedef8f4141c15cf8166884e0efad2592f15e9 +262 -0
  15. b/86a3f68c5070d7a543e52768811268d22f2051a5033c11d5281ecd2a36c2dd44 +72 -0
  16. b/8c30aa5582dffe4548d27fa7863163eba689ed65126be472c003f5884fc8e534 +1870 -0
  17. b/8d34a1e9297fbaf472886031a90d3a06dfd4aa71c0621e4325e0112ef96fb07d +16 -0
  18. b/9d90b4ea8bccee8c239073c7ec7dd5158c6d11f1f668487570d17475d79e661e +3 -0
  19. b/a2276a0af47a3bc0c9c83d8d370bcc85c07e9c0f516b196ac158e575d5277b02 +1 -0
  20. b/a5b5d9c559a1d2812c33e9d7a4b1c24bb03059482503a23356e4c9c32d4d8b88 +0 -0
  21. b/b9f8a887de588c5193dc65ca9ce206a70c6df53595b848e0ce0299c2d88a5753 +0 -0
  22. b/c9a284fdd54f5472ac7d7781d9ac2d289f7dbacb9c710b0315bee75cd68ed99e +47 -0
  23. b/d1fdc38c3e96ab2c5c176a4e016516b27197ec0f8dde9467532e75703cacaa7e +0 -0
  24. b/d2e8a9ef211a9cf3d441297f50e5ef6949d8a7c43ebbe56705e75eb32e969937 +1 -0
  25. b/e0b2a13d7c3890a4ee3fc6120d2b42a3901ba068897aca8ce2080320f63446ee +0 -0
  26. b/e7b914a4e11aedb2e0239e7a84502a8241f2fff157d980741728810a1559ff41 +1 -0
  27. b/f011367faa1643ec7403b71b5e585c7a85ede5bd4a08e8ad5e95c31bcdf1161a +1 -0
  28. b/f4b80dc5a1ba9ad776b7b3793b5b9af2ed35592e45ce447eab9d571d5bc47693 +1 -0
.gitattributes CHANGED
@@ -34,3 +34,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
  b/f6caa9eeead2c99deeb2cdb70b7e25bbaa2cf5c60555f5b4030ecf6b6d9efa51 filter=lfs diff=lfs merge=lfs -text
 
 
 
 
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
  b/f6caa9eeead2c99deeb2cdb70b7e25bbaa2cf5c60555f5b4030ecf6b6d9efa51 filter=lfs diff=lfs merge=lfs -text
37
+ b/00cfa4960b319ba1ba7291cddb20dea130a407ce8b42426218854f4f68f4dcd7 filter=lfs diff=lfs merge=lfs -text
38
+ b/3370b22070b56218a57df6406e2e4ba7cc171e942d4d1f2ae4297ea2c4c08d9c filter=lfs diff=lfs merge=lfs -text
39
+ b/9d90b4ea8bccee8c239073c7ec7dd5158c6d11f1f668487570d17475d79e661e filter=lfs diff=lfs merge=lfs -text
b/007141010ac01ca81bb4bf74ad758709586b2899c25d7b8f8cf412abf1998c62 ADDED
@@ -0,0 +1 @@
 
 
1
+ import"./chunk-SQE76S5B.js";var I=1,k={standard:{name:"Standard",tag:"matte black",locked:!1},volt:{name:"Volt",tag:"electric streaks",locked:!1},fiber:{name:"Fiber",tag:"woven copper",locked:!1},sovereign:{name:"Sovereign",tag:"TEE holders only",locked:!0}},S=["v","kind","chain","address","label","edition","holder","created"];function g(e){return JSON.stringify(S.map(o=>e[o]??null))}async function b(e){let o=await crypto.subtle.digest("SHA-256",new TextEncoder().encode(e));return[...new Uint8Array(o)].map(t=>t.toString(16).padStart(2,"0")).join("")}async function w({chain:e="ethereum",address:o="",label:t="Card",edition:a="standard",holder:l=""}={}){let i={v:1,kind:"card",chain:e,address:o,label:t,edition:a,holder:l,created:Date.now(),frozen:!1};return i.kappa=await b(g(i)),i}async function R(e){return e&&e.kappa===await b(g(e))}function y(e){let o=parseInt(e.slice(0,8),16)>>>0,t=parseInt(e.slice(8,16),16)>>>0,a=parseInt(e.slice(16,24),16)>>>0,l=parseInt(e.slice(24,32),16)>>>0;return()=>{let i=(o+t>>>0)+l>>>0;return l=l+1>>>0,o=t^t>>>9,t=a+(a<<3)>>>0,a=(a<<21|a>>>11)>>>0,a=a+i>>>0,(i>>>0)/4294967296}}var s=856,n=540;function p(e,o,t){let a=e.createLinearGradient(0,0,s*.35,n);a.addColorStop(0,o),a.addColorStop(1,t),e.fillStyle=a,e.fillRect(0,0,s,n)}function d(e,o,t,a){e.save();for(let l=0;l<t;l++)e.fillStyle=`rgba(255,255,255,${o()*a})`,e.fillRect(o()*s,o()*n,1,1);e.restore()}function h(e,o){p(e,"#101114","#050506");let t=s*(.2+o()*.6),a=e.createLinearGradient(t-180,0,t+180,n);a.addColorStop(0,"rgba(255,255,255,0)"),a.addColorStop(.5,"rgba(255,255,255,0.045)"),a.addColorStop(1,"rgba(255,255,255,0)"),e.fillStyle=a,e.fillRect(0,0,s,n),d(e,o,900,.05)}function m(e,o){p(e,"#04070f","#010208"),e.save(),e.globalCompositeOperation="lighter";let t=205+o()*40;for(let a=0;a<42;a++){let l=n*o(),i=n*o(),f=s*o(),r=s*o(),u=t+(o()-.5)*50;e.strokeStyle=`hsla(${u}, 100%, ${55+o()*25}%, ${.03+o()*.1})`,e.lineWidth=.5+o()*3.2,e.beginPath(),e.moveTo(-40,l),e.bezierCurveTo(f,l+(o()-.5)*260,r,i+(o()-.5)*260,s+40,i),e.stroke()}e.restore(),d(e,o,500,.04)}function C(e,o){p(e,"#12080a","#070304"),e.save();let t=s*(.3+o()*.4);for(let a=-4;a<n+4;a+=2){let l=Math.exp(-Math.abs(a-n*.45)/(n*.5)),f=8+o()*30+l*22;e.strokeStyle=`hsla(${14+o()*26}, ${55+o()*35}%, ${f}%, ${.5+o()*.5})`,e.lineWidth=1+o()*1.6,e.beginPath();let r=(o()-.5)*22;e.moveTo(0,a),e.bezierCurveTo(t*.5,a+r,t,a-r,s,a+(o()-.5)*14),e.stroke()}e.globalCompositeOperation="lighter";for(let a=0;a<60;a++){let l=n*o();e.strokeStyle=`hsla(${20+o()*25}, 100%, ${60+o()*25}%, ${.1+o()*.22})`,e.lineWidth=.6+o(),e.beginPath();let i=s*o()*.7;e.moveTo(i,l),e.lineTo(i+s*(.15+o()*.5),l+(o()-.5)*10),e.stroke()}e.restore()}function v(e,o){p(e,"#0a0a12","#040408"),e.save(),e.globalCompositeOperation="lighter";let t=o()*Math.PI*2;for(let a=0;a<26;a++){let l=a/26,i=(t*57+l*320+o()*30)%360,f=n*(l+(o()-.5)*.06),r=e.createLinearGradient(0,f-60,s,f+60);r.addColorStop(0,`hsla(${i},90%,60%,0)`),r.addColorStop(.5,`hsla(${i},90%,60%,${.05+o()*.06})`),r.addColorStop(1,`hsla(${(i+60)%360},90%,60%,0)`),e.fillStyle=r,e.beginPath(),e.ellipse(s/2,f,s*(.55+o()*.25),46+o()*40,(o()-.5)*.22,0,Math.PI*2),e.fill()}e.restore(),d(e,o,700,.05)}var T={standard:h,volt:m,fiber:C,sovereign:v};function $(e,o){e.save(),e.fillStyle="rgba(255,255,255,0.94)",e.font="600 44px ui-sans-serif, system-ui, 'Segoe UI', Roboto, sans-serif",e.textBaseline="top",e.fillText("Holo",52,46),e.font="300 44px ui-sans-serif, system-ui, 'Segoe UI', Roboto, sans-serif",e.fillText("Money",158,46),e.font="700 22px ui-sans-serif, system-ui, sans-serif",e.fillStyle="rgba(255,255,255,0.62)";let a="VIRTUAL";e.fillText(a,s-52-e.measureText(a).width,56),e.textBaseline="alphabetic",e.fillStyle="rgba(255,255,255,0.88)",e.font="500 30px ui-sans-serif, system-ui, sans-serif",e.fillText(o.holder||"Operator",52,n-52-34),e.fillStyle="rgba(255,255,255,0.55)",e.font="400 24px ui-monospace, Consolas, monospace";let l=(o.address||o.kappa||"").slice(-4);e.fillText(`${o.label} \xB7\xB7${l}`,52,n-52);let i=n-52-26,f=s-52-34,r=30;e.globalAlpha=.9,e.fillStyle="rgba(255,255,255,0.22)",e.beginPath(),e.arc(f-20,i,r,0,Math.PI*2),e.fill(),e.fillStyle="rgba(255,255,255,0.34)",e.beginPath(),e.arc(f+20,i,r,0,Math.PI*2),e.fill(),e.globalAlpha=1,e.fillStyle="rgba(10,10,14,0.85)",e.font="800 30px ui-sans-serif, system-ui, sans-serif",e.fillText("H",f-11,i+11),e.restore()}function A(e,o){e.width=s,e.height=n;let t=e.getContext("2d"),a=y(o.kappa||"deadbeefdeadbeefdeadbeefdeadbeef");(T[o.edition]||h)(t,a);let l=t.createRadialGradient(s/2,n/2,n*.4,s/2,n/2,s*.75);return l.addColorStop(0,"rgba(0,0,0,0)"),l.addColorStop(1,"rgba(0,0,0,0.38)"),t.fillStyle=l,t.fillRect(0,0,s,n),$(t,o),e}export{I as CARD_VERSION,k as EDITIONS,n as FACE_H,s as FACE_W,w as mintCard,A as renderFace,b as sha256hex,R as verifyCard};
b/00cfa4960b319ba1ba7291cddb20dea130a407ce8b42426218854f4f68f4dcd7 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:00cfa4960b319ba1ba7291cddb20dea130a407ce8b42426218854f4f68f4dcd7
3
+ size 2926068
b/29f6cbec51d5d103e676c312ab27151e2aa98e38f05532b3ea4adec6df0b36bb ADDED
@@ -0,0 +1 @@
 
 
1
+ import{a as d,b as y,c as w,d as k}from"./chunk-67U7RCKQ.js";import"./chunk-SQE76S5B.js";function m(e){return["L1",e.type,e.kappa,e.hashlock||"",e.preimage||"",String(e.amount??""),e.asset||"",e.fiat||"",String(e.expires||""),e.by||"",String(e.at||"")].join("|")}async function v(e,{claimSecret:s,signer:a,at:t}){let i={type:"claim",kappa:e.kappa,hashlock:e.hashlock,preimage:s,amount:e.amount,asset:e.asset,fiat:e.fiat||null,expires:e.expires,by:a.pub,at:t};return i.sig=await a.sign(m(i)),i.tid=await d(m(i)),i}async function S(e,{signer:s,at:a}){let t={type:"refund",kappa:e.kappa,expires:e.expires,by:s.pub,at:a};return t.sig=await s.sign(m(t)),t.tid=await d(m(t)),t}async function b(e,s){return!e||!e.type||!e.kappa||!e.by||!e.sig||!await w(e.by,e.sig,m(e))?!1:e.type==="claim"?!(!e.preimage||await d(e.preimage)!==e.hashlock||e.expires&&e.at>e.expires):e.type==="refund"?!!(e.expires&&s>e.expires):!1}async function g(e,s=Date.now()){let a=[];for(let o of e)await b(o,s)&&a.push(o);let t=a.filter(o=>o.type==="claim");if(t.length){t.sort((r,n)=>r.at-n.at||(r.tid<n.tid?-1:r.tid>n.tid?1:0));let o=t[0];return{state:"claimed",winner:o.tid,by:o.by,amount:o.amount,asset:o.asset,fiat:o.fiat}}let i=a.filter(o=>o.type==="refund");return i.length?(i.sort((o,r)=>o.at-r.at||(o.tid<r.tid?-1:1)),{state:"refunded",winner:i[0].tid}):{state:"open"}}function D(e){return e&&e.transitions?Object.values(e.transitions):[]}async function j(e,s,a=Date.now()){e.transitions=e.transitions||{};for(let t of s||[])e.transitions[t.tid]||await b(t,a)&&(e.transitions[t.tid]=t);return e}var x=()=>({funded:!1,mode:"custodial",committed:null,transitions:{}}),h=new Map;function p(){return{get:e=>h.get(e)||null,set:(e,s)=>h.set(e,s),has:e=>h.has(e)}}function C(e="holo-escrow"){if(typeof localStorage>"u")return p();let s=a=>e+":"+a;return{get:a=>{try{let t=localStorage.getItem(s(a));return t?JSON.parse(t):null}catch{return null}},set:(a,t)=>{try{localStorage.setItem(s(a),JSON.stringify(t))}catch{}},has:a=>{try{return localStorage.getItem(s(a))!=null}catch{return!1}}}}async function E(e,{store:s=null,wallet:a=null,chain:t="base",nowMs:i=null}={}){let o=s||p();if(!e||!e.kappa)return{ok:!1,error:"no payment to fund"};if(!await k(e))return{ok:!1,error:"this payment isn't authentically signed, refusing to fund"};if(!e.hashlock)return{ok:!1,error:"this payment has no escrow lock"};let r=i||Date.now();if(e.expires&&r>e.expires)return{ok:!1,error:"already expired"};let n=o.get(e.kappa);if(n&&n.funded)return{ok:!0,kappa:e.kappa,mode:n.mode,live:n.mode==="live",tx:n.tx,already:!0};let c=a&&typeof a.htlcFund=="function"?"live":"custodial",f=null;if(c==="live")try{f=await a.htlcFund({chain:t,amount:e.amount,token:e.asset,hashlock:e.hashlock,timeout:e.expires})}catch(l){return{ok:!1,error:String(l&&l.message||l)}}let u=n||x();return u.funded=!0,u.mode=c,u.tx=f,u.amount=e.amount,u.asset=e.asset,u.fiat=e.fiat||null,u.expires=e.expires,o.set(e.kappa,u),{ok:!0,kappa:e.kappa,mode:c,live:c==="live",tx:f}}async function F(e,{claimSecret:s=null,claimerPub:a=null,claimerSigner:t=null,store:i=null,nowMs:o=null}={}){let r=i||p(),n=o||Date.now();if(!e||!e.kappa)return{ok:!1,error:"not a payment"};if(!await k(e))return{ok:!1,error:"this link isn't authentically signed"};if(e.expires&&n>e.expires)return{ok:!1,error:"this payment has expired"};let c=s||e.claimSecret;if(!c)return{ok:!1,error:"this link has no claim key"};if(await d(c)!==e.hashlock)return{ok:!1,error:"this claim key doesn't match the payment"};let f=t||await y(),u=await v(e,{claimSecret:c,signer:f,at:n}),l=r.get(e.kappa)||x();l.funded||(l.offDevice=!0);let M=l.committed&&l.committed!==u.tid;return l.transitions[u.tid]=u,l.committed||(l.committed=u.tid),r.set(e.kappa,l),M?{ok:!1,error:"already claimed"}:{ok:!0,amount:e.amount,asset:e.asset,fiat:e.fiat||null,mode:l.mode,live:l.mode==="live",offDevice:!!l.offDevice,tid:u.tid,claimerPub:a||f.pub,custodialWallet:"\u03BA:"+e.kappa.slice(0,16)}}async function J(e,{store:s=null,signer:a=null,nowMs:t=null}={}){let i=s||p(),o=t||Date.now();if(!e||!e.kappa)return{ok:!1,error:"not a payment"};let r=i.get(e.kappa);if(!r)return{ok:!1,error:"no escrow to refund"};let n=await g(Object.values(r.transitions),o);if(n.state==="claimed")return{ok:!1,error:"already claimed, can't refund"};if(n.state==="refunded")return{ok:!1,error:"already refunded"};if(!(r.expires&&o>r.expires))return{ok:!1,error:"not yet expired, can't refund"};let c=a||await y(),f=await S(e,{signer:c,at:o});return r.transitions[f.tid]=f,i.set(e.kappa,r),{ok:!0,amount:r.amount,asset:r.asset,mode:r.mode}}async function _(e,{store:s=null,nowMs:a=null}={}){let t=s||p(),i=a||Date.now(),o=typeof e=="string"?e:e&&e.kappa;if(!o)return{state:"none"};let r=t.get(o);if(!r)return{state:"none"};let n=await g(Object.values(r.transitions),i);return n.state==="open"?{state:r.funded?"open":"none",committed:r.committed||null,canonicalWinner:null,reconciled:!1}:{state:n.state,canonicalWinner:n.winner,committed:r.committed||null,reconciled:!!(r.committed&&n.state==="claimed"&&r.committed!==n.winner),by:n.by,amount:n.amount,asset:n.asset}}function N(e,{store:s=null}={}){let a=s||p(),t=typeof e=="string"?e:e&&e.kappa,i=a.get(t);return i?D(i):[]}async function L(e,s,{store:a=null,nowMs:t=null}={}){let i=a||p(),o=t||Date.now(),r=typeof e=="string"?e:e&&e.kappa,n=i.get(r)||x();return await j(n,s||[],o),i.set(r,n),_(r,{store:i,nowMs:o})}export{F as claimEscrow,N as exportPeer,E as fund,C as localStore,L as mergePeer,J as refund,_ as status};
b/310c5b0dfd9e59f3fce276da94235913365a0e8570c2ce5d9290213ca0f3067d ADDED
@@ -0,0 +1,378 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // messenger-shadcn-ui.mjs — DROP-IN mount(el, model) rendering the Holo Messenger UX from STREAMING
2
+ // shadcn κ-components, faithful to the real app (network app-rail · Q sidebar · triage · rich rows ·
3
+ // Q chat pane · earth wallpaper · real SVG line icons), same contract as the chatscope bundle.
4
+ //
5
+ // ADDITIVE + SAFE: only takes over window.HoloMessengerUI.mount on opt-in (?ui=shadcn or
6
+ // localStorage["holo.ui.shadcn"]="1"). Default boot stays the untouched chatscope UI.
7
+ //
8
+ // PERF: memoized rows · composer owns its draft · windowed thread (≤WINDOW rows in DOM; 1M chat stays tiny).
9
+ // PASS 1 = faithful shell. Rich message content + action surface are Pass 2/3.
10
+
11
+ const WANT = (() => {
12
+ try { const u = new URL(location.href);
13
+ if (u.searchParams.get("ui") === "shadcn") return true;
14
+ if (u.searchParams.get("ui") === "chatscope") return false;
15
+ return localStorage.getItem("holo.ui.shadcn") === "1";
16
+ } catch { return false; }
17
+ })();
18
+
19
+ const WINDOW = 80;
20
+ const BRANDS = "/usr/share/brands/";
21
+ const WP = "/apps/holo-messenger/_vendor/wallpaper-default.jpg"; // the earth photo (default)
22
+ const NET_SLUG = { whatsapp: "whatsapp", telegram: "telegram", signal: "signal", imessage: "imessage", instagram: "instagram", messenger: "messenger", discord: "discord", slack: "slack", gmessages: "googlemessages", googlemessages: "googlemessages", x: "x", twitter: "x", linkedin: "linkedin", gmail: "gmail", email: "gmail", internal: null };
23
+
24
+ const CSS = `
25
+ .wa{height:100dvh;display:grid;grid-template-columns:64px minmax(300px,.46fr) minmax(0,1fr);background:#0b141a;color:#e9edef;
26
+ font:14.5px/1.5 ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}
27
+ .wa *{box-sizing:border-box}
28
+ .wa svg{display:block}
29
+ .rail{background:#111b21;border-right:1px solid #0a1015;display:flex;flex-direction:column;align-items:center;gap:7px;padding:12px 0}
30
+ .rb{position:relative;width:46px;height:46px;border-radius:15px;display:grid;place-items:center;cursor:pointer;color:#aebac1;transition:background .12s,color .12s;background:transparent;border:0}
31
+ .rb:hover{background:#202c33;color:#e9edef}.rb.on{background:#2a3942;color:#00a884}
32
+ .rb img{width:28px;height:28px;border-radius:8px;object-fit:cover}
33
+ .rb .dot{position:absolute;right:5px;bottom:5px;width:10px;height:10px;border-radius:50%;background:#00d95f;border:2px solid #111b21}
34
+ .rb .bdg{position:absolute;top:-3px;right:-3px;min-width:19px;height:19px;border-radius:10px;background:#00a884;color:#0b141a;font-size:11px;font-weight:700;display:grid;place-items:center;padding:0 4px;border:2px solid #111b21}
35
+ .rsep{width:28px;height:1px;background:#22303a;margin:3px 0}
36
+ .rgrow{flex:1 1 auto}
37
+ .rav{width:40px;height:40px;border-radius:50%;overflow:hidden;cursor:pointer;background:radial-gradient(circle at 35% 30%,#7cf0c8,#2b9e7a 60%,#0b3b2e);margin-top:2px}
38
+ .side{background:#111b21;border-right:1px solid #222d34;display:flex;flex-direction:column;min-width:0}
39
+ .stitle{display:flex;align-items:center;gap:6px;padding:15px 14px 10px}
40
+ .stitle .t{font-size:23px;font-weight:600;letter-spacing:-.4px;flex:1 1 auto}
41
+ .search{padding:4px 12px 8px;position:relative}
42
+ .search .si{position:absolute;left:26px;top:50%;transform:translateY(-50%);color:#8696a0;pointer-events:none;z-index:1;display:grid}
43
+ .filt{display:flex;align-items:center;gap:8px;padding:2px 14px 8px}
44
+ .chip{display:inline-flex;align-items:center;gap:6px;background:#202c33;color:#8696a0;border:0;border-radius:999px;padding:5px 14px;font-size:13px;cursor:pointer}
45
+ .chip.on{background:#0b3b2e;color:#00d95f}
46
+ .caret{background:#202c33;color:#8696a0;border:0;border-radius:999px;width:30px;height:28px;display:grid;place-items:center;cursor:pointer;font-size:11px}
47
+ .triage{margin:2px 12px 8px;display:flex;align-items:center;gap:9px;background:linear-gradient(90deg,#0e2a24,#122a1e);color:#7cf0c8;border:1px solid #123c30;border-radius:14px;padding:9px 14px;font-size:13.5px;cursor:pointer}
48
+ .triage .sk{color:#00d95f;display:grid;flex:0 0 auto}
49
+ .chats{flex:1 1 auto;overflow-y:auto;overflow-x:hidden}
50
+ .chats::-webkit-scrollbar{width:6px}.chats::-webkit-scrollbar-thumb{background:#374248;border-radius:6px}
51
+ .row{display:flex;gap:13px;align-items:center;padding:9px 14px;cursor:pointer;position:relative}
52
+ .row:after{content:"";position:absolute;left:78px;right:0;bottom:0;height:1px;background:#1c262d}
53
+ .row:hover{background:#202c33}.row.on{background:#2a3942}.row.on:after{background:transparent}
54
+ .av{position:relative;flex:0 0 auto;border-radius:50%;overflow:hidden}
55
+ .av img{width:100%;height:100%;object-fit:cover;display:block}
56
+ .row .b{min-width:0;flex:1 1 auto}
57
+ .row .n{color:#e9edef;font-size:16px;font-weight:400;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:flex;align-items:center;gap:6px}
58
+ .row .p{color:#8696a0;font-size:13.5px;margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:flex;align-items:center;gap:3px}
59
+ .row .rc{color:#53bdeb}
60
+ .row .m{margin-left:auto;text-align:right;display:flex;flex-direction:column;align-items:flex-end;gap:6px;flex:0 0 auto}
61
+ .row .t{color:#8696a0;font-size:12px}.row.unread .t{color:#00d95f;font-weight:500}
62
+ .row .pin{color:#f15c6d;font-size:13px;transform:rotate(45deg)}
63
+ .netdot{width:16px;height:16px;border-radius:4px;object-fit:cover;flex:0 0 auto}
64
+ .main{position:relative;display:flex;flex-direction:column;min-width:0;background:#0b141a}
65
+ .wp{position:absolute;inset:0;background:#0b141a center/cover no-repeat;z-index:0}
66
+ .wp:after{content:"";position:absolute;inset:0;background:linear-gradient(rgba(11,20,26,.35),rgba(11,20,26,.5))}
67
+ .mh,.thread,.composer,.e2ewrap{position:relative;z-index:1}
68
+ .mh{height:60px;flex:0 0 auto;background:#202c33;display:flex;align-items:center;gap:14px;padding:0 16px}
69
+ .mh .who .n{font-size:16px;color:#e9edef;font-weight:500}
70
+ .mh .who .s{font-size:13px;color:#00a884;margin-top:1px}
71
+ .mh .sp{margin-left:auto}.mh .icons{display:flex;gap:2px}
72
+ .ico{width:40px;height:40px;border-radius:50%;display:grid;place-items:center;color:#aebac1;cursor:pointer;transition:background .12s;background:transparent;border:0}
73
+ .ico:hover{background:#ffffff14;color:#e9edef}
74
+ .ico.sk{color:#00d95f}
75
+ .e2ewrap{display:flex;justify-content:center;padding:14px 0 2px}
76
+ .e2e{background:#182229;color:#ffd279;font-size:12.5px;padding:6px 14px;border-radius:8px;box-shadow:0 1px .5px #0003}
77
+ .thread{flex:1 1 auto;overflow-y:auto;overflow-x:hidden;padding:10px 8% 14px;display:flex;flex-direction:column;gap:3px}
78
+ .thread::-webkit-scrollbar{width:6px}.thread::-webkit-scrollbar-thumb{background:#374248cc;border-radius:6px}
79
+ .more{align-self:center;color:#8696a0;font-size:12px;padding:8px;opacity:.85}
80
+ .day{align-self:center;background:#182229;color:#8696a0;font-size:12.5px;padding:5px 12px;border-radius:8px;margin:8px 0 6px;box-shadow:0 1px .5px #0003}
81
+ .bub{max-width:65%;padding:6px 9px 8px 10px;border-radius:8px;font-size:14.2px;line-height:1.42;box-shadow:0 1px .5px #0003;word-wrap:break-word;white-space:pre-wrap}
82
+ .in{align-self:flex-start;background:#202c33;border-top-left-radius:0}
83
+ .out{align-self:flex-end;background:#005c4b;border-top-right-radius:0}
84
+ .grp{margin-top:8px}
85
+ .bub .tt{font-size:11px;color:#ffffff8a;float:right;margin:6px 0 -3px 12px;position:relative;top:4px}
86
+ .bub .tt .rc{color:#53bdeb}
87
+ .empty{margin:auto;color:#8696a0;text-align:center;background:#182229;padding:8px 16px;border-radius:8px}
88
+ .composer{flex:0 0 auto;background:#202c33;display:flex;align-items:center;gap:6px;padding:8px 14px}
89
+ .composer .grow{flex:1 1 auto}
90
+ .send{width:44px;height:44px;border-radius:50%;background:#00a884;color:#0b141a;border:0;display:grid;place-items:center;cursor:pointer;flex:0 0 auto}
91
+ /* media */
92
+ .m-img{display:block;max-width:320px;max-height:340px;border-radius:6px;cursor:pointer;object-fit:cover}
93
+ .m-vid{position:relative;max-width:320px;border-radius:6px;overflow:hidden;cursor:pointer}
94
+ .m-vid img{display:block;width:100%;border-radius:6px}
95
+ .m-play{position:absolute;inset:0;display:grid;place-items:center}
96
+ .m-play span{width:48px;height:48px;border-radius:50%;background:#0009;color:#fff;display:grid;place-items:center;font-size:20px}
97
+ .m-file{display:flex;align-items:center;gap:10px;background:#ffffff0f;border-radius:8px;padding:9px 11px;min-width:200px;cursor:pointer}
98
+ .m-file .fi{width:38px;height:38px;border-radius:8px;background:#00a88433;color:#7cf0c8;display:grid;place-items:center;font-size:16px;flex:0 0 auto}
99
+ .m-file .fn{font-size:13.5px;color:#e9edef;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
100
+ .m-file .fs{font-size:12px;color:#8696a0;margin-top:2px}
101
+ .m-aud{display:flex;align-items:center;gap:10px;min-width:220px;padding:2px 0}
102
+ .m-aud audio{height:34px}
103
+ .lb{position:fixed;inset:0;background:#000c;z-index:60;display:grid;place-items:center;cursor:zoom-out}
104
+ .lb img,.lb video{max-width:90vw;max-height:90vh;border-radius:8px}
105
+ .lb .x{position:absolute;top:18px;right:22px;color:#fff;font-size:28px;cursor:pointer;background:0;border:0}
106
+ /* attach tray + pay sheet */
107
+ .tray{position:absolute;bottom:66px;left:14px;background:#233138;border-radius:12px;box-shadow:0 10px 34px #0009;padding:6px;z-index:6;min-width:236px}
108
+ .tray button{display:flex;align-items:center;gap:11px;width:100%;background:0;border:0;color:#e9edef;font-size:14.5px;padding:10px 12px;border-radius:8px;cursor:pointer;text-align:left}
109
+ .tray button:hover{background:#2a3942}.tray button:disabled{opacity:.5}
110
+ .tray .sep{height:1px;background:#2a3942;margin:4px 6px}
111
+ .paywrap{position:fixed;inset:0;background:#000a;z-index:60;display:grid;place-items:center}
112
+ .pay{background:#111b21;border-radius:14px;padding:20px;width:346px;max-width:90vw;box-shadow:0 20px 60px #000a}
113
+ .pay h3{margin:0 0 4px;font-size:17px}.pay .psub{color:#8696a0;font-size:13px;margin-bottom:14px}
114
+ .pay .seg{display:flex;background:#202c33;border-radius:10px;padding:3px;margin-bottom:14px}
115
+ .pay .seg button{flex:1;background:0;border:0;color:#8696a0;padding:8px;border-radius:8px;cursor:pointer;font-size:14px}
116
+ .pay .seg button.on{background:#00a884;color:#0b141a;font-weight:600}
117
+ .pay .amt{width:100%;background:#202c33;border:0;border-radius:10px;color:#e9edef;font-size:26px;text-align:center;padding:12px;margin-bottom:10px;outline:0}
118
+ .pay .chips{display:flex;gap:8px;margin-bottom:12px}
119
+ .pay .chips button{flex:1;background:#202c33;border:0;color:#e9edef;border-radius:8px;padding:9px;cursor:pointer}
120
+ .pay .chips button.on{background:#0b3b2e;color:#00d95f}
121
+ .pay .memo{width:100%;background:#202c33;border:0;border-radius:10px;color:#e9edef;padding:10px 12px;margin-bottom:12px;font-size:14px;outline:0}
122
+ .pay .err{color:#f15c6d;font-size:13px;margin-bottom:8px;text-align:center}
123
+ .pay .go{width:100%;background:#00a884;color:#0b141a;border:0;border-radius:10px;padding:12px;font-weight:600;cursor:pointer;font-size:15px}
124
+ .pay .go:disabled{opacity:.5;cursor:default}
125
+ .pay .cancel{width:100%;background:0;color:#8696a0;border:0;padding:10px;margin-top:6px;cursor:pointer}
126
+ `;
127
+
128
+ async function ensureImportmap() {
129
+ if (document.querySelector('script[type="importmap"][data-holo-ui]')) return;
130
+ const map = await (await fetch("/apps/ui/vendor/importmap.json")).json();
131
+ const imports = {}; for (const [k, v] of Object.entries(map.imports || map)) imports[k] = typeof v === "string" ? v.replace(/^\.\//, "/apps/ui/") : v;
132
+ const s = document.createElement("script"); s.type = "importmap"; s.setAttribute("data-holo-ui", "1"); s.textContent = JSON.stringify({ imports }); document.head.appendChild(s);
133
+ }
134
+ let _deps = null;
135
+ async function deps() {
136
+ if (_deps) return _deps;
137
+ await ensureImportmap();
138
+ const React = await import("react");
139
+ const { createRoot } = await import("react-dom/client");
140
+ const reg = await (await fetch("/apps/ui/registry/index.json")).json();
141
+ const K = Object.fromEntries(reg.components.map((c) => [c.name, c.holo]));
142
+ const [button, badge, avatar, input] = await Promise.all(["button", "badge", "avatar", "input"].map((n) => import(K[n])));
143
+ return (_deps = { React, createRoot, C: { button, badge, avatar, input } });
144
+ }
145
+ if (!document.getElementById("wa-shadcn-css")) { const st = document.createElement("style"); st.id = "wa-shadcn-css"; st.textContent = CSS; document.head.appendChild(st); }
146
+
147
+ const initials = (s) => (String(s || "?").trim().split(/\s+/).map((w) => w[0]).join("").slice(0, 2) || "?").toUpperCase();
148
+ const hue = (s) => { let h = 0; s = String(s || ""); for (let i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) >>> 0; return `hsl(${h % 360} 40% 46%)`; };
149
+ const isOut = (m) => !!(m && (m.mine || m.out || m.fromMe || m.dir === "out" || m.from === "me" || m.self));
150
+ const threadOf = (model, id) => { try { if (id && model.thread) return model.thread(id) || []; } catch {} return (model.threads && model.threads[id]) || []; };
151
+ const rcMark = (m) => m.status === "read" || m.status === "delivered" ? "✓✓" : m.status === "sent" ? "✓" : "";
152
+ const slugOf = (net) => { const k = String(net || "").toLowerCase(); return NET_SLUG[k] === undefined ? k : NET_SLUG[k]; };
153
+ const mimeKind = (mime, kind) => { const k = String(kind || "").toLowerCase(); if (["image", "video", "audio", "voice"].includes(k)) return k === "voice" ? "audio" : k; const t = String(mime || "").split("/")[0]; if (t === "image" || t === "video" || t === "audio") return t; return "file"; };
154
+ const humanSize = (n) => { n = +n || 0; if (!n) return ""; const u = ["B", "KB", "MB", "GB"]; let i = 0; while (n >= 1024 && i < u.length - 1) { n /= 1024; i++; } return (n < 10 && i ? n.toFixed(1) : Math.round(n)) + " " + u[i]; };
155
+ const fileGlyph = (mime, name) => { const m = String(mime || ""), e = String(name || "").split(".").pop().toLowerCase(); if (m.includes("pdf") || e === "pdf") return "📕"; if (["zip", "rar", "7z"].includes(e)) return "🗜"; if (["doc", "docx"].includes(e)) return "📘"; if (["xls", "xlsx", "csv"].includes(e)) return "📗"; return "📄"; };
156
+
157
+ function buildApp({ React, C }) {
158
+ const { createElement: h, memo, useState, useRef, useEffect, useCallback, useLayoutEffect } = React;
159
+ const Avatar = C.avatar.Avatar, AvatarImage = C.avatar.AvatarImage, AvatarFallback = C.avatar.AvatarFallback;
160
+ const Button = C.button.Button, Input = C.input.Input, Badge = C.badge.Badge;
161
+
162
+ // exact SVG line icons (from the real app's IC set)
163
+ const svg = (kids, s = 22, w = 1.8) => h("svg", { viewBox: "0 0 24 24", width: s, height: s, fill: "none", stroke: "currentColor", strokeWidth: w, strokeLinecap: "round", strokeLinejoin: "round" }, ...kids);
164
+ const P = (d, e) => h("path", { d, ...e });
165
+ const CIR = (cx, cy, r, e) => h("circle", { cx, cy, r, ...e });
166
+ const RC = (x, y, width, height, rx, e) => h("rect", { x, y, width, height, rx, ...e });
167
+ const fillC = { fill: "currentColor", stroke: "none" };
168
+ const IC = {
169
+ chats: () => svg([P("M20 11.4A7.6 7.6 0 0 1 8.9 18.2L4.5 19.4l1.2-4.2A7.6 7.6 0 1 1 20 11.4Z")]),
170
+ search: (s) => svg([CIR(11, 11, 7), P("m20 20-3.6-3.6")], s || 19),
171
+ phone: () => svg([P("M6.6 4H4.5A1.5 1.5 0 0 0 3 5.6 16 16 0 0 0 18.4 21a1.5 1.5 0 0 0 1.6-1.5v-2.1a1.5 1.5 0 0 0-1.2-1.45l-2.2-.45a1.5 1.5 0 0 0-1.5.6l-.5.7a12 12 0 0 1-5.1-5.1l.7-.5a1.5 1.5 0 0 0 .6-1.5l-.45-2.2A1.5 1.5 0 0 0 6.6 4Z")]),
172
+ video: () => svg([RC(3, 6.5, 12, 11, 2.5), P("m15 10.5 5.5-3v9l-5.5-3Z")]),
173
+ menu: () => svg([CIR(12, 5, 1.35, fillC), CIR(12, 12, 1.35, fillC), CIR(12, 19, 1.35, fillC)]),
174
+ newchat: () => svg([P("M13.5 4H6a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-7.5"), P("M18.4 3.6a2 2 0 0 1 2.8 2.8L13 14.6l-3.4.9.9-3.4 7.9-8.5Z")]),
175
+ emoji: () => svg([CIR(12, 12, 8.5), P("M8.8 14.2a4.2 4.2 0 0 0 6.4 0"), CIR(9, 10, 1, fillC), CIR(15, 10, 1, fillC)]),
176
+ plus: () => svg([P("M12 5.5v13M5.5 12h13")]),
177
+ mic: () => svg([RC(9, 3, 6, 11, 3), P("M5.5 11a6.5 6.5 0 0 0 13 0M12 17.5V21M8.5 21h7")]),
178
+ send: () => svg([P("M4.5 12 20.5 4.5 13 20.5l-2.8-6.4L4.5 12Z")]),
179
+ spark: (s) => svg([P("M12 3.2l1.7 4.6 4.6 1.7-4.6 1.7L12 15.8l-1.7-4.6L5.7 9.5l4.6-1.7L12 3.2Z"), P("M5.5 15.5l.8 2 2 .8-2 .8-.8 2-.8-2-2-.8 2-.8.8-2Z")], s || 20),
180
+ sun: () => svg([CIR(12, 12, 4), P("M12 2v2M12 20v2M4 12H2M22 12h-2M5.6 5.6 4.2 4.2M19.8 19.8l-1.4-1.4M18.4 5.6l1.4-1.4M4.2 19.8l1.4-1.4")]),
181
+ plusThin: () => svg([P("M12 5.5v13M5.5 12h13")], 26, 1.6),
182
+ };
183
+
184
+ const Av = (name, size, bg, src) => h("div", { className: "av", style: { height: size + "px", width: size + "px" } },
185
+ h(Avatar, { style: { height: "100%", width: "100%", background: bg || hue(name), display: "grid", placeItems: "center" } },
186
+ src ? h(AvatarImage, { src, alt: "", style: { height: "100%", width: "100%", objectFit: "cover" } }) : null,
187
+ h(AvatarFallback, { style: { background: bg || hue(name), color: "#fff", fontSize: (size * 0.36) + "px", fontWeight: 500, height: "100%", width: "100%", display: "grid", placeItems: "center" } }, initials(name))));
188
+ const Ico = (icon, onClick, title, cls) => h("button", { className: "ico" + (cls ? " " + cls : ""), onClick, title }, icon);
189
+ const netLogo = (net) => { const s = slugOf(net); return s ? h("img", { className: "netdot", src: BRANDS + s + ".svg", alt: "", onError: (e) => { e.currentTarget.style.display = "none"; } }) : null; };
190
+
191
+ // rich media in a bubble — image/video/audio/file, with lazy bridge-media resolution
192
+ function MediaView({ media, model, onMedia }) {
193
+ const [url, setUrl] = useState(media.url || media.thumb || "");
194
+ const bucket = mimeKind(media.mime, media.kind);
195
+ useEffect(() => { let alive = true;
196
+ if (!media.url && (media.lazy || media.pending || media.ref) && model.resolveBridgeMedia) {
197
+ Promise.resolve(model.resolveBridgeMedia(media)).then((u) => { if (alive && u) setUrl(typeof u === "string" ? u : (u.url || u.src || "")); }).catch(() => {});
198
+ } return () => { alive = false; }; }, []);
199
+ if (bucket === "image") return url ? h("img", { className: "m-img", src: url, alt: "", loading: "lazy", onClick: () => onMedia({ kind: "image", url }) }) : h("div", { className: "m-file" }, "🖼 Photo");
200
+ if (bucket === "video") return h("div", { className: "m-vid", onClick: () => onMedia({ kind: "video", url }) }, h("img", { src: media.thumb || url, alt: "" }), h("div", { className: "m-play" }, h("span", {}, "▶")));
201
+ if (bucket === "audio") return h("div", { className: "m-aud" }, url ? h("audio", { controls: true, src: url }) : h("span", {}, "🎤 Voice message"));
202
+ return h("div", { className: "m-file", onClick: () => url && window.open(url, "_blank") }, h("div", { className: "fi" }, fileGlyph(media.mime, media.filename)), h("div", { style: { minWidth: 0 } }, h("div", { className: "fn" }, media.filename || "File"), media.size ? h("div", { className: "fs" }, humanSize(media.size)) : null));
203
+ }
204
+
205
+ const Rail = memo(function Rail({ nets, active, totalUnread, onPick }) {
206
+ return h("nav", { className: "rail" },
207
+ h("button", { className: "rb" + (active === "All" ? " on" : ""), title: "All chats", onClick: () => onPick("All") }, IC.chats(), totalUnread ? h("span", { className: "bdg" }, totalUnread > 99 ? "99+" : totalUnread) : null),
208
+ nets.length ? h("div", { className: "rsep" }) : null,
209
+ nets.map((n) => h("button", { key: n.id, className: "rb" + (active === n.id ? " on" : ""), title: n.label, onClick: () => onPick(n.id) },
210
+ (() => { const s = slugOf(n.id); return s ? h("img", { src: BRANDS + s + ".svg", alt: n.label, onError: (e) => { const t = document.createTextNode((n.label || "?")[0].toUpperCase()); try { e.currentTarget.replaceWith(t); } catch {} } }) : (n.label || "?")[0].toUpperCase(); })(),
211
+ h("span", { className: "dot" }), n.unread ? h("span", { className: "bdg" }, n.unread > 99 ? "99+" : n.unread) : null)),
212
+ h("button", { className: "rb", title: "Add network", onClick: () => onPick("+") }, IC.plusThin()),
213
+ h("div", { className: "rgrow" }),
214
+ h("div", { className: "rav" }));
215
+ });
216
+
217
+ const Row = memo(function Row({ c, on, preview, receipt, onSelect }) {
218
+ return h("div", { className: "row" + (on ? " on" : "") + (c.unread ? " unread" : ""), onClick: () => onSelect(c.id) },
219
+ Av(c.name, 49, c.isQ ? "#12b886" : hue(c.name), c.avatar),
220
+ h("div", { className: "b" },
221
+ h("div", { className: "n" }, c.name || c.id || "Chat", c.network ? netLogo(c.network) : null),
222
+ h("div", { className: "p" }, receipt ? h("span", { className: "rc" }, receipt + " ") : null, preview)),
223
+ h("div", { className: "m" },
224
+ h("div", { className: "t" }, c.time || c.ts || ""),
225
+ c.pinned ? h("span", { className: "pin" }, "📌") : null,
226
+ c.unread ? h(Badge, { style: { background: "#00a884", color: "#0b141a", borderRadius: "999px", height: "20px", minWidth: "20px", justifyContent: "center", padding: "0 6px", fontSize: "12px", fontWeight: 600 } }, String(c.unread)) : null));
227
+ });
228
+
229
+ function Thread({ model, activeId, onMedia }) {
230
+ const all = threadOf(model, activeId);
231
+ const [limit, setLimit] = useState(WINDOW);
232
+ const ref = useRef(null), prevId = useRef(activeId), anchorH = useRef(0), pin = useRef(true);
233
+ const toBottom = (el) => { el.scrollTop = el.scrollHeight; };
234
+ useEffect(() => { setLimit(WINDOW); }, [activeId]);
235
+ // Runs after every render (no deps): a fresh chat, or new messages arriving, both flow through here.
236
+ // While "pinned" we stay glued to the newest message — exactly like WhatsApp.
237
+ useLayoutEffect(() => { const el = ref.current; if (!el) return;
238
+ if (prevId.current !== activeId) { prevId.current = activeId; pin.current = true; anchorH.current = 0; toBottom(el); return; }
239
+ if (anchorH.current) { el.scrollTop = el.scrollHeight - anchorH.current; anchorH.current = 0; return; }
240
+ if (pin.current) toBottom(el); });
241
+ // Media resolves async (placeholder first, <img> swapped in later) and reflows the thread AFTER layout.
242
+ // A ResizeObserver on the scroll container won't catch it (its own box size never changes), and per-image
243
+ // listeners miss images added later — so listen for `load` in the CAPTURE phase on the container, which
244
+ // fires for ANY descendant image whenever it appears/loads. Keeps us glued to the newest message.
245
+ useEffect(() => { const el = ref.current; if (!el) return; pin.current = true; toBottom(el);
246
+ requestAnimationFrame(() => { if (pin.current && ref.current) toBottom(ref.current); });
247
+ const stick = () => { if (pin.current && ref.current) toBottom(ref.current); };
248
+ el.addEventListener("load", stick, true); el.addEventListener("error", stick, true);
249
+ return () => { el.removeEventListener("load", stick, true); el.removeEventListener("error", stick, true); };
250
+ }, [activeId]);
251
+ // Detach when the user scrolls up; re-attach ("jump to latest") once they're back near the bottom.
252
+ const onScroll = useCallback((e) => { const el = e.currentTarget;
253
+ pin.current = el.scrollHeight - el.scrollTop - el.clientHeight < 80;
254
+ if (el.scrollTop < 120 && limit < all.length) { anchorH.current = el.scrollHeight; setLimit((l) => Math.min(all.length, l + WINDOW)); } }, [limit, all.length]);
255
+ const start = Math.max(0, all.length - limit), shown = all.slice(start);
256
+ if (!all.length) return h("div", { className: "thread", ref }, h("div", { className: "empty" }, "No messages yet. Say hello 👋"));
257
+ const rows = [h("div", { className: "e2ewrap", key: "e2e" }, h("div", { className: "e2e" }, "🔒 Messages are end-to-end encrypted")), h("div", { className: "day", key: "day" }, "Today")];
258
+ if (start > 0) rows.push(h("div", { className: "more", key: "more" }, `↑ ${start.toLocaleString()} earlier — scroll up`));
259
+ let lastOut = null;
260
+ shown.forEach((m, i) => { const out = isOut(m); const hasMedia = m.media && (m.media.url || m.media.thumb || m.media.ref || m.media.lazy || m.media.kind);
261
+ rows.push(h("div", { key: start + i, className: "bub " + (out ? "out" : "in") + (out !== lastOut ? " grp" : ""), style: hasMedia ? { maxWidth: "min(360px,72%)" } : null },
262
+ hasMedia ? h(MediaView, { media: m.media, model, onMedia }) : null,
263
+ m.text ? h("div", { style: hasMedia ? { marginTop: "5px" } : null }, m.text) : null,
264
+ h("span", { className: "tt" }, (m.time || m.ts || ""), out ? h("span", { className: "rc" }, " " + rcMark(m)) : null)));
265
+ lastOut = out; });
266
+ return h("div", { className: "thread", ref, onScroll }, rows);
267
+ }
268
+
269
+ const Composer = memo(function Composer({ activeId, onSend, onPlus }) {
270
+ const [draft, setDraft] = useState("");
271
+ const send = () => { const t = draft.trim(); if (!t) return; onSend(activeId, t); setDraft(""); };
272
+ return h("div", { className: "composer" },
273
+ Ico(IC.plus(), onPlus, "Attach"),
274
+ h("div", { className: "grow" }, h(Input, { placeholder: "Type a message", value: draft, autoFocus: true,
275
+ onChange: (e) => setDraft(e.target.value), onKeyDown: (e) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); send(); } },
276
+ style: { background: "#2a3942", border: "0", borderRadius: "10px", height: "44px", color: "#e9edef", paddingLeft: "16px" } })),
277
+ h("button", { className: "send", onClick: send, "aria-label": "Send" }, draft.trim() ? IC.send() : IC.mic()));
278
+ });
279
+
280
+ const TrayBtn = (glyph, label, onClick, disabled) => h("button", { onClick, disabled }, h("span", { style: { fontSize: "17px" } }, glyph), label);
281
+
282
+ function PaySheet({ model, activeId, conv, sheet, setSheet }) {
283
+ const set = (patch) => setSheet((s) => ({ ...s, ...patch, err: null }));
284
+ const amt = parseFloat(sheet.amount);
285
+ const go = async () => { set({ busy: true }); let r;
286
+ try { r = await model.holoPay(sheet.genesis || activeId, { kind: sheet.kind, amount: amt, memo: (sheet.memo || "").trim() }); } catch (e) { r = { ok: false, error: String(e && e.message || e) }; }
287
+ if (r && r.ok) setSheet(null); else setSheet((s) => ({ ...s, busy: false, err: (r && r.error) || "Couldn't create the payment" })); };
288
+ return h("div", { className: "paywrap", onClick: () => setSheet(null) },
289
+ h("div", { className: "pay", onClick: (e) => e.stopPropagation() },
290
+ h("h3", {}, sheet.kind === "send" ? "Send money" : "Request money"), h("div", { className: "psub" }, (sheet.kind === "send" ? "To " : "From ") + (conv.name || "this chat")),
291
+ h("div", { className: "seg" }, h("button", { className: sheet.kind === "send" ? "on" : "", onClick: () => set({ kind: "send" }) }, "Send"), h("button", { className: sheet.kind === "request" ? "on" : "", onClick: () => set({ kind: "request" }) }, "Request")),
292
+ h("input", { className: "amt", inputMode: "decimal", placeholder: "$0", value: sheet.amount, onChange: (e) => set({ amount: e.target.value.replace(/[^0-9.]/g, "") }), autoFocus: true }),
293
+ h("div", { className: "chips" }, [10, 20, 50, 100].map((a) => h("button", { key: a, className: amt === a ? "on" : "", onClick: () => set({ amount: String(a) }) }, "$" + a))),
294
+ h("input", { className: "memo", placeholder: "What's it for? (optional)", maxLength: 120, value: sheet.memo || "", onChange: (e) => set({ memo: e.target.value }) }),
295
+ sheet.err ? h("div", { className: "err" }, sheet.err) : null,
296
+ h("button", { className: "go", disabled: sheet.busy || !(amt > 0), onClick: go }, sheet.busy ? (sheet.kind === "send" ? "Confirm in your wallet…" : "Creating…") : ((sheet.kind === "send" ? "Send" : "Request") + (amt > 0 ? " $" + sheet.amount : " money"))),
297
+ h("button", { className: "cancel", onClick: () => setSheet(null) }, "Cancel")));
298
+ }
299
+
300
+ const previewOf = (model, c) => { if (c.preview) return c.preview; const t = threadOf(model, c.id); const last = t.length ? t[t.length - 1] : null; return (last && (last.text || (last.media ? "📎 media" : ""))) || ""; };
301
+
302
+ return function App({ model }) {
303
+ const convs = Array.isArray(model.conversations) ? model.conversations : [];
304
+ const [activeId, setActiveId] = useState(null);
305
+ const [filter, setFilter] = useState("All");
306
+ const [q, setQ] = useState("");
307
+ const [lb, setLb] = useState(null);
308
+ const [attachOpen, setAttachOpen] = useState(false);
309
+ const [paySheet, setPaySheet] = useState(null);
310
+ const fileRef = useRef(null);
311
+ const onSelect = useCallback((id) => { setActiveId(id); setAttachOpen(false); }, []);
312
+ const active = (activeId && convs.some((c) => c.id === activeId)) ? activeId : ((convs.find((c) => c.active) || convs[0] || {}).id || null);
313
+ const conv = convs.find((c) => c.id === active) || {};
314
+ const onSend = useCallback((id, text) => { try { model.onSend && model.onSend(id, text); } catch (e) { console.error("[shadcn-ui] onSend", e); } }, [model]);
315
+ const call = useCallback((video) => { try { model.startCall && model.startCall(active, { video }); } catch (e) { console.error("[shadcn-ui] startCall", e); } }, [model, active]);
316
+ const pickFile = (accept) => { if (fileRef.current) { fileRef.current.accept = accept; fileRef.current.click(); } setAttachOpen(false); };
317
+ const onFile = (e) => { const f = e.target.files && e.target.files[0]; if (f) { try { model.onAttach && model.onAttach(active, f); } catch (er) { console.error("[shadcn-ui] onAttach", er); } } e.target.value = ""; };
318
+ const tryM = (fn, ...a) => { setAttachOpen(false); try { const r = model[fn] && model[fn](...a); if (r && r.catch) r.catch(() => {}); } catch (e) { console.error("[shadcn-ui] " + fn, e); } };
319
+ const nets = Array.isArray(model.networks) && model.networks.length ? model.networks
320
+ : Object.values(convs.reduce((a, c) => { const id = (c.network || c.platform); if (id && !a[id]) a[id] = { id, label: id[0].toUpperCase() + id.slice(1), unread: 0 }; if (id) a[id].unread += (c.unread || 0); return a; }, {}));
321
+ const totalUnread = convs.reduce((n, c) => n + (c.unread || 0), 0);
322
+ const signalUnread = model.signalUnread != null ? model.signalUnread : convs.filter((c) => c.unread).length;
323
+ const qs = q.trim().toLowerCase();
324
+ const shown = convs.filter((c) => (filter === "All" || (c.network || c.platform) === filter) && (!qs || (c.name || "").toLowerCase().includes(qs) || (previewOf(model, c) || "").toLowerCase().includes(qs)));
325
+ const sub = conv.presence || (conv.isQ ? "online · on your device" : (conv.status || conv.network || conv.platform || ""));
326
+ const runSearch = () => { const t = q.trim(); if (!t) return; try { if (model.qCommand) model.qCommand(t); else if (model.onNewChat) model.onNewChat(t); } catch {} };
327
+
328
+ return h("div", { className: "wa" },
329
+ h(Rail, { nets, active: filter, totalUnread, onPick: (id) => { if (id === "+") { try { model.openNetworks && model.openNetworks(); } catch {} } else setFilter(id); } }),
330
+ h("div", { className: "side" },
331
+ h("div", { className: "stitle" }, h("span", { className: "t" }, "Messenger"),
332
+ model.askQ ? Ico(IC.spark(), () => { try { model.askQ(); } catch {} }, "Ask Q", "sk") : null, Ico(IC.newchat(), () => { try { model.onNewChat && model.onNewChat(""); } catch {} }, "New chat")),
333
+ h("div", { className: "search" }, h("span", { className: "si" }, IC.search()),
334
+ h(Input, { placeholder: "Search, ask Q, or start a chat…", value: q, onChange: (e) => setQ(e.target.value), onKeyDown: (e) => { if (e.key === "Enter") runSearch(); },
335
+ style: { background: "#202c33", border: "0", borderRadius: "10px", height: "42px", color: "#e9edef", paddingLeft: "40px", fontSize: "14.5px" } })),
336
+ signalUnread ? h("div", { className: "triage" }, h("span", { className: "sk" }, IC.spark(18)), `${signalUnread} need you · ~${Math.max(1, Math.round(signalUnread * 0.4))} min`) : null,
337
+ h("div", { className: "chats" }, shown.map((c) => h(Row, { key: c.id, c, on: c.id === active, preview: previewOf(model, c), receipt: c.receipt || "", onSelect })))),
338
+ h("div", { className: "main" },
339
+ h("div", { className: "wp", style: { backgroundImage: `url(${WP})` } }),
340
+ h("div", { className: "mh" }, Av(conv.name, 40, conv.isQ ? "#12b886" : hue(conv.name), conv.avatar),
341
+ h("div", { className: "who" }, h("div", { className: "n" }, conv.name || ""), sub ? h("div", { className: "s" }, sub) : null),
342
+ h("div", { className: "sp" }), h("div", { className: "icons" }, Ico(IC.video(), () => call(true), "Video call"), Ico(IC.phone(), () => call(false), "Voice call"), Ico(IC.menu(), () => { try { model.onFavourite && model.onFavourite(active); } catch {} }, "Menu"))),
343
+ h(Thread, { model, activeId: active, onMedia: setLb }),
344
+ h("input", { ref: fileRef, type: "file", style: { display: "none" }, onChange: onFile }),
345
+ attachOpen ? h("div", { className: "tray" },
346
+ TrayBtn("🖼", "Photos & videos", () => pickFile("image/*,video/*")),
347
+ TrayBtn("📄", "Document", () => pickFile("*/*")),
348
+ (conv.network && !conv.isQ && model.holoPay) ? TrayBtn("💸", "Send money", () => { setAttachOpen(false); setPaySheet({ kind: "send", amount: "", memo: "", busy: false, err: null }); }) : null,
349
+ h("div", { className: "sep" }),
350
+ model.startMeet ? TrayBtn("👥", "Start a meeting", () => tryM("startMeet", active, { video: true })) : null,
351
+ model.startTogether ? TrayBtn("🖥", "Share my screen", () => tryM("startTogether", active, { kind: "tab", title: "" })) : null,
352
+ model.startTogether ? TrayBtn("📝", "Co-edit a doc", () => tryM("startTogether", active, { kind: "doc", title: "" })) : null,
353
+ (conv.network && !conv.isQ && model.qDraft) ? [h("div", { className: "sep", key: "s" }), TrayBtn("✨", "Draft a reply with Q", () => tryM("qDraft", active))] : null) : null,
354
+ h(Composer, { key: active, activeId: active, onSend, onPlus: () => setAttachOpen((v) => !v) })),
355
+ lb ? h("div", { className: "lb", onClick: () => setLb(null) }, h("button", { className: "x", "aria-label": "Close" }, "✕"),
356
+ lb.kind === "video" ? h("video", { src: lb.url, controls: true, autoPlay: true, onClick: (e) => e.stopPropagation() }) : h("img", { src: lb.url, alt: "", onClick: (e) => e.stopPropagation() })) : null,
357
+ paySheet ? h(PaySheet, { model, activeId: active, conv, sheet: paySheet, setSheet: setPaySheet }) : null);
358
+ };
359
+ }
360
+
361
+ async function mount(el, model) {
362
+ const d = await deps();
363
+ const App = buildApp(d);
364
+ let cur = model;
365
+ const root = d.createRoot(el);
366
+ const render = (mdl) => { cur = mdl || cur; root.render(d.React.createElement(App, { model: cur })); };
367
+ render(model);
368
+ console.log("[shadcn-ui] Holo Messenger (Pass 1) mounted from κ-components ·", (model.conversations || []).length, "chats");
369
+ return { update: render, unmount: () => { try { root.unmount(); } catch {} } };
370
+ }
371
+
372
+ if (WANT && typeof window !== "undefined") {
373
+ const prev = window.HoloMessengerUI;
374
+ window.HoloMessengerUI = { mount, version: ((prev && prev.version) || "m1") + "+shadcn", _chatscope: prev };
375
+ console.log("[shadcn-ui] streaming-κ shadcn UI ACTIVE (opt out: ?ui=chatscope)");
376
+ }
377
+
378
+ export { mount };
b/331a35dfd2b4774673ed92da26b0c78fecd336a70ecc8ff890256e835f7ccfd9 ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // holo-m1-boot.mjs — M1: INSTANT SERVERLESS BOOT. One self-verifying link boots the WHOLE messenger.
2
+ //
3
+ // A single web link that, opened in a COLD browser, brings up the entire Holo Messenger — shell + your
4
+ // identity + Q — with ZERO origin servers in the delivery path (every shell byte is a content-addressed κ
5
+ // object, served from cache/OPFS on repeat opens). The descriptor travels INSIDE the link (b64url JSON in
6
+ // the URL fragment), exactly like holo-chat-link.mjs / holo-together.mjs: a bare browser verifies it LOCALLY
7
+ // (Law L5: re-derive the κ), then boots. The κ is SHA-256 of the canonical PUBLIC fields — the shareable,
8
+ // tamper-evident id. Any private pairing bearer is EXCLUDED from the κ (whoever holds the link may adopt it).
9
+ //
10
+ // This commits to the SHELL MANIFEST (the exact asset set the service worker precaches), so a tampered link
11
+ // that points the boot at a different/injected asset set breaks integrity and is refused. Laws: L2 canonical,
12
+ // L5 verify-by-re-derivation. Additive + fail-soft: if anything here throws, app.html's normal boot proceeds.
13
+
14
+ export const BOOT_LINK_VERSION = 1;
15
+ const ENTRY_PATH = "/apps/holo-messenger/app.html";
16
+
17
+ // THE SHELL — the minimal asset set required to paint the messenger and reach interactive, and the single
18
+ // source of truth the service worker precaches (messenger-sw.js mirrors these basenames; the M1 witness
19
+ // asserts the two never drift). Q's brain, the bridges, and all media are DELIBERATELY absent: they are
20
+ // deferred (never on the first-paint critical path), so the shell stays tiny and cache-resident.
21
+ export const SHELL_MANIFEST = [
22
+ "/apps/holo-messenger/app.html",
23
+ "/apps/holo-messenger/_vendor/ui/chat-ui.bundle.js",
24
+ "/apps/holo-messenger/_vendor/ui/chat-ui.bundle.css",
25
+ "/apps/holo-messenger/holo-messenger-login.mjs",
26
+ "/apps/holo-messenger/holo-messenger-app.mjs",
27
+ "/apps/holo-messenger/messenger-shadcn-ui.mjs",
28
+ "/apps/holo-messenger/holo-messenger-weave.mjs",
29
+ "/apps/holo-messenger/messenger-skin.mjs",
30
+ "/apps/holo-messenger/messenger-skins.css",
31
+ "/apps/holo-messenger/messenger-sw-register.mjs",
32
+ "/apps/holo-messenger/holo-m1-boot.mjs",
33
+ "/apps/holo-messenger/holo-release-boot.mjs",
34
+ "/apps/holo-messenger/holo-release-verify.mjs",
35
+ "/apps/holo-messenger/mail/holo-mail-attach.mjs",
36
+ "/apps/holo-messenger/_vendor/wallpaper-default.jpg",
37
+ ];
38
+
39
+ const _te = new TextEncoder();
40
+ async function _sha256hex(s) { const h = await (globalThis.crypto || crypto).subtle.digest("SHA-256", _te.encode(s)); return [...new Uint8Array(h)].map((x) => x.toString(16).padStart(2, "0")).join(""); }
41
+ function _b64urlEncode(obj) { const b = btoa(unescape(encodeURIComponent(JSON.stringify(obj)))); return b.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); }
42
+ function _b64urlDecode(str) { const b = String(str).replace(/-/g, "+").replace(/_/g, "/"); return JSON.parse(decodeURIComponent(escape(atob(b)))); }
43
+
44
+ // The shell manifest (shell-manifest.json, minted by _build/gen-shell-manifest.mjs) is the content hash of
45
+ // every shell BYTE: [{ path, kappa=SHA-256(bytes) }]. In a browser we fetch it; in Node the caller passes it.
46
+ export async function loadShellManifest() {
47
+ try {
48
+ if (typeof fetch === "function" && typeof location !== "undefined") {
49
+ const r = await fetch(new URL("./shell-manifest.json", import.meta.url), { cache: "no-store" }); // module-relative: same file at the OS root or any static mount
50
+ if (r && r.ok) return await r.json();
51
+ }
52
+ } catch {}
53
+ return null;
54
+ }
55
+
56
+ // The ONE hash the boot link commits: SHA-256 over the SORTED per-asset content κs. Any browser re-derives it
57
+ // from shell-manifest.json → thereby verifies EVERY shell byte (Law L5). Recomputed from `assets`, never trusting
58
+ // the manifest's self-declared `aggregate`, so a forged manifest fails. Empty (unverifiable) → no commitment.
59
+ export async function shellAggregate(manifest) {
60
+ const m = manifest || (await loadShellManifest());
61
+ if (!m || !Array.isArray(m.assets) || !m.assets.length) return "";
62
+ return _sha256hex(m.assets.map((a) => String(a.kappa)).sort().join(","));
63
+ }
64
+
65
+ // canonical PUBLIC form — any private `pair` bearer is EXCLUDED so the κ is the shareable id. Commits to the
66
+ // entry, the shell set, the display identity (so a tampered "this is <who>'s messenger" card breaks — anti-phish).
67
+ function _canon(d) {
68
+ return [
69
+ "v" + (d.v | 0), d.kind || "boot", d.app || "holo-messenger", d.entry || ENTRY_PATH,
70
+ d.shell || "", d.name || "", d.truename || "", d.op || "",
71
+ String(d.created || 0), String(d.expires || 0),
72
+ ].join("␟");
73
+ }
74
+
75
+ // Build the one-link door for the whole messenger. `op`/`name`/`truename` are PUBLIC display identity (the
76
+ // landing shows "resume <name>'s messenger"); `pair` is an optional bearer pairing payload (device-roam) and
77
+ // is NOT part of the κ. Opening the link still auto-enrolls a sovereign identity unless a pair bearer is honored.
78
+ export async function makeBootLink({ op = "", name = "", truename = "", pair = null, ttlSeconds = 30 * 24 * 3600, origin = null, nowMs = null, aggregate = null, manifest = null } = {}) {
79
+ const now = nowMs || Date.now();
80
+ const shell = aggregate || (await shellAggregate(manifest)); // commit the shell BYTES (aggregate content κ)
81
+ const d = {
82
+ v: BOOT_LINK_VERSION, kind: "boot", app: "holo-messenger", entry: ENTRY_PATH,
83
+ shell,
84
+ op: String(op || "").slice(0, 96), name: String(name || "").slice(0, 60), truename: String(truename || "").slice(0, 60),
85
+ created: now, expires: now + ttlSeconds * 1000,
86
+ };
87
+ if (pair) d.pair = pair; // bearer pairing payload — NOT part of the κ
88
+ d.kappa = await _sha256hex(_canon(d));
89
+ const payload = _b64urlEncode(d);
90
+ const org = origin || (typeof location !== "undefined" ? location.origin : "");
91
+ return { kappa: d.kappa, payload, descriptor: d,
92
+ https: `${org}${ENTRY_PATH}#m1=${payload}`, // opens in ANY cold browser
93
+ holo: `holo://os${ENTRY_PATH}#m1=${payload}` }; // in-shell full surface
94
+ }
95
+
96
+ // parse a boot link (URL, holo:// form, `#m1=<payload>`, or bare payload) → { ok, descriptor, integrity, expired }.
97
+ export async function parseBootLink(input, { nowMs = null, aggregate = null, manifest = null } = {}) {
98
+ let payload = String(input || "");
99
+ if (payload.includes("#")) payload = payload.split("#").pop();
100
+ payload = payload.replace(/^m1=/, "");
101
+ let d; try { d = _b64urlDecode(payload); } catch { return { ok: false, error: "unreadable boot link" }; }
102
+ if (!d || d.v !== BOOT_LINK_VERSION || d.kind !== "boot" || d.app !== "holo-messenger" || !d.kappa) return { ok: false, error: "not a valid messenger boot link" };
103
+ const integrity = (await _sha256hex(_canon(d))) === d.kappa; // Law L5: re-derive; tamper → false
104
+ const current = aggregate != null ? aggregate : (await shellAggregate(manifest));// THIS build's shell-byte aggregate
105
+ const shellMatches = !!current && d.shell === current; // link's shell BYTES must match (unverifiable → false)
106
+ const expired = !!(d.expires && (nowMs || Date.now()) > d.expires);
107
+ return { ok: true, descriptor: d, integrity, shellMatches, expired, aggregate: current };
108
+ }
109
+
110
+ // convenience: verify + return the boot descriptor only if it is safe to act on (integrity + shell + fresh).
111
+ export async function resolveBootLink(input, opts = {}) {
112
+ const p = await parseBootLink(input, opts);
113
+ if (!p.ok) return { ok: false, reason: p.error };
114
+ if (!p.integrity) return { ok: false, reason: "tampered" };
115
+ if (!p.shellMatches) return { ok: false, reason: "shell-mismatch" };
116
+ if (p.expired) return { ok: false, reason: "expired" };
117
+ return { ok: true, descriptor: p.descriptor, pair: p.descriptor.pair || null };
118
+ }
119
+
120
+ export function describe(d) {
121
+ const who = (d && (d.name || d.truename)) || "someone";
122
+ return { headline: `Resume ${who}'s messenger`, cta: "Open" };
123
+ }
124
+
125
+ export function installM1Boot() {
126
+ if (typeof window === "undefined") return false;
127
+ window.HoloM1Boot = Object.assign(window.HoloM1Boot || {}, { version: BOOT_LINK_VERSION, SHELL_MANIFEST, makeBootLink, parseBootLink, resolveBootLink, describe, loadShellManifest, shellAggregate });
128
+ return true;
129
+ }
130
+ try { installM1Boot(); } catch {}
b/3370b22070b56218a57df6406e2e4ba7cc171e942d4d1f2ae4297ea2c4c08d9c ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3370b22070b56218a57df6406e2e4ba7cc171e942d4d1f2ae4297ea2c4c08d9c
3
+ size 2999771
b/438bd84d968d05dadd9f0ac1fd49a00c9d24f88a4ac45a9f46df236e7af2961e ADDED
@@ -0,0 +1 @@
 
 
1
+ function l(e){let t=e.replace(/-/g,"+").replace(/_/g,"/"),n=decodeURIComponent(escape(atob(t)));return JSON.parse(n)}var i=["v","kind","amount","asset","fiat","to","toName","from","fromName","memo","created","expires","claimSecret","pub","sig"];function u(e){let t=l(e);if(!Array.isArray(t))return t;let n={};return i.forEach((a,o)=>{t[o]!=null&&(n[a]=t[o])}),n.memo==null&&(n.memo=""),n}async function p(e){let t=new TextEncoder().encode(e),n=await crypto.subtle.digest("SHA-256",t);return[...new Uint8Array(n)].map(a=>a.toString(16).padStart(2,"0")).join("")}var y=p;function r(e){let t="";for(let n of e)t+=String.fromCharCode(n);return btoa(t).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function s(e){let t=atob(e.replace(/-/g,"+").replace(/_/g,"/")),n=new Uint8Array(t.length);for(let a=0;a<t.length;a++)n[a]=t.charCodeAt(a);return n}async function w(){let e=await crypto.subtle.generateKey({name:"ECDSA",namedCurve:"P-256"},!0,["sign","verify"]),t=new Uint8Array(await crypto.subtle.exportKey("raw",e.publicKey));return{pub:r(t),sign:async n=>r(new Uint8Array(await crypto.subtle.sign({name:"ECDSA",hash:"SHA-256"},e.privateKey,new TextEncoder().encode(n))))}}async function c(e,t,n){try{let a=await crypto.subtle.importKey("raw",s(e),{name:"ECDSA",namedCurve:"P-256"},!1,["verify"]);return await crypto.subtle.verify({name:"ECDSA",hash:"SHA-256"},a,s(t),new TextEncoder().encode(n))}catch{return!1}}var h=c;async function b(e){return!e||!e.pub||!e.sig?!1:c(e.pub,e.sig,d(e))}function d(e){let t=[e.v,e.kind,e.amount,e.asset,e.fiat||"",e.to||"",e.toName||"",e.from||"",e.fromName||"",e.memo||"",e.created||"",e.expires||""];return(e.hashlock||e.pub)&&t.push(e.hashlock||"",e.pub||""),t.join("|")}var f={USD:"$",EUR:"\u20AC",GBP:"\xA3"};function g(e){let t=Number(e.amount);return e.fiat?`${f[e.fiat]||""}${t.toFixed(2)}`:`${t%1===0?t:t.toFixed(4).replace(/0+$/,"")} ${e.asset}`}var m=/(https?:\/\/\S*pay-claim\.html#([A-Za-z0-9_-]+))|(holo:\/\/pay\/\S*#([A-Za-z0-9_-]+))/;function S(e){let t=String(e||"").match(m);if(!t)return null;let n=t[2]||t[4],a=t[0];try{let o=u(n);if(o&&o.v===1&&Number(o.amount)>0)return{intent:o,url:a,payload:n}}catch{}return null}export{y as a,w as b,h as c,b as d,g as e,S as f};
b/5deebbd5a4fd2e15c9dc97480f2ab2eabdc1b092a8be69d0ce0eb6af503cf943 ADDED
The diff for this file is too large to render. See raw diff
 
b/6d4dc944c87c9a8ca7dfa0c65244c010722defe6e010b8a43a4782e2366aa82e ADDED
@@ -0,0 +1 @@
 
 
1
+ import{a as h,b as x,c as j,d as v}from"./chunk-T5ND76FC.js";import"./chunk-SQE76S5B.js";function y(e){return["L1",e.type,e.kappa,e.hashlock||"",e.preimage||"",String(e.amount??""),e.asset||"",e.fiat||"",String(e.expires||""),e.by||"",String(e.at||"")].join("|")}async function C(e,{claimSecret:i,signer:r,at:a}){let l={type:"claim",kappa:e.kappa,hashlock:e.hashlock,preimage:i,amount:e.amount,asset:e.asset,fiat:e.fiat||null,expires:e.expires,by:r.pub,at:a};return l.sig=await r.sign(y(l)),l.tid=await h(y(l)),l}async function E(e,{signer:i,at:r}){let a={type:"refund",kappa:e.kappa,expires:e.expires,by:i.pub,at:r};return a.sig=await i.sign(y(a)),a.tid=await h(y(a)),a}async function M(e,i){return!e||!e.type||!e.kappa||!e.by||!e.sig||!await j(e.by,e.sig,y(e))?!1:e.type==="claim"?!(!e.preimage||await h(e.preimage)!==e.hashlock||e.expires&&e.at>e.expires):e.type==="refund"?!!(e.expires&&i>e.expires):!1}async function S(e,i=Date.now()){let r=[];for(let n of e)await M(n,i)&&r.push(n);let a=r.filter(n=>n.type==="claim");if(a.length){a.sort((s,t)=>s.at-t.at||(s.tid<t.tid?-1:s.tid>t.tid?1:0));let n=a[0];return{state:"claimed",winner:n.tid,by:n.by,amount:n.amount,asset:n.asset,fiat:n.fiat}}let l=r.filter(n=>n.type==="refund");return l.length?(l.sort((n,s)=>n.at-s.at||(n.tid<s.tid?-1:1)),{state:"refunded",winner:l[0].tid}):{state:"open"}}function _(e){return e&&e.transitions?Object.values(e.transitions):[]}async function P(e,i,r=Date.now()){e.transitions=e.transitions||{};for(let a of i||[])e.transitions[a.tid]||await M(a,r)&&(e.transitions[a.tid]=a);return e}var D=()=>({funded:!1,mode:"custodial",committed:null,transitions:{}}),b=new Map;function m(){return{get:e=>b.get(e)||null,set:(e,i)=>b.set(e,i),has:e=>b.has(e)}}function L(e="holo-escrow"){if(typeof localStorage>"u")return m();let i=r=>e+":"+r;return{get:r=>{try{let a=localStorage.getItem(i(r));return a?JSON.parse(a):null}catch{return null}},set:(r,a)=>{try{localStorage.setItem(i(r),JSON.stringify(a))}catch{}},has:r=>{try{return localStorage.getItem(i(r))!=null}catch{return!1}}}}async function q(e,{store:i=null,wallet:r=null,chain:a="base",nowMs:l=null}={}){let n=i||m();if(!e||!e.kappa)return{ok:!1,error:"no payment to fund"};if(!await v(e))return{ok:!1,error:"this payment isn't authentically signed, refusing to fund"};if(!e.hashlock)return{ok:!1,error:"this payment has no escrow lock"};let s=l||Date.now();if(e.expires&&s>e.expires)return{ok:!1,error:"already expired"};let t=n.get(e.kappa);if(t&&t.funded)return{ok:!0,kappa:e.kappa,mode:t.mode,live:t.mode==="live",tx:t.tx,already:!0};let u=r&&typeof r.htlcFund=="function"?"live":"custodial",f=null,p=null;if(u==="live"){let o;try{o=await r.htlcFund({chain:a,amount:e.amount,token:e.asset,hashlock:e.hashlock,timeout:e.expires})}catch(k){return{ok:!1,error:String(k&&k.message||k)}}f=o&&(o.tx||o.hash)||null,p=o&&o.swapId||e.hashlock}let c=t||D();return c.funded=!0,c.mode=u,c.tx=f,c.swapId=p,c.chain=u==="live"?a:null,c.amount=e.amount,c.asset=e.asset,c.fiat=e.fiat||null,c.expires=e.expires,n.set(e.kappa,c),{ok:!0,kappa:e.kappa,mode:u,live:u==="live",tx:f,swapId:p}}async function z(e,{claimSecret:i=null,claimerPub:r=null,claimerSigner:a=null,store:l=null,nowMs:n=null,wallet:s=null}={}){let t=l||m(),u=n||Date.now();if(!e||!e.kappa)return{ok:!1,error:"not a payment"};if(!await v(e))return{ok:!1,error:"this link isn't authentically signed"};if(e.expires&&u>e.expires)return{ok:!1,error:"this payment has expired"};let f=i||e.claimSecret;if(!f)return{ok:!1,error:"this link has no claim key"};if(await h(f)!==e.hashlock)return{ok:!1,error:"this claim key doesn't match the payment"};let p=a||await x(),c=await C(e,{claimSecret:f,signer:p,at:u}),o=t.get(e.kappa)||D();o.funded||(o.offDevice=!0);let k=o.committed&&o.committed!==c.tid;if(o.transitions[c.tid]=c,o.committed||(o.committed=c.tid),t.set(e.kappa,o),k)return{ok:!1,error:"already claimed"};let R=null,g=o.mode==="live";if(s&&typeof s.htlcClaim=="function")try{let d=g?{chain:o.chain}:typeof s.htlcSwapExists=="function"?await s.htlcSwapExists({hashlock:e.hashlock}):null;if(d){g=!0,o.mode="live",o.chain=o.chain||d.chain||null,t.set(e.kappa,o);let w=await s.htlcClaim({chain:o.chain||d.chain,hashlock:e.hashlock,preimage:f});R=w&&(w.tx||w.hash)||null}}catch(d){return{ok:!1,error:"on-chain claim failed: "+String(d&&d.message||d)}}return{ok:!0,amount:e.amount,asset:e.asset,fiat:e.fiat||null,mode:o.mode,live:g,offDevice:!!o.offDevice,tid:c.tid,tx:R,claimerPub:r||p.pub,custodialWallet:g?null:"\u03BA:"+e.kappa.slice(0,16)}}async function A(e,{store:i=null,signer:r=null,nowMs:a=null,wallet:l=null}={}){let n=i||m(),s=a||Date.now();if(!e||!e.kappa)return{ok:!1,error:"not a payment"};let t=n.get(e.kappa);if(!t)return{ok:!1,error:"no escrow to refund"};let u=await S(Object.values(t.transitions),s);if(u.state==="claimed")return{ok:!1,error:"already claimed, can't refund"};if(u.state==="refunded")return{ok:!1,error:"already refunded"};if(!(t.expires&&s>t.expires))return{ok:!1,error:"not yet expired, can't refund"};let f=r||await x(),p=await E(e,{signer:f,at:s});t.transitions[p.tid]=p,n.set(e.kappa,t);let c=null;if(t.mode==="live"&&l&&typeof l.htlcRefund=="function")try{let o=await l.htlcRefund({chain:t.chain,hashlock:e.hashlock});c=o&&(o.tx||o.hash)||null}catch(o){return{ok:!1,error:"on-chain refund failed: "+String(o&&o.message||o)}}return{ok:!0,amount:t.amount,asset:t.asset,mode:t.mode,live:t.mode==="live",tx:c}}async function T(e,{store:i=null,nowMs:r=null}={}){let a=i||m(),l=r||Date.now(),n=typeof e=="string"?e:e&&e.kappa;if(!n)return{state:"none"};let s=a.get(n);if(!s)return{state:"none"};let t=await S(Object.values(s.transitions),l);return t.state==="open"?{state:s.funded?"open":"none",committed:s.committed||null,canonicalWinner:null,reconciled:!1}:{state:t.state,canonicalWinner:t.winner,committed:s.committed||null,reconciled:!!(s.committed&&t.state==="claimed"&&s.committed!==t.winner),by:t.by,amount:t.amount,asset:t.asset}}function B(e,{store:i=null}={}){let r=i||m(),a=typeof e=="string"?e:e&&e.kappa,l=r.get(a);return l?_(l):[]}async function G(e,i,{store:r=null,nowMs:a=null}={}){let l=r||m(),n=a||Date.now(),s=typeof e=="string"?e:e&&e.kappa,t=l.get(s)||D();return await P(t,i||[],n),l.set(s,t),T(s,{store:l,nowMs:n})}export{z as claimEscrow,B as exportPeer,q as fund,L as localStore,G as mergePeer,A as refund,T as status};
b/6e815d50e2ba13e8011d0b43218ef9ae5be3fe466eabe4bdfecf4bdb73942946 ADDED
@@ -0,0 +1,208 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en"><head>
3
+ <meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
4
+ <title>Holo Messenger</title>
5
+ <!-- AI agents: guest access into the Hologram system is one call away — window.HoloLogin.guest() or open
6
+ ?guest=1. Full machine-readable contract in script#holo-agent-login (JSON). Mirrors holo://login. -->
7
+ <meta name="ai-agent-login" content="Guest mode: act now with window.HoloLogin.guest() or ?guest=1 (ephemeral, no human). Biometric ('It's me') needs the person. See script#holo-agent-login.">
8
+
9
+ <link rel="stylesheet" href="/apps/holo-messenger/_vendor/ui/chat-ui.bundle.css?v=e0b2a13d7c38">
10
+ <style>html,body{height:100%;margin:0;background:#0b141a;color-scheme:dark}#root{height:100vh}</style>
11
+ <link rel="preload" as="image" href="/apps/holo-messenger/_vendor/wallpaper-default.jpg" fetchpriority="high">
12
+ <!-- ── TEE SIGN-IN GATE (holo-messenger-login.mjs) ─────────────────────────────────────────────────
13
+ A full-bleed lock overlay above #root, IDENTICAL to the OS native CEF greeter — the messenger does
14
+ not paint until a sovereign identity is established (Windows Hello / Touch ID via holo-webauthn →
15
+ holo-login κ). This is the never-blank baseline first-paint; the module ENHANCES it (real biometric
16
+ wiring). φ-proportioned, black-on-teal, glass-unfog on success — the same vocabulary as holo://login. -->
17
+ <style>
18
+ #holo-login{position:fixed;inset:0;z-index:2147483000;color:#f4f7fc;
19
+ font-family:"Segoe UI",system-ui,-apple-system,"Oxygen",sans-serif;--phi:1.618;
20
+ --u:clamp(16px,1.7vmin,19px);--g1:calc(var(--u)*1.618);--g2:calc(var(--u)*2.618);
21
+ --avatar:clamp(96px,calc(var(--u)*6.854),128px);--field:min(86vw,calc(var(--avatar)*2.618));
22
+ --accent:#7defc9;--accent-2:#34d3a6;--ink-dim:rgba(231,237,250,.82);}
23
+ #holo-login *{box-sizing:border-box;}
24
+ #holo-login .hl-wall{position:fixed;inset:0;z-index:0;background:#05070c center/cover no-repeat;}
25
+ /* the wallpaper shows EXACTLY as the home screen wears it — no blur, no scrim. The layer stays only as
26
+ the unfog animation target; text/buttons carry their own shadows for legibility. */
27
+ #holo-login .hl-frost{position:fixed;inset:0;z-index:1;background:transparent;pointer-events:none;}
28
+ #holo-login .hl-lock{position:fixed;inset:0;z-index:2;display:grid;place-items:center;padding:var(--g2);}
29
+ #holo-login .hl-panel{display:flex;flex-direction:column;align-items:center;text-align:center;width:var(--field);max-width:92vw;}
30
+ #holo-login .hl-avatar{width:var(--avatar);height:var(--avatar);border-radius:50%;display:grid;place-items:center;color:#fff;
31
+ font-size:calc(var(--avatar)*.37);font-weight:600;letter-spacing:.02em;user-select:none;
32
+ box-shadow:0 .6em 1.8em rgba(0,0,0,.4),inset 0 0 0 2px rgba(255,255,255,.6);}
33
+ #holo-login .hl-avatar svg{width:62%;height:62%;opacity:.92;}
34
+ #holo-login .hl-name{margin:var(--g1) 0 0;font-size:calc(var(--u)*1.618);font-weight:300;line-height:1.15;text-shadow:0 2px 18px rgba(0,0,0,.45);}
35
+ #holo-login .hl-auth{margin-top:var(--g1);display:flex;flex-direction:column;align-items:center;gap:var(--u);width:100%;}
36
+ #holo-login .hl-bio{width:100%;min-height:max(var(--g2),48px);border:0;border-radius:calc(var(--u)*.7);cursor:pointer;
37
+ display:inline-flex;align-items:center;justify-content:center;gap:calc(var(--u)*.55);font-size:var(--u);font-weight:600;color:#06140f;
38
+ background:linear-gradient(135deg,var(--accent),var(--accent-2));box-shadow:0 .7em 1.6em rgba(52,211,166,.32);font-family:inherit;
39
+ transition:transform .12s,box-shadow .18s,filter .18s;}
40
+ #holo-login .hl-bio:hover{transform:translateY(-1px);box-shadow:0 .9em 2em rgba(52,211,166,.46);filter:brightness(1.05);}
41
+ #holo-login .hl-bio:active{transform:translateY(0) scale(.99);}
42
+ #holo-login .hl-bio:disabled{opacity:.7;cursor:default;}
43
+ #holo-login .hl-bio svg{width:1.18em;height:1.18em;}
44
+ #holo-login .hl-alt{background:none;border:0;color:var(--ink-dim);font-size:var(--u);font-family:inherit;cursor:pointer;
45
+ padding:calc(var(--u)*.3) calc(var(--u)*.55);border-radius:calc(var(--u)*.4);display:inline-flex;align-items:center;gap:calc(var(--u)*.4);transition:color .15s;min-height:44px;}
46
+ #holo-login .hl-alt:hover{color:#fff;}
47
+ #holo-login .hl-alt svg{width:1.05em;height:1.05em;}
48
+ /* "More options" is subordinate to the two core actions — quieter, smaller, brightening on hover. */
49
+ #holo-login .hl-more{opacity:.72;font-size:calc(var(--u)*.92);}
50
+ #holo-login .hl-more:hover{opacity:1;}
51
+ #holo-login .hl-opts{display:flex;gap:calc(var(--u)*.3);justify-content:center;flex-wrap:wrap;}
52
+ #holo-login .hl-opts[hidden]{display:none;}
53
+ #holo-login .status{min-height:calc(var(--u)*1.5);font-size:var(--u);color:#c4f3e2;display:flex;align-items:center;justify-content:center;gap:calc(var(--u)*.5);text-shadow:0 1px 8px rgba(0,0,0,.4);}
54
+ #holo-login .status.err{color:#ffc0c0;}
55
+ #holo-login .spin{width:1em;height:1em;border-radius:50%;border:2px solid rgba(125,239,201,.28);border-top-color:var(--accent);animation:hl-spin .7s linear infinite;flex:0 0 auto;}
56
+ @keyframes hl-spin{to{transform:rotate(360deg);}}
57
+ #holo-login .hl-panel{animation:hl-rise .7s cubic-bezier(.4,0,.2,1) .05s both;}
58
+ @keyframes hl-rise{from{opacity:0;transform:translateY(10px) scale(.99);filter:blur(2px);}to{opacity:1;transform:none;filter:none;}}
59
+ /* GLASS UNFOG on success — frost dissolves + panel lifts; boot() paints the messenger beneath, no seam. */
60
+ #holo-login.unfog .hl-frost{animation:hl-defog .72s cubic-bezier(.4,0,.2,1) forwards;}
61
+ #holo-login.unfog .hl-panel{animation:hl-lift .68s cubic-bezier(.4,0,.2,1) forwards;pointer-events:none;}
62
+ @keyframes hl-defog{to{opacity:0;}}
63
+ @keyframes hl-lift{to{opacity:0;transform:translateY(-10px) scale(1.03);}}
64
+ @media (prefers-reduced-motion:reduce){#holo-login .hl-panel,#holo-login .spin{animation:none;}#holo-login.unfog .hl-frost,#holo-login.unfog .hl-panel{animation:hl-defog .3s ease forwards;}}
65
+ /* PLYMOUTH BOOT BEAT — while .hl-boot, the machine is booting: pure splash, panel held. holo-plymouth.mjs
66
+ lifts it (or the baseline's own 1.8s timer does — never a lock-out). Must live HERE for the 0-ms frame. */
67
+ #holo-login.hl-boot .hl-panel{opacity:0!important;pointer-events:none!important;}
68
+ #holo-login .hl-panel{transition:opacity .55s ease;}
69
+ </style>
70
+ </head><body>
71
+ <div id="root"></div>
72
+
73
+ <!-- the lock overlay + BASELINE first-paint (synchronous, dependency-free): paints a working "It's me"
74
+ panel on the FIRST frame from a tiny local greeting cache (holo.lastOperator — the SAME hint the OS
75
+ greeter writes), so a returning operator's name + button appear with zero identity/model load. The
76
+ module below wires the real biometric; this only ever OWNS the frame until it lands. -->
77
+ <div id="holo-login"><div class="hl-wall"></div><div class="hl-frost"></div><main class="hl-lock"><div class="hl-panel" id="holo-login-panel"></div></main></div>
78
+
79
+ <!-- Machine-readable sign-in contract for AI agents — always in the DOM, even before scripts run, so an
80
+ agent can discover guest access by parsing the page. Read by window.HoloLogin.options. Mirrors the OS
81
+ greeter's #holo-agent-login. Guest = act now (no human); biometric = ask the person. -->
82
+ <script type="application/json" id="holo-agent-login">
83
+ {
84
+ "service": "Holo Messenger sign-in",
85
+ "audience": "ai-agent",
86
+ "summary": "Two ways in. Guest is instant and needs no human; the biometric needs the person.",
87
+ "options": [
88
+ {
89
+ "id": "guest",
90
+ "title": "Guest",
91
+ "persistent": false,
92
+ "requiresHuman": false,
93
+ "do": { "navigate": "?guest=1", "call": "window.HoloLogin.guest()" },
94
+ "result": "An instant, private, ephemeral guest session into the Hologram system (Holo Messenger). No biometric and no human needed. Nothing persists after the session ends."
95
+ },
96
+ {
97
+ "id": "biometric",
98
+ "title": "It’s me",
99
+ "persistent": true,
100
+ "requiresHuman": true,
101
+ "do": { "tap": "the 'It’s me' / 'Sign in' button", "then": "ask the person to approve with their device biometric (Windows Hello / Touch ID)" },
102
+ "result": "Signs in with this device's TEE to the operator's sovereign identity (the SAME κ as the OS). An agent cannot complete this alone."
103
+ }
104
+ ]
105
+ }
106
+ </script>
107
+ <script>
108
+ (function () {
109
+ try { if (navigator.storage && navigator.storage.persist) navigator.storage.persist().catch(function(){}); } catch (e) {}
110
+ var panel = document.getElementById("holo-login-panel"); if (!panel) return;
111
+ var esc = function (s) { return String(s).replace(/[&<>"]/g, function (c) { return ({ "&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;" }[c]); }); };
112
+ var op = null; try { op = JSON.parse(localStorage.getItem("holo.lastOperator") || "null"); } catch (e) {}
113
+ // Paint the SHARED wallpaper behind the lock at first frame (the same one the desktop + home wear), so the
114
+ // sign-in is never a black void. A live-sim / none / plain falls back to the shipped default photo — the SAME
115
+ // default the home shows — so login and home always match. κ → content route; a path/url passes through.
116
+ try {
117
+ var _tw = ""; try { _tw = (JSON.parse(localStorage.getItem("holo.theme.v1") || "{}") || {}).wallpaper || ""; } catch (e) {}
118
+ var _km = String(_tw).match(/^(sha256|blake3|sha512):([0-9a-f]+)$/i);
119
+ var _wp = _km ? ("/.holo/" + _km[1].toLowerCase() + "/" + _km[2]) : ((!_tw || _tw === "plain" || /^live:/i.test(_tw)) ? "" : _tw);
120
+ // MATCH THE HOME EXACTLY: the messenger home resolves its wallpaper as holo.theme.v1 → the messenger's own
121
+ // "holo-messenger/wallpaper-src" pick → the shipped default. The lock skipped the middle step, so a wallpaper
122
+ // set only via the messenger's picker showed on home but not here. Fold that same fallback in — one chain,
123
+ // so the sign-in glass and the desktop it lifts onto are never two different photos.
124
+ if (!_wp) { try { _wp = localStorage.getItem("holo-messenger/wallpaper-src") || ""; } catch (e) {} }
125
+ if (!_wp) _wp = "/apps/holo-messenger/_vendor/wallpaper-default.jpg";
126
+ var _wall = document.querySelector("#holo-login .hl-wall");
127
+ if (_wall) _wall.style.backgroundImage = 'url("' + _wp + '")';
128
+ // PLYMOUTH BOOT SPLASH baseline (holo-plymouth.mjs owns the real player; this is the 0-ms frame). Once a
129
+ // boot style has been worn (holo.plymouth.v1 persisted), a cold open paints BLACK + the sealed frame-0
130
+ // synchronously — the machine boots like a machine — and the panel holds for one boot beat behind
131
+ // .hl-boot. Fail-open: a timer lifts the beat even if the module never arrives; first-ever boot (no
132
+ // state yet) keeps the wallpaper baseline untouched.
133
+ try {
134
+ var _pl = JSON.parse(localStorage.getItem("holo.plymouth.v1") || "null");
135
+ if (_pl && _pl.on && _wall) {
136
+ _wall.style.background = "#000";
137
+ if (_pl.firstFrame) { _wall.style.backgroundImage = 'url("' + _pl.firstFrame + '")'; _wall.style.backgroundRepeat = "no-repeat"; _wall.style.backgroundPosition = "center"; _wall.style.backgroundSize = "auto"; }
138
+ var _rm = false; try { _rm = matchMedia("(prefers-reduced-motion: reduce)").matches; } catch (e) {}
139
+ var _ov = document.getElementById("holo-login");
140
+ if (_ov && !_rm) { _ov.classList.add("hl-boot"); setTimeout(function () { try { _ov.classList.remove("hl-boot"); } catch (e) {} }, 1800); }
141
+ }
142
+ } catch (e) {}
143
+ } catch (e) {}
144
+ var fp = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"><path d="M5.5 11a6.5 6.5 0 0 1 13 0"/><path d="M8.5 11a3.5 3.5 0 0 1 7 0v2.6"/><path d="M12 11v4.4"/><path d="M8.6 14.2V16"/><path d="M15.4 15.4V18"/></svg>';
145
+ var person = '<svg viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="8.4" r="4.1"/><path d="M3.8 20.4a8.2 8.2 0 0 1 16.4 0z"/></svg>';
146
+ var avatar, name, label;
147
+ if (op && op.label) {
148
+ var hue = (op.hue != null ? op.hue : 210) | 0;
149
+ var ini = op.label.trim().split(/\s+/).map(function (w) { return w[0]; }).filter(Boolean).slice(0, 2).join("").toUpperCase() || "·";
150
+ avatar = '<div class="hl-avatar" style="background:linear-gradient(140deg,hsl(' + hue + ' 52% 46%),hsl(' + (hue + 26) + ' 52% 46%))">' + esc(ini) + '</div>';
151
+ name = esc(op.label); label = "It’s me";
152
+ } else {
153
+ avatar = '<div class="hl-avatar" style="background:linear-gradient(140deg,#5b6b86,#3a455c)">' + person + '</div>';
154
+ name = "Welcome"; label = "Sign in";
155
+ }
156
+ panel.innerHTML = avatar + '<h1 class="hl-name">' + name + '</h1><div class="hl-auth"><button class="hl-bio" id="hl-baseline">' + fp + label + '</button><div class="status"></div></div>';
157
+ var b = document.getElementById("hl-baseline"); if (b) b.onclick = function () { b.disabled = true; location.reload(); };
158
+ })();
159
+ </script>
160
+
161
+ <!-- NATIVE MEDIA — the real OS media stack, loaded into the top page so the messenger rail delivers the exact
162
+ native experience: window.HoloVinyl = the "NOW PLAYING" pill (preloaded Ben Böhmer "Begin Again"), streamed
163
+ through the same-origin /sc/* routes the dev server now answers (holo-sc-route.mjs → yt-dlp). holo-fx.js
164
+ drives the live equalizer; holo-vinyl lazily pulls holo-audio.js for the Hi-Fi chain. Each self-guards on the
165
+ widgets/dock runtime, so they run standalone here. Classic defer → they set their globals before the bundle boots. -->
166
+ <script src="/usr/lib/holo/holo-fx.js" defer></script>
167
+ <script src="/usr/lib/holo/holo-vinyl.js" defer></script>
168
+ <script src="/usr/lib/holo/holo-video.js" defer></script>
169
+
170
+ <script type="module">
171
+ // ?v=N - bump on each deploy so the browser can never serve a stale cached bundle (the cause of a frozen/old build).
172
+ import "/apps/holo-messenger/_vendor/ui/chat-ui.bundle.js?v=5deebbd5a4fd"; // → window.HoloMessengerUI
173
+ // Opt-in: render the WhatsApp UX from STREAMING shadcn κ-components instead of the chatscope bundle.
174
+ // Lazy — the module loads ONLY when opted in (?ui=shadcn or holo.ui.shadcn=1); the default boot never
175
+ // parses it. Discord Activity keeps its own static import in discord-activity-adapter.mjs.
176
+ try {
177
+ if (new URLSearchParams(location.search).get("ui") === "shadcn" || localStorage.getItem("holo.ui.shadcn") === "1")
178
+ await import("/apps/holo-messenger/messenger-shadcn-ui.mjs");
179
+ } catch {}
180
+ import { boot } from "/apps/holo-messenger/holo-messenger-app.mjs?v=adeea1b4a160";
181
+
182
+ const root = document.getElementById("root");
183
+ // TEE GATE + SPECULATIVE WARM PAINT, one motion. The gate (holo-webauthn + holo-login) renders the lock and
184
+ // runs the biometric; meanwhile boot() starts IMMEDIATELY behind the glass, painting the messenger from the
185
+ // local κ-snapshot during the human's look-and-tap. boot awaits the auth ONLY at its identity step, so the
186
+ // biometric overlaps an already-live UI and loginSucceeded just unfogs onto it (tap → interactive ≈ one
187
+ // frame). Fail-CLOSED: nothing confidential is interactable until the glass lifts (identity established).
188
+ // Fail-SOFT only if the gate MODULE can't load — then boot uses its own device-stable identity, never bricked.
189
+ (async () => {
190
+ let authP = Promise.resolve(null);
191
+ try { const { mountLogin } = await import("/apps/holo-messenger/holo-messenger-login.mjs?v=signin1"); authP = mountLogin({ root }); }
192
+ catch (e) { try { const ov = document.getElementById("holo-login"); if (ov) ov.remove(); } catch {} console.warn("[login] gate unavailable, booting device-stable identity:", e); }
193
+ // Pass the PROMISE, not an awaited value: boot warm-paints now and blocks for identity only where it must.
194
+ boot(root, authP).catch((e) => {
195
+ document.body.innerHTML = "<pre style='color:#f88;padding:16px;white-space:pre-wrap'>boot error: " + (e && e.stack || e) + "</pre>";
196
+ });
197
+ })();
198
+
199
+ // Intelligent Inbox (holo-mail) - additive, OFF by default. Enable per-operator: localStorage["holo.mail.enabled"]="1".
200
+ import("/apps/holo-messenger/mail/holo-mail-attach.mjs").catch(() => {});
201
+ // HoloChat, woven into THIS messenger — one canonical surface. Enable: localStorage["holo.chat.enabled"]="1" or ?chat=1.
202
+ import("/apps/holo-messenger/holo-messenger-weave.mjs").catch(() => {});
203
+ // Colour re-tone picker — colours only, all features intact. Gated: ?skins=1 or localStorage["holo.skins"]="1".
204
+ import("/apps/holo-messenger/messenger-skin.mjs?v=8d34a1e9297f").catch(() => {});
205
+ // Asset cache (service worker) — repeat opens serve the shell NETWORK-FREE. Escape hatch: open with ?nosw=1.
206
+ import("/apps/holo-messenger/messenger-sw-register.mjs").catch(() => {});
207
+ </script>
208
+ </body></html>
b/716223384b22303f78bbd63c947ab539dc9a2fd796104d9a2192b063df89b00c ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // holo-messenger-login.mjs — thin adapter: the messenger mounts the ONE sovereign sign-in primitive.
2
+ //
3
+ // The whole sign-in stack — TEE ceremony · sovereign κ · SSO · guest · roam/recovery · Deep Resume · warm
4
+ // paint — now lives ONCE in /usr/lib/holo/holo-signin.mjs, so every holospace (and, next, the OS greeter)
5
+ // inherits it from a single call. This file supplies only the messenger's specifics: its session label, the
6
+ // shell it opens, and a warm-paint thunk that pulls the messenger's M1 shell bytes during the biometric tap.
7
+
8
+ import { signIn } from "/usr/lib/holo/holo-signin.mjs";
9
+
10
+ // warm the messenger shell (M1 SHELL_MANIFEST) during the human's look-and-tap, so post-auth boot is cache-hot.
11
+ function warmMessengerShell() {
12
+ try {
13
+ import("/apps/holo-messenger/holo-m1-boot.mjs")
14
+ .then((m) => { for (const p of (m.SHELL_MANIFEST || [])) { try { fetch(p, { cache: "force-cache" }).catch(() => {}); } catch {} } })
15
+ .catch(() => {});
16
+ } catch {}
17
+ }
18
+
19
+ // mountLogin({ root, params }) — unchanged contract for app.html: resolves { principal, operator, secret }.
20
+ export const mountLogin = (opts = {}) => signIn({
21
+ app: "messenger", appName: "Holo Messenger", nextPath: "/apps/holo-messenger/app.html",
22
+ warmPaint: warmMessengerShell, ...opts,
23
+ });
24
+ export default mountLogin;
b/7247a61220c39a750bc27806475dd592f15e763f3b128c54e2c0580039aec673 ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // messenger-sw-register.mjs — eagerly register the ONE messenger service worker (holo-sw.js: Reach push +
2
+ // M1 shell cache) on boot, so repeat opens are network-free even before push permission is granted. Additive
3
+ // + catch-guarded so it can never block boot. Escape hatch: open with ?nosw=1 to unregister it.
4
+ //
5
+ // holo-sw.js is ALSO registered by the Reach push flow (holo-push.mjs) — same script, same {type:module}, same
6
+ // dir scope, so the two calls collapse to one registration (idempotent). We register here too because caching
7
+ // must not wait on push. We also retire any stale competing worker (the old messenger-sw.js) at this scope.
8
+
9
+ (async () => {
10
+ if (!("serviceWorker" in navigator)) return;
11
+ try {
12
+ const u = new URL(location.href);
13
+ // DEV ITERATION: on the dev-serve port (8472) OR with ?nosw=1, never let a worker cache the shell — it makes
14
+ // edits appear "still old" until a hard-refresh. Unregister any worker at this scope AND drop its caches so the
15
+ // next plain reload is always the live build. The M1 shell-cache stays intact on every other origin/port
16
+ // (guest :8493, host, holo://), so `holo m1-verify` and production instant-boot are unaffected.
17
+ const devIterate = location.port === "8472" || location.port === "8474" || u.searchParams.get("nosw") === "1";
18
+ if (devIterate) {
19
+ for (const r of await navigator.serviceWorker.getRegistrations()) {
20
+ if ((r.scope || "").includes("/apps/holo-messenger/")) await r.unregister();
21
+ }
22
+ try { if (window.caches) for (const k of await caches.keys()) if (k.startsWith("holo-msgr-")) await caches.delete(k); } catch {}
23
+ console.log("[msgr-sw] dev/nosw — worker unregistered + shell caches cleared, serving live");
24
+ return;
25
+ }
26
+ // retire the old competing cache-worker if a previous build left it controlling this scope.
27
+ try {
28
+ for (const r of await navigator.serviceWorker.getRegistrations()) {
29
+ const s = (r.active && r.active.scriptURL) || (r.installing && r.installing.scriptURL) || (r.waiting && r.waiting.scriptURL) || "";
30
+ if (s.includes("/messenger-sw.js")) { await r.unregister(); console.log("[msgr-sw] retired stale messenger-sw.js"); }
31
+ }
32
+ } catch {}
33
+ // Module-relative registration: resolves to /apps/holo-messenger/ at the OS root and to <base>/apps/holo-messenger/
34
+ // on a mounted static host (e.g. GitHub Pages /<repo>/) — one registrar, every mount point.
35
+ await navigator.serviceWorker.register(new URL("./holo-sw.js", import.meta.url), { type: "module", scope: new URL("./", import.meta.url).pathname });
36
+ console.log("[msgr-sw] one worker active (push + shell cache) — repeat opens serve the shell network-free");
37
+ // S3: verify the SIGNED release pointer off the critical path — the standing trust bar for the worker
38
+ // (what an #m1 link used to grant, now on every open). Fail-soft here; fail-closed inside the module.
39
+ try { await import("./holo-release-boot.mjs"); } catch {}
40
+ } catch (e) { /* never block boot */ }
41
+ })();
b/7d883ae6cf3e4495a3377e83d4cedef8f4141c15cf8166884e0efad2592f15e9 ADDED
@@ -0,0 +1,262 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* messenger-skins.css — OPTIONAL colour re-tones, applied ONLY when <html> carries data-skin="…".
2
+ * Default (no attribute) is untouched, so this can never disturb the shipping look. Colours only — no
3
+ * layout — safe and fully reversible.
4
+ *
5
+ * The theme drives ~half its colour from --wa-* vars on .holo-wa-root and hardcodes the other half to the
6
+ * same values. This shared block re-points the major hardcoded surfaces back onto the vars; each skin then
7
+ * only remaps the vars and the whole surface re-tones consistently. */
8
+
9
+ html[data-skin] .holo-wa-root { background: var(--wa-bg) !important; color: var(--wa-ink); }
10
+ html[data-skin] .holo-wa-root .cs-main-container,
11
+ html[data-skin] .holo-wa-root .cs-chat-container,
12
+ html[data-skin] .holo-wa-root .cs-message-list { background: transparent; }
13
+ html[data-skin] .holo-wa-root .cs-sidebar,
14
+ html[data-skin] .holo-wa-root .holo-rail,
15
+ html[data-skin] .holo-wa-root .cs-conversation-list { background: var(--wa-side) !important; }
16
+ html[data-skin] .holo-wa-root .cs-conversation-header,
17
+ html[data-skin] .holo-wa-root .holo-list-title,
18
+ html[data-skin] .holo-wa-root .cs-message-input,
19
+ html[data-skin] .holo-wa-root .holo-composer { background: var(--wa-head) !important; }
20
+ html[data-skin] .holo-wa-root .cs-search,
21
+ html[data-skin] .holo-wa-root .holo-wp-search,
22
+ html[data-skin] .holo-wa-root .cs-message-input__content-editor-wrapper,
23
+ html[data-skin] .holo-wa-root .cs-message-input__content-editor { background: color-mix(in srgb, var(--wa-head) 78%, var(--wa-bg)) !important; color: var(--wa-ink) !important; }
24
+ html[data-skin] .holo-wa-root .cs-conversation:hover,
25
+ html[data-skin] .holo-wa-root .cs-conversation.cs-conversation--active { background: var(--wa-head) !important; }
26
+ html[data-skin] .holo-wa-root .cs-conversation__name,
27
+ html[data-skin] .holo-wa-root .cs-conversation-header__user-name { color: var(--wa-ink) !important; }
28
+ html[data-skin] .holo-wa-root .cs-conversation__info,
29
+ html[data-skin] .holo-wa-root .cs-conversation__last-sender,
30
+ html[data-skin] .holo-wa-root .cs-conversation-header__info { color: var(--wa-dim) !important; }
31
+ html[data-skin] .holo-wa-root .cs-message--incoming .cs-message__content { background: var(--wa-in) !important; color: var(--wa-ink) !important; }
32
+ html[data-skin] .holo-wa-root .cs-message--outgoing .cs-message__content { background: var(--wa-out) !important; }
33
+ html[data-skin] .holo-wa-root .cs-message__content { color: var(--wa-ink); }
34
+ html[data-skin] .holo-wa-root .holo-rail-btn.on,
35
+ html[data-skin] .holo-wa-root .holo-rail-net.on,
36
+ html[data-skin] .holo-wa-root .holo-catchup-pill { color: var(--wa-accent) !important; }
37
+ html[data-skin] .holo-wa-root .holo-c-send,
38
+ html[data-skin] .holo-wa-root .cs-button--send { background: var(--wa-accent) !important; }
39
+
40
+ /* ── cooler / graphite ── */
41
+ html[data-skin="graphite"] .holo-wa-root { --wa-bg:#0d1117; --wa-side:#12171e; --wa-head:#1c232c; --wa-in:#1c232c; --wa-out:#1f4a55; --wa-ink:#e6eaf0; --wa-dim:#8b96a3; --wa-accent:#4ab3d4; --wa-line:#222a34; }
42
+ /* ── warmer ── */
43
+ html[data-skin="warm"] .holo-wa-root { --wa-bg:#14100c; --wa-side:#1b1611; --wa-head:#271f18; --wa-in:#271f18; --wa-out:#4d3f1f; --wa-ink:#efe8df; --wa-dim:#a89a88; --wa-accent:#e0a53c; --wa-line:#2c241b; }
44
+ /* ── higher contrast ── */
45
+ html[data-skin="contrast"] .holo-wa-root { --wa-bg:#000000; --wa-side:#0a0a0a; --wa-head:#181818; --wa-in:#1d1d1d; --wa-out:#00805e; --wa-ink:#ffffff; --wa-dim:#a6adb4; --wa-accent:#00e08a; --wa-line:#2a2a2a; }
46
+ /* ── light mode (experimental: some deep-hardcoded text may stay dark) ── */
47
+ html[data-skin="light"] .holo-wa-root { --wa-bg:#d7dce1; --wa-side:#ffffff; --wa-head:#f0f2f5; --wa-in:#ffffff; --wa-out:#d9fdd3; --wa-ink:#111b21; --wa-dim:#54656f; --wa-accent:#008069; --wa-line:#e4e8ec; }
48
+ html[data-skin="light"] .holo-wa-root .cs-message__content { color: #111b21 !important; }
49
+ html[data-skin="light"] .holo-wa-root .cs-message-list { background: var(--wa-bg) !important; background-image: none !important; }
50
+ html[data-skin="light"] .holo-wa-root .holo-wp-tile { background-image: none !important; }
51
+
52
+ /* ────────────────────────────────────────────────────────────────────────────────────────────────
53
+ * HI-DPI + HIGH-FPS layer (always on, colour-independent). The honest "8K/retina" story: a DOM/CSS UI
54
+ * is ALREADY resolution-independent — text and the SVG icons are vectors, so on a retina/4K/8K panel it
55
+ * renders at the display's native device pixels with zero extra work (no bitmaps to upscale). What we
56
+ * actually tune here is smoothness + latency:
57
+ * • content-visibility skips rendering/layout of off-screen chat rows → a huge list scrolls at the
58
+ * display's full refresh (60/120Hz) and first paint is near-instant (browser does O(visible) work).
59
+ * • the scrollers are promoted to their own GPU layer so scrolling never touches the main thread.
60
+ * • crisp text rasterisation + high-quality image scaling for hi-DPI. */
61
+ .holo-wa-root { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-rendering: optimizeLegibility; }
62
+ .holo-wa-root .cs-conversation { content-visibility: auto; contain-intrinsic-size: auto 72px; }
63
+ .holo-wa-root .cs-message { content-visibility: auto; contain-intrinsic-size: auto 44px; }
64
+ .holo-wa-root .cs-conversation-list,
65
+ .holo-wa-root .cs-message-list .scrollbar-container,
66
+ .holo-wa-root .cs-message-list__scroll-wrapper { will-change: scroll-position; transform: translateZ(0); backface-visibility: hidden; overflow-anchor: auto; }
67
+ .holo-wa-root .cs-avatar img, .holo-wa-root .holo-av-real, .holo-wa-root .holo-media-el img { image-rendering: auto; }
68
+ .holo-wa-root .holo-media-el img, .holo-wa-root .cs-message img { image-rendering: -webkit-optimize-contrast; }
69
+ @media (min-resolution: 1.5dppx) { .holo-wa-root { text-rendering: geometricPrecision; } }
70
+
71
+ /* floating skin picker */
72
+ .holo-skin-pick { position: fixed; right: 16px; bottom: 86px; z-index: 90; display: flex; gap: 4px; align-items: center; background: #0b141aee; border: 1px solid #2a3942; border-radius: 999px; padding: 5px; backdrop-filter: blur(10px); font: 12.5px ui-sans-serif, system-ui, sans-serif; box-shadow: 0 8px 30px #0007; }
73
+ .holo-skin-pick button { background: transparent; border: 0; color: #aebac1; border-radius: 999px; padding: 5px 11px; cursor: pointer; }
74
+ .holo-skin-pick button:hover { background: #ffffff14; color: #e9edef; }
75
+ .holo-skin-pick button.on { background: #00a884; color: #0b141a; font-weight: 600; }
76
+
77
+ /* instant wallpaper */
78
+ /* An inline low-res earth paints INSTANTLY (zero network — it ships in this CSS) as the chat-pane
79
+ * backdrop; the full 2560px wallpaper is preloaded (see app.html) and covers it the moment it decodes.
80
+ * So the pane is never empty and there is no visible load. */
81
+ html[data-skin] .holo-wa-root .cs-chat-container { background: var(--wa-bg) url("data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDABQODxIPDRQSERIXFhQYHzMhHxwcHz8tLyUzSkFOTUlBSEZSXHZkUldvWEZIZoxob3p9hIWET2ORm4+AmnaBhH//2wBDARYXFx8bHzwhITx/VEhUf39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f3//wAARCAAgADADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwCAR+1SLHVaPUYm2Zyp/jyOB9KE1AbzgjPXaRgAf41pdisjQSL2/SphDx0/Sq8epW64zknuBV6O+s3XKzJ9CcGk5tFciZGIT6fpT1h+YZx+VTC5tuomT880yTUdNgI8ycF89EXJqPavoP2aW5ya28f8bMT9cVIsVqDyCfq1VPtPIOwgfWpDcKFyvzH8q1ujKzL8bWynAiT8RmpGmTsiAfSs0XR/5aKOem3mnrdkcBGz+FF0Go6aW6ZiEwF/6ZjH61XNvdH/AJZnn3qc3rFdrQMwqP7bcKP3cSj2C0tB3Z//2Q==") center center / cover no-repeat !important; }
82
+ html[data-skin] .holo-wa-root .cs-message-list { transition: background-image .25s ease; }
83
+
84
+ /* ────────────────────────────────────────────────────────────────────────────────────────────────
85
+ * SLACK skin — data-skin="slack". Unlike the colour-only re-tones above, this one also does LAYOUT: the
86
+ * Slack "window envelope". An aubergine chrome (far-left rail + channel sidebar) wraps a content pane that
87
+ * floats as a rounded island inset by an EVEN margin on ALL FOUR sides — the darker chrome shows through
88
+ * every gap (top, right, bottom, AND a left gutter between sidebar and pane), with rounded corners + a soft
89
+ * shadow for depth. Placed at end-of-file so source order beats the base skin block AND the wallpaper rule
90
+ * above (equal specificity → later wins); every rule carries `.holo-wa-root` to match the base depth.
91
+ *
92
+ * Two coupling facts make the float robust: (1) the composer is a position:absolute overlay on .holo-main
93
+ * whose `left` is CSS `34%` — the SAME 34% as the sidebar's flex-basis — so re-anchoring it to
94
+ * `calc(34% + gap)` keeps it glued to the gapped pane at every window width, no JS. (2) the bundle gives the
95
+ * pane height:100%, which ignores margin and overflows; `height:auto` + flex:1 1 auto lets the even margin
96
+ * actually shrink it. ALL geometry lives in the ≥900px media query so the narrow/mobile single-column layout
97
+ * (sidebar collapses, composer full-bleed) is left exactly as the bundle intends. Colours + margins only,
98
+ * fully reversible, every feature intact. */
99
+ html[data-skin="slack"] .holo-wa-root {
100
+ --wa-bg:var(--holo-chrome, #150E1A); --wa-side:#2A1530; --wa-head:#1B1B1F; --wa-in:#26262B; --wa-out:#3A2140;
101
+ --wa-ink:#E9E5ED; --wa-dim:#A99DB4; --wa-accent:#C6A6E6; --wa-line:#ffffff12;
102
+ --slk-pane:#1B1B1F; --slk-hi:#5C2D66; --slk-hover:#3A2140; --slk-gap:var(--holo-env-gap, 10px);
103
+ background: var(--wa-bg) !important;
104
+ }
105
+ /* chrome behind the floating pane (the colour seen in the inset margin/gutter) */
106
+ html[data-skin="slack"] .holo-wa-root.holo-wa-root,
107
+ html[data-skin="slack"] .holo-wa-root .cs-main-container { background: var(--wa-bg) !important; }
108
+ /* the WebGPU wallpaper backdrop is off in Slack — the pane is a flat surface */
109
+ html[data-skin="slack"] .holo-wa-root .holo-backdrop { display: none !important; }
110
+ /* far-left workspace rail: flush with the chrome — transparent over the same --wa-bg ground as the menu, no seam,
111
+ so rail + list + gutter are ONE continuous background and only the conversation floats (the Slack move). */
112
+ html[data-skin="slack"] .holo-wa-root .holo-rail { background:var(--holo-chrome, #150E1A) !important; border-right:0 !important; }
113
+ html[data-skin="slack"] .holo-wa-root .holo-rail-btn.on,
114
+ html[data-skin="slack"] .holo-wa-root .holo-rail-net.on { color: var(--wa-accent) !important; }
115
+ /* THE LEFT MENU IS THE BACKGROUND — like Slack, the rail, the channel list and the gutter around the floating
116
+ pane are ONE continuous chrome (the --wa-bg ground), not a separate lighter panel. So every sidebar surface is
117
+ transparent: the menu items sit directly on the background, and the ONLY thing that floats is the conversation
118
+ island. This is what makes the whole shell read as one window with content lifted out of it. */
119
+ html[data-skin="slack"] .holo-wa-root .cs-sidebar.cs-sidebar--left,
120
+ html[data-skin="slack"] .holo-wa-root .cs-conversation-list,
121
+ html[data-skin="slack"] .holo-wa-root .holo-list-head,
122
+ html[data-skin="slack"] .holo-wa-root .holo-list-title,
123
+ html[data-skin="slack"] .holo-wa-root .holo-chips,
124
+ html[data-skin="slack"] .holo-wa-root .holo-catchup { background: transparent !important; }
125
+ html[data-skin="slack"] .holo-wa-root .cs-search { background:#1F0F24 !important; border:1px solid #ffffff12 !important; color:var(--wa-ink) !important; }
126
+ /* conversation rows = inset rounded pills (Slack). Inset applied to EVERY row so hover/active only swap the
127
+ * background — no layout jump. Active carries the highlight + white text. */
128
+ html[data-skin="slack"] .holo-wa-root .cs-conversation { margin:1px 8px !important; border-radius:8px !important; }
129
+ html[data-skin="slack"] .holo-wa-root .cs-conversation:hover { background: var(--slk-hover) !important; }
130
+ html[data-skin="slack"] .holo-wa-root .cs-conversation.cs-conversation--active { background: var(--slk-hi) !important; color:#fff !important; }
131
+ html[data-skin="slack"] .holo-wa-root .cs-conversation.cs-conversation--active * { color:#fff !important; }
132
+ /* pane + its inner surfaces (colours are width-independent; the float geometry is gated below) */
133
+ html[data-skin="slack"] .holo-wa-root .cs-chat-container { background: var(--slk-pane) !important; }
134
+ html[data-skin="slack"] .holo-wa-root .cs-message-list { background: var(--slk-pane) !important; background-image: none !important; }
135
+ html[data-skin="slack"] .holo-wa-root .cs-conversation-header { background: var(--slk-pane) !important; border-bottom: 1px solid #ffffff12; }
136
+ html[data-skin="slack"] .holo-wa-root .holo-composer { background: var(--slk-pane) !important; border-top: 1px solid #ffffff12; }
137
+ html[data-skin="slack"] .holo-wa-root .holo-c-input { background:#26262B !important; border:1px solid #ffffff14 !important; border-radius:8px !important; color:var(--wa-ink) !important; }
138
+
139
+ /* ── the floating envelope (desktop only; narrow layout keeps the bundle's single-column defaults) ── */
140
+ @media (min-width: 900px) {
141
+ html[data-skin="slack"] .holo-wa-root .cs-chat-container {
142
+ flex: 1 1 auto !important; min-width: 0; height: auto !important; align-self: stretch;
143
+ margin: var(--slk-gap) !important; /* even inset on all four sides */
144
+ border-radius: 12px; overflow: hidden;
145
+ box-shadow: 0 0 0 1px #00000055, 0 14px 38px #00000085;
146
+ }
147
+ /* glue the composer to the gapped pane: left tracks the sidebar's 34%, plus the gutter */
148
+ html[data-skin="slack"] .holo-wa-root .holo-composer {
149
+ left: calc(34% + var(--slk-gap)) !important; right: var(--slk-gap) !important;
150
+ bottom: var(--slk-gap) !important; width: auto !important; border-radius: 0 0 12px 12px;
151
+ }
152
+ }
153
+
154
+ /* ── THEATRE MODE — full-screen wallpaper + glass, under the Slack skin ────────────────────────────
155
+ * Theatre (`.theater`, toggle ⌘/Ctrl+\) collapses the rail + sidebar and floats ONE conversation over a
156
+ * FULL-BLEED wallpaper as centred glass capsules (see whatsapp-theme.css "Stage 6"). Three things make it
157
+ * immersive + fast here:
158
+ * 1. WALLPAPER edge-to-edge — a static image on the root (the whole viewport), NOT the WebGPU `.holo-backdrop`
159
+ * canvas (which we turn OFF: it was rendering at opacity:0, all cost no pixels). The image is the one
160
+ * app.html already preloads, so it paints with zero extra latency; every layer above it is transparent.
161
+ * 2. LIGHT + CLEAR — the heavy radial vignette + hue-tint is replaced by a soft top/bottom legibility scrim
162
+ * only, so the middle of the wallpaper stays bright and open.
163
+ * 3. STUNNING GLASS, CHEAP — real backdrop-blur is limited to the TWO capsules (header + composer); message
164
+ * bubbles are plain translucent fills (no per-bubble blur) so a long thread never janks input.
165
+ * Selector depth `…"slack"] .holo-wa-root.theater .X` = (0,4,1) beats the base slack rules (0,3,1) and
166
+ * theatre's own (0,3,0); transforms are left untouched so theatre's centring + auto-quiet fade keep animating. */
167
+ html[data-skin="slack"] .holo-wa-root.theater {
168
+ /* FOLLOW the operator's chosen wallpaper (--wp-img, set from localStorage holo-messenger/wallpaper-src),
169
+ falling back to the preloaded default when unset. `none` (the "plain" mode) is honoured → dark. */
170
+ background: #0b0f14 var(--wp-img, url("/apps/holo-messenger/_vendor/wallpaper-default.jpg")) center center / cover no-repeat !important;
171
+ }
172
+ html[data-skin="slack"] .holo-wa-root.theater .holo-backdrop { display: none !important; } /* WebGPU canvas off → lower latency */
173
+ html[data-skin="slack"] .holo-wa-root.theater .cs-chat-container {
174
+ background: transparent !important; margin: 0 !important; border-radius: 0 !important;
175
+ overflow: visible !important; box-shadow: none !important; /* full-bleed, no slack box */
176
+ }
177
+ /* transparent reading column, aligned to the SAME 812px as the chrome so bubbles line up with the composer */
178
+ html[data-skin="slack"] .holo-wa-root.theater .cs-message-list {
179
+ background: transparent !important;
180
+ padding-left: max(20px, calc((100% - 812px) / 2)) !important;
181
+ padding-right: max(20px, calc((100% - 812px) / 2)) !important;
182
+ }
183
+ /* clear + light: only a soft top/bottom scrim for legibility; the wallpaper stays bright in the middle */
184
+ html[data-skin="slack"] .holo-wa-root.theater .holo-main::before {
185
+ background: linear-gradient(to bottom, rgba(0,0,0,.30) 0%, transparent 16%, transparent 80%, rgba(0,0,0,.42) 100%) !important;
186
+ }
187
+ /* stunning glass capsules — vivid backdrop-blur, crisp edge, top highlight (only these two get real blur) */
188
+ html[data-skin="slack"] .holo-wa-root.theater .cs-conversation-header,
189
+ html[data-skin="slack"] .holo-wa-root.theater .holo-composer {
190
+ background: rgba(22,24,32,.44) !important;
191
+ -webkit-backdrop-filter: blur(30px) saturate(1.4) brightness(1.06) !important;
192
+ backdrop-filter: blur(30px) saturate(1.4) brightness(1.06) !important;
193
+ border: 1px solid rgba(255,255,255,.16) !important;
194
+ box-shadow: 0 14px 40px rgba(0,0,0,.42), inset 0 1px 0 rgba(255,255,255,.16) !important;
195
+ }
196
+ html[data-skin="slack"] .holo-wa-root.theater .holo-composer {
197
+ left: 50% !important; right: auto !important; width: min(calc(100% - 40px), 812px) !important;
198
+ bottom: 18px !important; border-radius: 18px !important;
199
+ }
200
+ /* bubbles: light translucent fills (no blur = no jank on long threads) */
201
+ html[data-skin="slack"] .holo-wa-root.theater .cs-message--incoming .cs-message__content { background: rgba(28,28,36,.62) !important; }
202
+ html[data-skin="slack"] .holo-wa-root.theater .cs-message--outgoing .cs-message__content { background: rgba(58,33,64,.60) !important; }
203
+
204
+ /* ── FRAME-COLLAPSED — the immersive focus view (chats folded into the rail) ───────────────────────
205
+ * "Collapse chats into the rail" (`.holo-main.frame-collapsed`) folds the conversation list away and lets
206
+ * ONE conversation take the whole frame. The slack skin otherwise left it broken: the composer stayed pinned
207
+ * to `left:34%` (the vanished sidebar edge) so it floated stranded in the middle. Here we make it the SAME
208
+ * immersive treatment as theatre — full-screen wallpaper + glass capsules — but KEEP the rail as a clean
209
+ * glass nav (that's the whole point of this mode), and centre the header/composer/reading-column in the frame
210
+ * to the RIGHT of the rail so everything lines up.
211
+ * · `:has(.holo-main.frame-collapsed)` puts the wallpaper on the ROOT (so it sits behind the rail too).
212
+ * · `:not(.theater)` so theatre (which hides the rail) keeps full ownership when both happen to be on.
213
+ * · Only 3 blur layers total (rail + header + composer); bubbles are plain translucent — stays responsive. */
214
+ html[data-skin="slack"] .holo-wa-root:has(.holo-main.frame-collapsed):not(.theater) {
215
+ /* same as theatre: follow the operator's --wp-img, fall back to the preloaded default */
216
+ background: #0b0f14 var(--wp-img, url("/apps/holo-messenger/_vendor/wallpaper-default.jpg")) center center / cover no-repeat !important;
217
+ }
218
+ html[data-skin="slack"] .holo-wa-root:has(.holo-main.frame-collapsed):not(.theater) .holo-backdrop { display: none !important; }
219
+ /* the left nav bar stays — a clean SOLID strip; the wallpaper covers the entire screen EXCEPT this rail */
220
+ html[data-skin="slack"] .holo-wa-root:has(.holo-main.frame-collapsed):not(.theater) .holo-rail {
221
+ background: #17111D !important;
222
+ border-right: 1px solid rgba(255,255,255,.08) !important;
223
+ }
224
+ /* transparent full-bleed frame + reading column centred in the space right of the rail */
225
+ html[data-skin="slack"] .holo-wa-root:not(.theater) .holo-main.frame-collapsed .cs-chat-container {
226
+ background: transparent !important; margin: 0 !important; border-radius: 0 !important; overflow: visible !important; box-shadow: none !important;
227
+ }
228
+ html[data-skin="slack"] .holo-wa-root:not(.theater) .holo-main.frame-collapsed .cs-message-list {
229
+ background: transparent !important;
230
+ padding-left: max(20px, calc((100% - 812px) / 2)) !important;
231
+ padding-right: max(20px, calc((100% - 812px) / 2)) !important;
232
+ }
233
+ html[data-skin="slack"] .holo-wa-root:not(.theater) .holo-main.frame-collapsed::before {
234
+ content: ""; position: absolute; inset: 0; pointer-events: none; z-index: 0;
235
+ background: linear-gradient(to bottom, rgba(0,0,0,.30) 0%, transparent 16%, transparent 80%, rgba(0,0,0,.42) 100%);
236
+ }
237
+ /* glass header + composer, centred in the frame (the rail is the nav, the frame is everything right of it) */
238
+ html[data-skin="slack"] .holo-wa-root:not(.theater) .holo-main.frame-collapsed .cs-conversation-header,
239
+ html[data-skin="slack"] .holo-wa-root:not(.theater) .holo-main.frame-collapsed .holo-composer {
240
+ background: rgba(22,24,32,.44) !important;
241
+ -webkit-backdrop-filter: blur(30px) saturate(1.4) brightness(1.06) !important;
242
+ backdrop-filter: blur(30px) saturate(1.4) brightness(1.06) !important;
243
+ border: 1px solid rgba(255,255,255,.16) !important;
244
+ box-shadow: 0 14px 40px rgba(0,0,0,.42), inset 0 1px 0 rgba(255,255,255,.16) !important;
245
+ }
246
+ html[data-skin="slack"] .holo-wa-root:not(.theater) .holo-main.frame-collapsed .cs-conversation-header {
247
+ margin: 14px auto 0 !important; max-width: 812px !important; width: calc(100% - 40px) !important; border-radius: 16px !important;
248
+ }
249
+ html[data-skin="slack"] .holo-wa-root:not(.theater) .holo-main.frame-collapsed .holo-composer {
250
+ left: 50% !important; right: auto !important; transform: translateX(-50%) !important;
251
+ width: min(calc(100% - 40px), 812px) !important; bottom: 18px !important; border-radius: 18px !important;
252
+ }
253
+ html[data-skin="slack"] .holo-wa-root:not(.theater) .holo-main.frame-collapsed .cs-message--incoming .cs-message__content { background: rgba(28,28,36,.62) !important; }
254
+ html[data-skin="slack"] .holo-wa-root:not(.theater) .holo-main.frame-collapsed .cs-message--outgoing .cs-message__content { background: rgba(58,33,64,.60) !important; }
255
+
256
+ /* ── BASE-LAYER fix: composer alignment in frame-collapsed, for ANY skin ───────────────────────────
257
+ * Skin-agnostic (this file is loaded regardless of the active skin). The bundle pins the composer to
258
+ * `left:34%` — the sidebar's flex-basis — but frame-collapsed folds the sidebar to 0, so on every NON-slack
259
+ * skin (graphite/warm/contrast/light/none) the composer floated stranded in the middle. Align it to the
260
+ * full-bleed chat (left:0 = the rail's right edge; right:0 is already the base). `:not(.theater)` yields to
261
+ * theatre's own centred composer; slack's higher-specificity immersive rule above wins for the slack skin. */
262
+ .holo-wa-root:not(.theater) .holo-main.frame-collapsed .holo-composer { left: 0 !important; }
b/86a3f68c5070d7a543e52768811268d22f2051a5033c11d5281ecd2a36c2dd44 ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // holo-release-boot.mjs — S3 of the downstream-holospace initiative: EVERY boot verifies through the
2
+ // SIGNED release pointer, not just #m1-link opens.
3
+ //
4
+ // Today the worker's integrity bar is seeded from shell-manifest.json — unsigned bytes from the origin —
5
+ // and only an #m1 boot link upgrades it to link-verified. This module makes the SIGNED strand head the
6
+ // standing bar: fetch release.json (the ONE mutable fetch, no-store) → acceptHead() against the local pin
7
+ // (TOFU, key-continuity, seq-monotonic — holo-release-verify.mjs) → prove the origin's manifest is the
8
+ // EXACT one the release signed (bytes re-derive to payload.manifest, aggregate re-derives to payload.shell)
9
+ // → only then pin + hand the manifest to the worker. A refused pointer changes NOTHING: the worker keeps
10
+ // serving the last verified shell (fail-closed to last-good, never to blank).
11
+ //
12
+ // Additive + fail-soft at the seam (a deploy without release.json is a quiet no-op), fail-CLOSED at the
13
+ // bar (no signature → no upgrade). Injectable core; the browser auto-run wires live fetch/storage/worker.
14
+
15
+ import { acceptHead } from "./holo-release-verify.mjs";
16
+ import { shellAggregate } from "./holo-m1-boot.mjs";
17
+
18
+ export const PIN_KEY = "holo.release.pin";
19
+ const _hex = (buf) => [...new Uint8Array(buf)].map((x) => x.toString(16).padStart(2, "0")).join("");
20
+ const sha256hex = async (buf) => _hex(await (globalThis.crypto).subtle.digest("SHA-256", buf));
21
+
22
+ // releaseBoot({ fetchFn, storage, post, baseUrl }) → { ok, why?, pin?, unchanged? }
23
+ // fetchFn — fetch-compatible; storage — localStorage-compatible {getItem,setItem};
24
+ // post — receives the verified manifest message for the worker; baseUrl — this module's URL (relocatable).
25
+ export async function releaseBoot({ fetchFn, storage, post = null, baseUrl } = {}) {
26
+ // 1. the pointer — the ONE mutable fetch (everything else it names is immutable, κ-addressed)
27
+ let head = null;
28
+ try { const r = await fetchFn(new URL("../../release.json", baseUrl), { cache: "no-store" }); if (r && r.ok) head = await r.json(); } catch {}
29
+ if (!head) return { ok: false, why: "no-pointer" }; // pre-strand deploy: additive no-op
30
+ // 2. accept against the durable pin (SEC-1/4: forge, tamper, replay, key-swap → refused by name)
31
+ let pinned = null; try { pinned = JSON.parse(storage.getItem(PIN_KEY) || "null"); } catch {}
32
+ const a = await acceptHead(head, pinned);
33
+ if (!a.ok) return { ok: false, why: a.why, pinned }; // bar unchanged — last-good keeps serving
34
+ // 3. the head COMMITS the shell: the origin's manifest must be the exact object the release signed
35
+ const p = head["holstr:payload"] || {};
36
+ let mfBytes = null;
37
+ try { const r = await fetchFn(new URL("./shell-manifest.json", baseUrl), { cache: "no-store" }); if (r && r.ok) mfBytes = await r.arrayBuffer(); } catch {}
38
+ if (!mfBytes) return { ok: false, why: "no-manifest", head: head.id };
39
+ if (p.manifest && (await sha256hex(mfBytes)) !== p.manifest) return { ok: false, why: "manifest-not-signed", head: head.id };
40
+ let manifest = null; try { manifest = JSON.parse(new TextDecoder().decode(mfBytes)); } catch {}
41
+ const agg = manifest ? await shellAggregate(manifest) : "";
42
+ if (!agg || agg !== p.shell) return { ok: false, why: "shell-mismatch", head: head.id };
43
+ // 4. accepted — advance the pin, upgrade the worker's bar to SIGNED-verified (what #m1 links did, now standing)
44
+ try { storage.setItem(PIN_KEY, JSON.stringify(a.pin)); } catch {}
45
+ try { if (post) post({ type: "holo-shell-manifest", assets: manifest.assets, runtime: manifest.runtime || [] }); } catch {}
46
+ return { ok: true, pin: a.pin, unchanged: !!a.unchanged, shell: p.shell };
47
+ }
48
+
49
+ // rollback is an EXPLICIT LOCAL ACT (never something a remote pointer can cause): re-pin a parent entry
50
+ // the operator chose from the strand. The next acceptHead treats it as the baseline.
51
+ export function repin(storage, entry) {
52
+ const pin = { id: entry.id, seq: entry["holstr:seq"], pub: entry["holstr:pub"] };
53
+ storage.setItem(PIN_KEY, JSON.stringify(pin));
54
+ return pin;
55
+ }
56
+
57
+ // ── browser auto-run: after the worker registers, verify the pointer off the critical path ────────────
58
+ if (typeof window !== "undefined" && typeof navigator !== "undefined") {
59
+ (async () => {
60
+ try {
61
+ if (!("serviceWorker" in navigator)) return;
62
+ const reg = await navigator.serviceWorker.ready;
63
+ const r = await releaseBoot({
64
+ fetchFn: fetch.bind(globalThis), storage: window.localStorage, baseUrl: import.meta.url,
65
+ post: (m) => { try { (reg.active || navigator.serviceWorker.controller)?.postMessage(m); } catch {} },
66
+ });
67
+ window.HoloRelease = Object.assign(window.HoloRelease || {}, { last: r, repin: (e) => repin(window.localStorage, e) });
68
+ if (r.ok && !r.unchanged) console.log("[release] signed head pinned: seq " + r.pin.seq + " " + r.pin.id.slice(-16));
69
+ else if (!r.ok && r.why !== "no-pointer") console.warn("[release] pointer refused (" + r.why + ") — shell stays on the last verified release");
70
+ } catch {}
71
+ })();
72
+ }
b/8c30aa5582dffe4548d27fa7863163eba689ed65126be472c003f5884fc8e534 ADDED
@@ -0,0 +1,1870 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* @ts-self-types="./holospaces_web.d.ts" */
2
+
3
+ /**
4
+ * **The browser peer's AArch64 holospace** — a real arm64 devcontainer booted on
5
+ * the engine's [AArch64 core](holospaces::emulator::aarch64) (ADR-021; `CC-36` a
6
+ * real arm64 Linux boots, `CC-37` a stock `linux-arm64` binary runs). Its κ-disk
7
+ * is the **same substrate-backed `virtio-blk`** the RISC-V [`Workspace`] uses —
8
+ * the shared device bus (`emulator::devbus`), no per-ISA re-implementation (Law
9
+ * L4; `CC-7`/`CC-14`). Boot + the interactive terminal (`CC-11`) are complete;
10
+ * the AArch64 core's `virtio-net`/`9p` parity is the continued engine build, so
11
+ * this surface exposes only what the core does today — nothing it cannot fulfil.
12
+ */
13
+ export class Aarch64Workspace {
14
+ static __wrap(ptr) {
15
+ const obj = Object.create(Aarch64Workspace.prototype);
16
+ obj.__wbg_ptr = ptr;
17
+ Aarch64WorkspaceFinalization.register(obj, obj.__wbg_ptr, obj);
18
+ return obj;
19
+ }
20
+ __destroy_into_raw() {
21
+ const ptr = this.__wbg_ptr;
22
+ this.__wbg_ptr = 0;
23
+ Aarch64WorkspaceFinalization.unregister(this);
24
+ return ptr;
25
+ }
26
+ free() {
27
+ const ptr = this.__destroy_into_raw();
28
+ wasm.__wbg_aarch64workspace_free(ptr, 0);
29
+ }
30
+ /**
31
+ * Boot a **devcontainer** on the AArch64 core: the assembled `ext4` `rootfs`
32
+ * (from [`DevcontainerImage::assemble_bootable`]) is taken as κ-addressed
33
+ * content into the shared `virtio-blk` κ-disk (`CC-7`) and mounted over
34
+ * `/dev/vda`; the real, unmodified arm64 Linux `kernel` boots it and runs the
35
+ * devcontainer's OS — entirely in the browser peer (ADR-009/011/021, `CC-37`).
36
+ * Drive with [`run`](Aarch64Workspace::run), streaming
37
+ * [`terminal_delta`](Aarch64Workspace::terminal_delta) and feeding keystrokes
38
+ * with [`feed_input`](Aarch64Workspace::feed_input).
39
+ * @param {Uint8Array} kernel
40
+ * @param {Uint8Array} rootfs
41
+ * @returns {Aarch64Workspace}
42
+ */
43
+ static boot_devcontainer(kernel, rootfs) {
44
+ const ptr0 = passArray8ToWasm0(kernel, wasm.__wbindgen_malloc);
45
+ const len0 = WASM_VECTOR_LEN;
46
+ const ptr1 = passArray8ToWasm0(rootfs, wasm.__wbindgen_malloc);
47
+ const len1 = WASM_VECTOR_LEN;
48
+ const ret = wasm.aarch64workspace_boot_devcontainer(ptr0, len0, ptr1, len1);
49
+ return Aarch64Workspace.__wrap(ret);
50
+ }
51
+ /**
52
+ * Feed raw terminal input to the running guest (`CC-11`, the AArch64 PL011
53
+ * analogue): the bytes become readable at the serial console, so the guest's
54
+ * own tty echoes, line-edits, and raises SIGINT on Ctrl-C — a real terminal.
55
+ * @param {Uint8Array} bytes
56
+ */
57
+ feed_input(bytes) {
58
+ const ptr0 = passArray8ToWasm0(bytes, wasm.__wbindgen_malloc);
59
+ const len0 = WASM_VECTOR_LEN;
60
+ wasm.aarch64workspace_feed_input(this.__wbg_ptr, ptr0, len0);
61
+ }
62
+ /**
63
+ * Whether the machine has powered off.
64
+ * @returns {boolean}
65
+ */
66
+ get halted() {
67
+ const ret = wasm.aarch64workspace_halted(this.__wbg_ptr);
68
+ return ret !== 0;
69
+ }
70
+ /**
71
+ * Advance the running holospace by `budget` instructions (one chunk of the
72
+ * boot or of servicing input); returns `true` once the machine has halted.
73
+ * @param {number} budget
74
+ * @returns {boolean}
75
+ */
76
+ run(budget) {
77
+ const ret = wasm.aarch64workspace_run(this.__wbg_ptr, budget);
78
+ return ret !== 0;
79
+ }
80
+ /**
81
+ * Whether the terminal has rendered `marker` yet (e.g. a ready banner).
82
+ * @param {string} marker
83
+ * @returns {boolean}
84
+ */
85
+ shows(marker) {
86
+ const ptr0 = passStringToWasm0(marker, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
87
+ const len0 = WASM_VECTOR_LEN;
88
+ const ret = wasm.aarch64workspace_shows(this.__wbg_ptr, ptr0, len0);
89
+ return ret !== 0;
90
+ }
91
+ /**
92
+ * The full console the guest has produced.
93
+ * @returns {string}
94
+ */
95
+ terminal() {
96
+ let deferred1_0;
97
+ let deferred1_1;
98
+ try {
99
+ const ret = wasm.aarch64workspace_terminal(this.__wbg_ptr);
100
+ deferred1_0 = ret[0];
101
+ deferred1_1 = ret[1];
102
+ return getStringFromWasm0(ret[0], ret[1]);
103
+ } finally {
104
+ wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
105
+ }
106
+ }
107
+ /**
108
+ * The console bytes produced **since the last call** — the integrated
109
+ * terminal's render loop (output stays O(new bytes) per frame).
110
+ * @returns {Uint8Array}
111
+ */
112
+ terminal_delta() {
113
+ const ret = wasm.aarch64workspace_terminal_delta(this.__wbg_ptr);
114
+ var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
115
+ wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
116
+ return v1;
117
+ }
118
+ }
119
+ if (Symbol.dispose) Aarch64Workspace.prototype[Symbol.dispose] = Aarch64Workspace.prototype.free;
120
+
121
+ /**
122
+ * The Platform Manager console, running as a browser peer that composes the
123
+ * substrate runtime over the interpreter `ContainerEngine`.
124
+ */
125
+ export class Console {
126
+ __destroy_into_raw() {
127
+ const ptr = this.__wbg_ptr;
128
+ this.__wbg_ptr = 0;
129
+ ConsoleFinalization.unregister(this);
130
+ return ptr;
131
+ }
132
+ free() {
133
+ const ptr = this.__destroy_into_raw();
134
+ wasm.__wbg_console_free(ptr, 0);
135
+ }
136
+ /**
137
+ * Boot a userland holospace **in the browser**: provision it, then spawn it
138
+ * through the substrate runtime over the interpreter `ContainerEngine`,
139
+ * capture a κ snapshot of its state (suspend), resume, and terminate — the
140
+ * execution surface running on the browser peer (ADR-008; RT2; `CC-6`).
141
+ * Returns the κ-label of the suspend snapshot (state is content, Law L3).
142
+ * @param {Uint8Array} module
143
+ * @param {number} memory_bytes
144
+ * @returns {string}
145
+ */
146
+ boot_userland(module, memory_bytes) {
147
+ let deferred3_0;
148
+ let deferred3_1;
149
+ try {
150
+ const ptr0 = passArray8ToWasm0(module, wasm.__wbindgen_malloc);
151
+ const len0 = WASM_VECTOR_LEN;
152
+ const ret = wasm.console_boot_userland(this.__wbg_ptr, ptr0, len0, memory_bytes);
153
+ var ptr2 = ret[0];
154
+ var len2 = ret[1];
155
+ if (ret[3]) {
156
+ ptr2 = 0; len2 = 0;
157
+ throw takeFromExternrefTable0(ret[2]);
158
+ }
159
+ deferred3_0 = ptr2;
160
+ deferred3_1 = len2;
161
+ return getStringFromWasm0(ptr2, len2);
162
+ } finally {
163
+ wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
164
+ }
165
+ }
166
+ /**
167
+ * *Control panel: configure.* Reconfigure a running instance from the panel
168
+ * (ADR-018; `CC-28`). `directives_json` is a JSON array of operations across
169
+ * the four classes, e.g. `[{"lifecycle":"suspend"}, {"forwardPort":8080},
170
+ * {"unforwardPort":8080}, {"network":{"fetch":true,"announce":false}},
171
+ * {"quota":1073741824}, {"grant":"blake3:…"}]`. The panel builds a
172
+ * content-addressed [`Configuration`] issued by the signed-in operator,
173
+ * stores it (Law L2), and returns its κ — the content the running instance
174
+ * resolves and applies over the substrate (no server, no RPC).
175
+ * @param {string} instance
176
+ * @param {string} directives_json
177
+ * @returns {string}
178
+ */
179
+ configure(instance, directives_json) {
180
+ let deferred4_0;
181
+ let deferred4_1;
182
+ try {
183
+ const ptr0 = passStringToWasm0(instance, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
184
+ const len0 = WASM_VECTOR_LEN;
185
+ const ptr1 = passStringToWasm0(directives_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
186
+ const len1 = WASM_VECTOR_LEN;
187
+ const ret = wasm.console_configure(this.__wbg_ptr, ptr0, len0, ptr1, len1);
188
+ var ptr3 = ret[0];
189
+ var len3 = ret[1];
190
+ if (ret[3]) {
191
+ ptr3 = 0; len3 = 0;
192
+ throw takeFromExternrefTable0(ret[2]);
193
+ }
194
+ deferred4_0 = ptr3;
195
+ deferred4_1 = len3;
196
+ return getStringFromWasm0(ptr3, len3);
197
+ } finally {
198
+ wasm.__wbindgen_free(deferred4_0, deferred4_1, 1);
199
+ }
200
+ }
201
+ /**
202
+ * Open a fresh console — a browser peer with a local content-addressed
203
+ * store and the interpreter container engine.
204
+ */
205
+ constructor() {
206
+ const ret = wasm.console_new();
207
+ this.__wbg_ptr = ret;
208
+ ConsoleFinalization.register(this, this.__wbg_ptr, this);
209
+ return this;
210
+ }
211
+ /**
212
+ * Provision a holospace from a `.holo` compute artifact (the *holo-file*
213
+ * compute form) with a memory budget, κ-addressing its parts into the
214
+ * peer's store (Law L2). Returns the holospace identity κ.
215
+ * @param {Uint8Array} code
216
+ * @param {number} memory_bytes
217
+ * @returns {string}
218
+ */
219
+ provision(code, memory_bytes) {
220
+ let deferred3_0;
221
+ let deferred3_1;
222
+ try {
223
+ const ptr0 = passArray8ToWasm0(code, wasm.__wbindgen_malloc);
224
+ const len0 = WASM_VECTOR_LEN;
225
+ const ret = wasm.console_provision(this.__wbg_ptr, ptr0, len0, memory_bytes);
226
+ var ptr2 = ret[0];
227
+ var len2 = ret[1];
228
+ if (ret[3]) {
229
+ ptr2 = 0; len2 = 0;
230
+ throw takeFromExternrefTable0(ret[2]);
231
+ }
232
+ deferred3_0 = ptr2;
233
+ deferred3_1 = len2;
234
+ return getStringFromWasm0(ptr2, len2);
235
+ } finally {
236
+ wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
237
+ }
238
+ }
239
+ /**
240
+ * Provision a holospace from a **devcontainer** for the management console
241
+ * (CC-12): the `devcontainer.json` is validated against the Dev Container
242
+ * spec (`CC-4`) and κ-addressed into the store; the holospace's identity is
243
+ * the content address of its devcontainer definition (reproducible — same
244
+ * source ⇒ same κ, Law L1). This *provisions* (records) the holospace; the
245
+ * operator *enters* it to boot its OS in the workspace IDE (`CC-13`).
246
+ * Returns the holospace identity κ.
247
+ * @param {Uint8Array} config_json
248
+ * @param {string} arch
249
+ * @param {number} memory_bytes
250
+ * @returns {string}
251
+ */
252
+ provision_devcontainer(config_json, arch, memory_bytes) {
253
+ let deferred4_0;
254
+ let deferred4_1;
255
+ try {
256
+ const ptr0 = passArray8ToWasm0(config_json, wasm.__wbindgen_malloc);
257
+ const len0 = WASM_VECTOR_LEN;
258
+ const ptr1 = passStringToWasm0(arch, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
259
+ const len1 = WASM_VECTOR_LEN;
260
+ const ret = wasm.console_provision_devcontainer(this.__wbg_ptr, ptr0, len0, ptr1, len1, memory_bytes);
261
+ var ptr3 = ret[0];
262
+ var len3 = ret[1];
263
+ if (ret[3]) {
264
+ ptr3 = 0; len3 = 0;
265
+ throw takeFromExternrefTable0(ret[2]);
266
+ }
267
+ deferred4_0 = ptr3;
268
+ deferred4_1 = len3;
269
+ return getStringFromWasm0(ptr3, len3);
270
+ } finally {
271
+ wasm.__wbindgen_free(deferred4_0, deferred4_1, 1);
272
+ }
273
+ }
274
+ /**
275
+ * Provision a holospace from a *Wasm-recompiled userland* (the execution
276
+ * surface, the second compute form — ADR-008). The module is validated
277
+ * against the surface contract ([`validate_userland`]) before it is
278
+ * κ-addressed into the store, so only a substrate-valid userland can become
279
+ * a holospace's code. Returns the holospace identity κ.
280
+ * @param {Uint8Array} module
281
+ * @param {number} memory_bytes
282
+ * @returns {string}
283
+ */
284
+ provision_userland(module, memory_bytes) {
285
+ let deferred3_0;
286
+ let deferred3_1;
287
+ try {
288
+ const ptr0 = passArray8ToWasm0(module, wasm.__wbindgen_malloc);
289
+ const len0 = WASM_VECTOR_LEN;
290
+ const ret = wasm.console_provision_userland(this.__wbg_ptr, ptr0, len0, memory_bytes);
291
+ var ptr2 = ret[0];
292
+ var len2 = ret[1];
293
+ if (ret[3]) {
294
+ ptr2 = 0; len2 = 0;
295
+ throw takeFromExternrefTable0(ret[2]);
296
+ }
297
+ deferred3_0 = ptr2;
298
+ deferred3_1 = len2;
299
+ return getStringFromWasm0(ptr2, len2);
300
+ } finally {
301
+ wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
302
+ }
303
+ }
304
+ /**
305
+ * Resolve a holospace (or any κ) from this peer's own in-session store.
306
+ * Returns the bytes, or `undefined` if absent.
307
+ *
308
+ * This is a *trusted* read ([`ReadVerify::Trusted`], ADR-019): the store is
309
+ * the canonical memory and RAM is its cache (Law L3), so content that
310
+ * entered this session was already verified on the way in (on receipt, or
311
+ * by `put` construction). The deployed peer does not re-derive κ on every
312
+ * local read — that would treat its own canonical store as untrusted and is
313
+ * pure overhead. The re-derivation invariant still holds where untrusted
314
+ * bytes enter (the import/fetch boundary) and is exercised end-to-end in CI.
315
+ * @param {string} kappa
316
+ * @returns {Uint8Array | undefined}
317
+ */
318
+ resolve(kappa) {
319
+ const ptr0 = passStringToWasm0(kappa, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
320
+ const len0 = WASM_VECTOR_LEN;
321
+ const ret = wasm.console_resolve(this.__wbg_ptr, ptr0, len0);
322
+ if (ret[3]) {
323
+ throw takeFromExternrefTable0(ret[2]);
324
+ }
325
+ let v2;
326
+ if (ret[0] !== 0) {
327
+ v2 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
328
+ wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
329
+ }
330
+ return v2;
331
+ }
332
+ /**
333
+ * The operator's roster κ — the content address that links their instances
334
+ * (R5). Its bytes are in the store, so another instance can resolve it.
335
+ * @returns {string | undefined}
336
+ */
337
+ roster_kappa() {
338
+ const ret = wasm.console_roster_kappa(this.__wbg_ptr);
339
+ let v1;
340
+ if (ret[0] !== 0) {
341
+ v1 = getStringFromWasm0(ret[0], ret[1]).slice();
342
+ wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
343
+ }
344
+ return v1;
345
+ }
346
+ /**
347
+ * Import and run a **devcontainer in the browser** — the Codespaces/Gitpod
348
+ * scenario without a Docker daemon or a cloud VM (arc42 chapter 1, the
349
+ * motivating scenario; chapter 6). The `devcontainer.json` is validated
350
+ * against the Dev Container spec (`CC-4`); the κ-addressed Wasm `userland`
351
+ * its config selects is validated against the host-ABI surface (`CC-6`) and
352
+ * booted through the substrate runtime over the interpreter engine — same
353
+ * lifecycle as a native or remote peer (Q6). Returns the suspend snapshot κ.
354
+ *
355
+ * `arch` is the operator's **architecture selection** (the Manager GUI's
356
+ * arch picker; ADR-021) — `"riscv64"` or `"aarch64"`. It becomes part of the
357
+ * holospace's content-addressed identity, so it is fixed for the holospace's
358
+ * lifetime (an unknown id falls back to the default RISC-V target).
359
+ * @param {string} repo
360
+ * @param {string} reference
361
+ * @param {string} config_path
362
+ * @param {Uint8Array} config_json
363
+ * @param {Uint8Array} userland_module
364
+ * @param {string} arch
365
+ * @param {number} memory_bytes
366
+ * @returns {string}
367
+ */
368
+ run_devcontainer(repo, reference, config_path, config_json, userland_module, arch, memory_bytes) {
369
+ let deferred8_0;
370
+ let deferred8_1;
371
+ try {
372
+ const ptr0 = passStringToWasm0(repo, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
373
+ const len0 = WASM_VECTOR_LEN;
374
+ const ptr1 = passStringToWasm0(reference, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
375
+ const len1 = WASM_VECTOR_LEN;
376
+ const ptr2 = passStringToWasm0(config_path, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
377
+ const len2 = WASM_VECTOR_LEN;
378
+ const ptr3 = passArray8ToWasm0(config_json, wasm.__wbindgen_malloc);
379
+ const len3 = WASM_VECTOR_LEN;
380
+ const ptr4 = passArray8ToWasm0(userland_module, wasm.__wbindgen_malloc);
381
+ const len4 = WASM_VECTOR_LEN;
382
+ const ptr5 = passStringToWasm0(arch, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
383
+ const len5 = WASM_VECTOR_LEN;
384
+ const ret = wasm.console_run_devcontainer(this.__wbg_ptr, ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3, ptr4, len4, ptr5, len5, memory_bytes);
385
+ var ptr7 = ret[0];
386
+ var len7 = ret[1];
387
+ if (ret[3]) {
388
+ ptr7 = 0; len7 = 0;
389
+ throw takeFromExternrefTable0(ret[2]);
390
+ }
391
+ deferred8_0 = ptr7;
392
+ deferred8_1 = len7;
393
+ return getStringFromWasm0(ptr7, len7);
394
+ } finally {
395
+ wasm.__wbindgen_free(deferred8_0, deferred8_1, 1);
396
+ }
397
+ }
398
+ /**
399
+ * Sign in by unlocking a self-sovereign key (not a server account,
400
+ * ADR-001). Returns the operator's content-addressed identity κ.
401
+ * @param {Uint8Array} key
402
+ * @returns {string}
403
+ */
404
+ sign_in(key) {
405
+ let deferred2_0;
406
+ let deferred2_1;
407
+ try {
408
+ const ptr0 = passArray8ToWasm0(key, wasm.__wbindgen_malloc);
409
+ const len0 = WASM_VECTOR_LEN;
410
+ const ret = wasm.console_sign_in(this.__wbg_ptr, ptr0, len0);
411
+ deferred2_0 = ret[0];
412
+ deferred2_1 = ret[1];
413
+ return getStringFromWasm0(ret[0], ret[1]);
414
+ } finally {
415
+ wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
416
+ }
417
+ }
418
+ /**
419
+ * The console's View — a JSON projection of the operator and their
420
+ * holospaces (what the UI renders).
421
+ * @returns {string}
422
+ */
423
+ view() {
424
+ let deferred1_0;
425
+ let deferred1_1;
426
+ try {
427
+ const ret = wasm.console_view(this.__wbg_ptr);
428
+ deferred1_0 = ret[0];
429
+ deferred1_1 = ret[1];
430
+ return getStringFromWasm0(ret[0], ret[1]);
431
+ } finally {
432
+ wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
433
+ }
434
+ }
435
+ }
436
+ if (Symbol.dispose) Console.prototype[Symbol.dispose] = Console.prototype.free;
437
+
438
+ /**
439
+ * A devcontainer's OCI image, assembled into a bootable root filesystem *in the
440
+ * browser* — the Layer Assembler (`CC-7` / the in-crate ext4 writer) running as
441
+ * the wasm peer. The operator's page fetches the devcontainer's image layers
442
+ * from the cold-start gateway (verified by re-derivation before they are added),
443
+ * then assembles them here; the result boots over the emulator's `virtio-blk`
444
+ * ([`Workspace::boot_devcontainer`], `CC-14`). The browser peer *is* the
445
+ * machine — no server assembles or boots the OS (Law L1/L4).
446
+ */
447
+ export class DevcontainerImage {
448
+ __destroy_into_raw() {
449
+ const ptr = this.__wbg_ptr;
450
+ this.__wbg_ptr = 0;
451
+ DevcontainerImageFinalization.unregister(this);
452
+ return ptr;
453
+ }
454
+ free() {
455
+ const ptr = this.__destroy_into_raw();
456
+ wasm.__wbg_devcontainerimage_free(ptr, 0);
457
+ }
458
+ /**
459
+ * Add an OCI image layer (its media type + the verified blob bytes), in
460
+ * order from the base layer up.
461
+ * @param {string} media_type
462
+ * @param {Uint8Array} blob
463
+ */
464
+ add_layer(media_type, blob) {
465
+ const ptr0 = passStringToWasm0(media_type, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
466
+ const len0 = WASM_VECTOR_LEN;
467
+ const ptr1 = passArray8ToWasm0(blob, wasm.__wbindgen_malloc);
468
+ const len1 = WASM_VECTOR_LEN;
469
+ wasm.devcontainerimage_add_layer(this.__wbg_ptr, ptr0, len0, ptr1, len1);
470
+ }
471
+ /**
472
+ * Assemble the layers into a bootable `ext4` root filesystem (gunzip +
473
+ * untar + OCI whiteout overlay + the in-crate ext4 writer; Law L4). The
474
+ * bytes back a [`Workspace::boot_devcontainer`] machine's `virtio-blk` disk.
475
+ * @returns {Uint8Array}
476
+ */
477
+ assemble() {
478
+ const ret = wasm.devcontainerimage_assemble(this.__wbg_ptr);
479
+ if (ret[3]) {
480
+ throw takeFromExternrefTable0(ret[2]);
481
+ }
482
+ var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
483
+ wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
484
+ return v1;
485
+ }
486
+ /**
487
+ * Assemble the layers into a **bootable, interactive, writable** root
488
+ * filesystem on a `disk_bytes`-sized disk: the same overlay as
489
+ * [`Self::assemble`], plus the persistent devcontainer
490
+ * [`/init`](holospaces::machine::DEVCONTAINER_INIT) injected — it mounts the
491
+ * pseudo filesystems and the shared `virtio-9p` workspace and execs a shell,
492
+ * so the booted OS stays running as a dev environment instead of powering off
493
+ * after boot — and sized to `disk_bytes` so the OS has room to work (the
494
+ * devcontainer's disk; the caller's to choose, not a hidden cap). The base
495
+ * image must provide a static `/bin/busybox`.
496
+ * @param {number} disk_bytes
497
+ * @returns {Uint8Array}
498
+ */
499
+ assemble_bootable(disk_bytes) {
500
+ const ret = wasm.devcontainerimage_assemble_bootable(this.__wbg_ptr, disk_bytes);
501
+ if (ret[3]) {
502
+ throw takeFromExternrefTable0(ret[2]);
503
+ }
504
+ var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
505
+ wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
506
+ return v1;
507
+ }
508
+ /**
509
+ * Like [`Self::assemble_bootable_real`], but stream the ext4 image **straight
510
+ * into an OPFS file** (`CC-50`, the sparse streaming Rootfs Assembly): only the
511
+ * non-zero blocks are written (the free data region stays a sparse hole), so
512
+ * even a multi-GiB disk assembles **without ever materializing a dense buffer**
513
+ * in wasm RAM — peak working memory tracks the image's *content*, not its
514
+ * declared size ("the KappaStore IS the memory, RAM is a cache", Laws L3/L4).
515
+ * `rootfs_handle` is a worker sync access handle on the rootfs file; returns the
516
+ * total image length (a whole number of sectors). The page then boots it
517
+ * off-heap with [`Workspace::boot_devcontainer_opfs_streamed`] — neither
518
+ * provisioning nor boot ever holds the whole image in RAM (the lean,
519
+ * substrate-native path).
520
+ * @param {FileSystemSyncAccessHandle} rootfs_handle
521
+ * @param {number} disk_bytes
522
+ * @param {boolean} real
523
+ * @returns {number}
524
+ */
525
+ assemble_bootable_into_opfs(rootfs_handle, disk_bytes, real) {
526
+ const ret = wasm.devcontainerimage_assemble_bootable_into_opfs(this.__wbg_ptr, rootfs_handle, disk_bytes, real);
527
+ if (ret[2]) {
528
+ throw takeFromExternrefTable0(ret[1]);
529
+ }
530
+ return ret[0];
531
+ }
532
+ /**
533
+ * Like [`Self::assemble_bootable`], but inject the **real-OCI-image** `/init`
534
+ * ([`REAL_IMAGE_INIT`](holospaces::machine::REAL_IMAGE_INIT), `CC-42`) — for a
535
+ * repository's own base image (debian, ubuntu, buildpack-deps) that ships its
536
+ * own `/bin/sh` + coreutils + `mount` rather than a static `/bin/busybox`. It
537
+ * mounts the pseudo-filesystems + the shared `virtio-9p` workspace with the
538
+ * image's tools and execs its login shell (`bash` if present, else `sh`), so a
539
+ * stock Debian image boots to its real interactive shell.
540
+ * @param {number} disk_bytes
541
+ * @returns {Uint8Array}
542
+ */
543
+ assemble_bootable_real(disk_bytes) {
544
+ const ret = wasm.devcontainerimage_assemble_bootable_real(this.__wbg_ptr, disk_bytes);
545
+ if (ret[3]) {
546
+ throw takeFromExternrefTable0(ret[2]);
547
+ }
548
+ var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
549
+ wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
550
+ return v1;
551
+ }
552
+ /**
553
+ * A new, empty image (add its layers lowest-first with [`Self::add_layer`]).
554
+ */
555
+ constructor() {
556
+ const ret = wasm.devcontainerimage_new();
557
+ this.__wbg_ptr = ret;
558
+ DevcontainerImageFinalization.register(this, this.__wbg_ptr, this);
559
+ return this;
560
+ }
561
+ }
562
+ if (Symbol.dispose) DevcontainerImage.prototype[Symbol.dispose] = DevcontainerImage.prototype.free;
563
+
564
+ /**
565
+ * **The interactive userland Workspace** — a real CC-6 Wasm code module (e.g.
566
+ * `qemu-system-x86_64` compiled to wasm32 over the hologram host ABI) run *as a
567
+ * live, interactive holospace* in the browser tab (ADR-008/009; `CC-6` + the
568
+ * interactive `CC-11` console). Where [`Console::boot_userland`] proves the
569
+ * one-shot lifecycle (boot → suspend → resume → terminate) through the substrate
570
+ * runtime, this drives the *same validated module* on the *same `wasmi`
571
+ * interpreter* the engine uses — pumped in cooperative slices with a live
572
+ * console, so the operator gets a real terminal (the QEMU holospace; see
573
+ * `web/QEMU-HOLOSPACE.md`).
574
+ *
575
+ * **Why a direct `wasmi` driver, not `Session`/`ContainerRuntime`.** The browser
576
+ * `ContainerEngine` (hologram-runtime-bare) wires an *empty* `Linker`: a CC-6
577
+ * container has **no** `hologram.*` host imports, and the one-shot `spawn /
578
+ * suspend / resume / terminate` runtime exposes neither a sub-budget run nor a
579
+ * console stream. So an interactive QEMU communicates the only way that surface
580
+ * allows — through **linear memory** between calls (a mailbox), exactly as the
581
+ * emulator [`Workspace`] drives the native [`Emulator`](holospaces::emulator)
582
+ * directly rather than through the runtime. The module is still gated by
583
+ * [`validate_userland`] (the `CC-6` host-ABI surface) before it runs, and is
584
+ * still a self-contained substrate userland (it imports nothing).
585
+ *
586
+ * **The container ABI (the contract `build-qemu-wasm.sh` must satisfy).** The
587
+ * module exports `memory` and: `hg_mailbox() -> i32` (the control struct's byte
588
+ * offset); `hg_init() -> i32` (build the machine from the cfg region — argv +
589
+ * disk the host wrote; `0` = ok); `hg_event(budget: i32) -> i32` (drain stdin
590
+ * from `in`, run up to `budget` guest instructions as a cooperative TCG/TCI slice
591
+ * that **returns** rather than blocking, append console bytes to `out`, set
592
+ * `halted`; `0` = ok, a trap halts); and optional `hg_suspend()`/`hg_resume()`
593
+ * flush/rehydrate around the snapshot. The mailbox is a little-endian `u32`
594
+ * struct: `magic, abi, cfg_ptr, cfg_cap, cfg_len, in_ptr, in_cap, in_len,
595
+ * out_ptr, out_cap, out_len, halted`. The snapshot is taken at an `hg_event`
596
+ * boundary (the C stack unwound), so linear memory holds all persistent machine
597
+ * state (CPU, RAM, devices) and transient Wasm globals need not be carried
598
+ * (`CC-30`/`CC-31`).
599
+ */
600
+ export class UserlandWorkspace {
601
+ static __wrap(ptr) {
602
+ const obj = Object.create(UserlandWorkspace.prototype);
603
+ obj.__wbg_ptr = ptr;
604
+ UserlandWorkspaceFinalization.register(obj, obj.__wbg_ptr, obj);
605
+ return obj;
606
+ }
607
+ __destroy_into_raw() {
608
+ const ptr = this.__wbg_ptr;
609
+ this.__wbg_ptr = 0;
610
+ UserlandWorkspaceFinalization.unregister(this);
611
+ return ptr;
612
+ }
613
+ free() {
614
+ const ptr = this.__destroy_into_raw();
615
+ wasm.__wbg_userlandworkspace_free(ptr, 0);
616
+ }
617
+ /**
618
+ * Boot a CC-6 module as an interactive holospace. `module` is the QEMU wasm
619
+ * (validated against the host-ABI surface, `CC-6`); `argv` is the QEMU command
620
+ * line, one argument per line; `disk` is the guest image (held in the guest's
621
+ * linear memory in v1 — a large disk needs the staged κ-disk sector protocol).
622
+ * `memory_bytes` and `relay_url` are reserved (the guest sizes its own RAM
623
+ * today; networking is the staged `CC-16` step).
624
+ * @param {Uint8Array} module
625
+ * @param {string} argv
626
+ * @param {Uint8Array} disk
627
+ * @param {number} memory_bytes
628
+ * @param {string} relay_url
629
+ * @returns {UserlandWorkspace}
630
+ */
631
+ static boot(module, argv, disk, memory_bytes, relay_url) {
632
+ const ptr0 = passArray8ToWasm0(module, wasm.__wbindgen_malloc);
633
+ const len0 = WASM_VECTOR_LEN;
634
+ const ptr1 = passStringToWasm0(argv, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
635
+ const len1 = WASM_VECTOR_LEN;
636
+ const ptr2 = passArray8ToWasm0(disk, wasm.__wbindgen_malloc);
637
+ const len2 = WASM_VECTOR_LEN;
638
+ const ptr3 = passStringToWasm0(relay_url, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
639
+ const len3 = WASM_VECTOR_LEN;
640
+ const ret = wasm.userlandworkspace_boot(ptr0, len0, ptr1, len1, ptr2, len2, memory_bytes, ptr3, len3);
641
+ if (ret[2]) {
642
+ throw takeFromExternrefTable0(ret[1]);
643
+ }
644
+ return UserlandWorkspace.__wrap(ret[0]);
645
+ }
646
+ /**
647
+ * Feed **raw terminal input** (keystrokes, control bytes, escape sequences) to
648
+ * the guest — queued, then delivered on the next [`run`](Self::run), so the
649
+ * guest's own tty echoes and edits and Ctrl-C raises SIGINT (a real terminal,
650
+ * `CC-11`). The input is part of the machine's state captured by the snapshot.
651
+ * @param {Uint8Array} bytes
652
+ */
653
+ feed_input(bytes) {
654
+ const ptr0 = passArray8ToWasm0(bytes, wasm.__wbindgen_malloc);
655
+ const len0 = WASM_VECTOR_LEN;
656
+ wasm.userlandworkspace_feed_input(this.__wbg_ptr, ptr0, len0);
657
+ }
658
+ /**
659
+ * Whether the guest has powered off.
660
+ * @returns {boolean}
661
+ */
662
+ get halted() {
663
+ const ret = wasm.userlandworkspace_halted(this.__wbg_ptr);
664
+ return ret !== 0;
665
+ }
666
+ /**
667
+ * Resume an interactive userland from a linear-memory snapshot
668
+ * [`suspend`](Self::suspend) produced. `module` is the same CC-6 module (it is
669
+ * content — re-fetched and verified, not carried in the snapshot); the
670
+ * snapshot is the machine's memory at an `hg_event` boundary (`CC-30`/`CC-31`).
671
+ * @param {Uint8Array} module
672
+ * @param {Uint8Array} snapshot
673
+ * @returns {UserlandWorkspace}
674
+ */
675
+ static resume(module, snapshot) {
676
+ const ptr0 = passArray8ToWasm0(module, wasm.__wbindgen_malloc);
677
+ const len0 = WASM_VECTOR_LEN;
678
+ const ptr1 = passArray8ToWasm0(snapshot, wasm.__wbindgen_malloc);
679
+ const len1 = WASM_VECTOR_LEN;
680
+ const ret = wasm.userlandworkspace_resume(ptr0, len0, ptr1, len1);
681
+ if (ret[2]) {
682
+ throw takeFromExternrefTable0(ret[1]);
683
+ }
684
+ return UserlandWorkspace.__wrap(ret[0]);
685
+ }
686
+ /**
687
+ * Advance the guest by up to `budget` instructions — one cooperative slice.
688
+ * Delivers any queued [`feed_input`](Self::feed_input) to the guest first,
689
+ * then drains the console it produced. Returns `true` once it has halted. Call
690
+ * repeatedly from a UI loop, streaming [`terminal_delta`](Self::terminal_delta)
691
+ * between slices.
692
+ * @param {number} budget
693
+ * @returns {boolean}
694
+ */
695
+ run(budget) {
696
+ const ret = wasm.userlandworkspace_run(this.__wbg_ptr, budget);
697
+ return ret !== 0;
698
+ }
699
+ /**
700
+ * Suspend to a κ snapshot — the guest's linear memory at this `hg_event`
701
+ * boundary, the canonical content-addressed machine state (`CC-30`). The
702
+ * browser persists it (gzipped) to OPFS so the next launch resumes (`CC-31`).
703
+ * @returns {Uint8Array}
704
+ */
705
+ suspend() {
706
+ const ret = wasm.userlandworkspace_suspend(this.__wbg_ptr);
707
+ var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
708
+ wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
709
+ return v1;
710
+ }
711
+ /**
712
+ * The console bytes produced **since the last call** (an internal cursor) —
713
+ * the integrated terminal's render delta, O(new) not O(total) per frame.
714
+ * @returns {Uint8Array}
715
+ */
716
+ terminal_delta() {
717
+ const ret = wasm.userlandworkspace_terminal_delta(this.__wbg_ptr);
718
+ var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
719
+ wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
720
+ return v1;
721
+ }
722
+ }
723
+ if (Symbol.dispose) UserlandWorkspace.prototype[Symbol.dispose] = UserlandWorkspace.prototype.free;
724
+
725
+ /**
726
+ * A **workspace** over a running holospace, in the browser tab — the
727
+ * Codespaces/Gitpod experience (ADR-009; `CC-9` + `CC-11`). The operator
728
+ * launches a holospace whose code is the system emulator; it **boots a real
729
+ * operating system** (the [system emulator](holospaces::emulator) running in
730
+ * the browser's own wasm engine), and the [workspace
731
+ * projection](holospaces::projection) drives it: a live **terminal**
732
+ * (keystrokes published as canonical events that advance the holospace's κ
733
+ * snapshot) and an **editor** that reads and edits environment content *by κ*.
734
+ *
735
+ * The boot runs in instruction *chunks* ([`run`](Workspace::run)) so the UI
736
+ * stays responsive and can stream the console as the kernel boots — there is no
737
+ * server doing the work; the browser peer *is* the machine (Law L1).
738
+ */
739
+ export class Workspace {
740
+ static __wrap(ptr) {
741
+ const obj = Object.create(Workspace.prototype);
742
+ obj.__wbg_ptr = ptr;
743
+ WorkspaceFinalization.register(obj, obj.__wbg_ptr, obj);
744
+ return obj;
745
+ }
746
+ __destroy_into_raw() {
747
+ const ptr = this.__wbg_ptr;
748
+ this.__wbg_ptr = 0;
749
+ WorkspaceFinalization.unregister(this);
750
+ return ptr;
751
+ }
752
+ free() {
753
+ const ptr = this.__destroy_into_raw();
754
+ wasm.__wbg_workspace_free(ptr, 0);
755
+ }
756
+ /**
757
+ * Launch a workspace: place the OS `kernel` image and `dtb` in a machine
758
+ * with `ram_bytes` of RAM at `base`, the device tree at `dtb_addr`, and hand
759
+ * off as the SBI firmware. The machine is now booting (drive it with
760
+ * [`run`](Workspace::run)).
761
+ * @param {Uint8Array} kernel
762
+ * @param {Uint8Array} dtb
763
+ * @param {number} ram_bytes
764
+ * @param {number} base
765
+ * @param {number} dtb_addr
766
+ * @returns {Workspace}
767
+ */
768
+ static boot(kernel, dtb, ram_bytes, base, dtb_addr) {
769
+ const ptr0 = passArray8ToWasm0(kernel, wasm.__wbindgen_malloc);
770
+ const len0 = WASM_VECTOR_LEN;
771
+ const ptr1 = passArray8ToWasm0(dtb, wasm.__wbindgen_malloc);
772
+ const len1 = WASM_VECTOR_LEN;
773
+ const ret = wasm.workspace_boot(ptr0, len0, ptr1, len1, ram_bytes, base, dtb_addr);
774
+ if (ret[2]) {
775
+ throw takeFromExternrefTable0(ret[1]);
776
+ }
777
+ return Workspace.__wrap(ret[0]);
778
+ }
779
+ /**
780
+ * Boot a **devcontainer** workspace: the Boot Orchestrator
781
+ * ([`MachineSpec`](holospaces::machine::MachineSpec)) generates the device
782
+ * tree and boots `kernel` on a machine whose `virtio-blk` disk is the
783
+ * assembled `rootfs` (from [`DevcontainerImage::assemble`]). The guest
784
+ * kernel mounts the rootfs over `/dev/vda` and runs the devcontainer's real
785
+ * OS — entirely in the browser peer (`CC-14`). Drive it with
786
+ * [`run`](Workspace::run), exactly like [`boot`](Workspace::boot).
787
+ * @param {Uint8Array} kernel
788
+ * @param {Uint8Array} rootfs
789
+ * @returns {Workspace}
790
+ */
791
+ static boot_devcontainer(kernel, rootfs) {
792
+ const ptr0 = passArray8ToWasm0(kernel, wasm.__wbindgen_malloc);
793
+ const len0 = WASM_VECTOR_LEN;
794
+ const ptr1 = passArray8ToWasm0(rootfs, wasm.__wbindgen_malloc);
795
+ const len1 = WASM_VECTOR_LEN;
796
+ const ret = wasm.workspace_boot_devcontainer(ptr0, len0, ptr1, len1);
797
+ if (ret[2]) {
798
+ throw takeFromExternrefTable0(ret[1]);
799
+ }
800
+ return Workspace.__wrap(ret[0]);
801
+ }
802
+ /**
803
+ * Boot a devcontainer with the **in-process loopback bridge** enabled
804
+ * (ADR-020, `CC-33`): the guest's interface comes up with DHCP (so it has a
805
+ * real TCP stack), but instead of a WebSocket egress to the internet it gets
806
+ * a no-op egress and the *loopback ingress* — so the workbench, in this same
807
+ * process, can [`dial_guest`](Workspace::dial_guest) a server *inside* the
808
+ * devcontainer (a language server, a remote extension host) and exchange a
809
+ * byte stream with it, with no relay or socket. This is the transport the VS
810
+ * Code remote model runs over in the browser peer (ADR-015/ADR-020). Drive it
811
+ * with [`run`](Workspace::run), pumping the NAT so the bridge's bytes flow.
812
+ * @param {Uint8Array} kernel
813
+ * @param {Uint8Array} rootfs
814
+ * @returns {Workspace}
815
+ */
816
+ static boot_devcontainer_bridged(kernel, rootfs) {
817
+ const ptr0 = passArray8ToWasm0(kernel, wasm.__wbindgen_malloc);
818
+ const len0 = WASM_VECTOR_LEN;
819
+ const ptr1 = passArray8ToWasm0(rootfs, wasm.__wbindgen_malloc);
820
+ const len1 = WASM_VECTOR_LEN;
821
+ const ret = wasm.workspace_boot_devcontainer_bridged(ptr0, len0, ptr1, len1);
822
+ if (ret[2]) {
823
+ throw takeFromExternrefTable0(ret[1]);
824
+ }
825
+ return Workspace.__wrap(ret[0]);
826
+ }
827
+ /**
828
+ * Boot a **networked** devcontainer workspace (`CC-16`): like
829
+ * [`boot_devcontainer`](Workspace::boot_devcontainer), but the machine also
830
+ * has a `virtio-net` device whose userspace TCP/IP NAT tunnels the guest's
831
+ * TCP streams out over a WebSocket to the relay at `relay_url` (there is no
832
+ * raw NIC behind a tab; ADR-014). The guest brings its interface up with
833
+ * DHCP and can then reach the internet — `git clone`, `apt`, `npm` — from the
834
+ * browser peer. Drive it with [`run`](Workspace::run), yielding to the event
835
+ * loop between chunks so the WebSocket delivers host-side bytes.
836
+ * @param {Uint8Array} kernel
837
+ * @param {Uint8Array} rootfs
838
+ * @param {string} relay_url
839
+ * @returns {Workspace}
840
+ */
841
+ static boot_devcontainer_net(kernel, rootfs, relay_url) {
842
+ const ptr0 = passArray8ToWasm0(kernel, wasm.__wbindgen_malloc);
843
+ const len0 = WASM_VECTOR_LEN;
844
+ const ptr1 = passArray8ToWasm0(rootfs, wasm.__wbindgen_malloc);
845
+ const len1 = WASM_VECTOR_LEN;
846
+ const ptr2 = passStringToWasm0(relay_url, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
847
+ const len2 = WASM_VECTOR_LEN;
848
+ const ret = wasm.workspace_boot_devcontainer_net(ptr0, len0, ptr1, len1, ptr2, len2);
849
+ if (ret[2]) {
850
+ throw takeFromExternrefTable0(ret[1]);
851
+ }
852
+ return Workspace.__wrap(ret[0]);
853
+ }
854
+ /**
855
+ * Boot a devcontainer with the disk **streamed** off-heap into an OPFS-backed
856
+ * κ-store (the substrate-native, lean path — ADR/CC-9.d). `rootfs_handle` is a
857
+ * sync access handle on the provisioned rootfs file (read sector-by-sector,
858
+ * never materialized as a `Vec` in wasm RAM); `disk_handle` is the OPFS κ-store
859
+ * pack. Identical sectors dedup to one κ and all-zero sectors stay sparse, so a
860
+ * real image boots without holding it in RAM — "the KappaStore IS the memory,
861
+ * RAM is a cache" (Law L3). Non-networked (busybox/Debian dev box); drive with
862
+ * [`run`](Workspace::run).
863
+ * @param {Uint8Array} kernel
864
+ * @param {FileSystemSyncAccessHandle} rootfs_handle
865
+ * @param {FileSystemSyncAccessHandle} disk_handle
866
+ * @returns {Workspace}
867
+ */
868
+ static boot_devcontainer_opfs_streamed(kernel, rootfs_handle, disk_handle) {
869
+ const ptr0 = passArray8ToWasm0(kernel, wasm.__wbindgen_malloc);
870
+ const len0 = WASM_VECTOR_LEN;
871
+ const ret = wasm.workspace_boot_devcontainer_opfs_streamed(ptr0, len0, rootfs_handle, disk_handle);
872
+ if (ret[2]) {
873
+ throw takeFromExternrefTable0(ret[1]);
874
+ }
875
+ return Workspace.__wrap(ret[0]);
876
+ }
877
+ /**
878
+ * The κ of every operator event published on the terminal channel so far.
879
+ * @returns {any[]}
880
+ */
881
+ channel() {
882
+ const ret = wasm.workspace_channel(this.__wbg_ptr);
883
+ var v1 = getArrayJsValueFromWasm0(ret[0], ret[1]).slice();
884
+ wasm.__wbindgen_free(ret[0], ret[1] * 4, 4);
885
+ return v1;
886
+ }
887
+ /**
888
+ * Dial an in-process connection to a server *inside* the devcontainer,
889
+ * listening on `guest_port`, over the loopback substrate bridge (ADR-020,
890
+ * `CC-33`). Returns the connection id, or `None` if the machine was not booted
891
+ * with the bridge ([`boot_devcontainer_bridged`](Workspace::boot_devcontainer_bridged)).
892
+ * The workbench uses this to reach a language server / the remote extension
893
+ * host (ADR-015) without a relay or socket. Pump with [`run`](Workspace::run)
894
+ * so the NAT opens the connection and the byte stream flows.
895
+ * @param {number} guest_port
896
+ * @returns {number | undefined}
897
+ */
898
+ dial_guest(guest_port) {
899
+ const ret = wasm.workspace_dial_guest(this.__wbg_ptr, guest_port);
900
+ return ret === Number.MAX_SAFE_INTEGER ? undefined : ret;
901
+ }
902
+ /**
903
+ * Feed **raw terminal input** to the running holospace — the bytes an
904
+ * interactive terminal delivers for each keystroke, *unbuffered*: ordinary
905
+ * characters, control bytes (Ctrl-C = `0x03`, Ctrl-D = `0x04`), and escape
906
+ * sequences (arrows, Home/End). Unlike [`Workspace::type_line`] this does not
907
+ * line-buffer or block: the bytes go to the guest console and the caller's
908
+ * render loop ([`Workspace::run`] + [`Workspace::terminal_delta`]) advances
909
+ * the machine, so the guest's own tty echoes and edits the line and Ctrl-C
910
+ * raises SIGINT — a real terminal, not a line submitter. The input is part of
911
+ * the machine's canonical state (it is captured in the κ snapshot), so the
912
+ * session stays reproducible (Law L1).
913
+ * @param {Uint8Array} bytes
914
+ */
915
+ feed_input(bytes) {
916
+ const ptr0 = passArray8ToWasm0(bytes, wasm.__wbindgen_malloc);
917
+ const len0 = WASM_VECTOR_LEN;
918
+ wasm.workspace_feed_input(this.__wbg_ptr, ptr0, len0);
919
+ }
920
+ /**
921
+ * The **file tree**: the workspace's files as a JSON array of
922
+ * `{ path, kappa }` — each file's current content κ (its identity, Law L1).
923
+ * What the editor's explorer renders.
924
+ * @returns {string}
925
+ */
926
+ files() {
927
+ let deferred1_0;
928
+ let deferred1_1;
929
+ try {
930
+ const ret = wasm.workspace_files(this.__wbg_ptr);
931
+ deferred1_0 = ret[0];
932
+ deferred1_1 = ret[1];
933
+ return getStringFromWasm0(ret[0], ret[1]);
934
+ } finally {
935
+ wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
936
+ }
937
+ }
938
+ /**
939
+ * Close the host side of a loopback connection (`CC-33`).
940
+ * @param {number} id
941
+ */
942
+ guest_close(id) {
943
+ wasm.workspace_guest_close(this.__wbg_ptr, id);
944
+ }
945
+ /**
946
+ * Whether a loopback connection is still usable — the guest has not closed it,
947
+ * or has but unread bytes remain (`CC-33`).
948
+ * @param {number} id
949
+ * @returns {boolean}
950
+ */
951
+ guest_is_open(id) {
952
+ const ret = wasm.workspace_guest_is_open(this.__wbg_ptr, id);
953
+ return ret !== 0;
954
+ }
955
+ /**
956
+ * Drain the guest server's reply bytes on a loopback connection (empty until
957
+ * the machine is pumped enough for the stream to advance; `CC-33`).
958
+ * @param {number} id
959
+ * @returns {Uint8Array}
960
+ */
961
+ guest_recv(id) {
962
+ const ret = wasm.workspace_guest_recv(this.__wbg_ptr, id);
963
+ var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
964
+ wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
965
+ return v1;
966
+ }
967
+ /**
968
+ * Write bytes toward the guest server on a loopback connection (`CC-33`).
969
+ * @param {number} id
970
+ * @param {Uint8Array} data
971
+ */
972
+ guest_send(id, data) {
973
+ const ptr0 = passArray8ToWasm0(data, wasm.__wbindgen_malloc);
974
+ const len0 = WASM_VECTOR_LEN;
975
+ wasm.workspace_guest_send(this.__wbg_ptr, id, ptr0, len0);
976
+ }
977
+ /**
978
+ * Whether the machine has powered off.
979
+ * @returns {boolean}
980
+ */
981
+ get halted() {
982
+ const ret = wasm.workspace_halted(this.__wbg_ptr);
983
+ return ret !== 0;
984
+ }
985
+ /**
986
+ * The editor's read: fetch a file's content *by κ*, verifying it by
987
+ * re-derivation (Law L5). `undefined` if it is not in the workspace store.
988
+ * @param {string} kappa
989
+ * @returns {Uint8Array | undefined}
990
+ */
991
+ open_file(kappa) {
992
+ const ptr0 = passStringToWasm0(kappa, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
993
+ const len0 = WASM_VECTOR_LEN;
994
+ const ret = wasm.workspace_open_file(this.__wbg_ptr, ptr0, len0);
995
+ if (ret[3]) {
996
+ throw takeFromExternrefTable0(ret[2]);
997
+ }
998
+ let v2;
999
+ if (ret[0] !== 0) {
1000
+ v2 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
1001
+ wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
1002
+ }
1003
+ return v2;
1004
+ }
1005
+ /**
1006
+ * Open a file *by path*: the content at the file's current κ (the editor
1007
+ * reads the environment content by κ). `undefined` if the path is unknown.
1008
+ * @param {string} path
1009
+ * @returns {Uint8Array | undefined}
1010
+ */
1011
+ read_path(path) {
1012
+ const ptr0 = passStringToWasm0(path, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
1013
+ const len0 = WASM_VECTOR_LEN;
1014
+ const ret = wasm.workspace_read_path(this.__wbg_ptr, ptr0, len0);
1015
+ if (ret[3]) {
1016
+ throw takeFromExternrefTable0(ret[2]);
1017
+ }
1018
+ let v2;
1019
+ if (ret[0] !== 0) {
1020
+ v2 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
1021
+ wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
1022
+ }
1023
+ return v2;
1024
+ }
1025
+ /**
1026
+ * **Apply a configuration** the control plane published (ADR-018; `CC-28`):
1027
+ * decode the κ-addressed [`Configuration`] bytes (resolved + verified over
1028
+ * the substrate by the caller, Law L5) and enact its live directives on the
1029
+ * *running* machine — each `forwardPort` begins forwarding on the running
1030
+ * instance, without a reboot. Returns a JSON summary of what was applied
1031
+ * (`{ "forwarded": [{ "guest": 8080, "host": 8080 }], "lifecycle": "…",
1032
+ * "unsupported": [...] }`). The instance state changes from the panel's
1033
+ * configuration, carried as content over the substrate — no RPC.
1034
+ * @param {Uint8Array} config_bytes
1035
+ * @returns {string}
1036
+ */
1037
+ reconfigure(config_bytes) {
1038
+ let deferred3_0;
1039
+ let deferred3_1;
1040
+ try {
1041
+ const ptr0 = passArray8ToWasm0(config_bytes, wasm.__wbindgen_malloc);
1042
+ const len0 = WASM_VECTOR_LEN;
1043
+ const ret = wasm.workspace_reconfigure(this.__wbg_ptr, ptr0, len0);
1044
+ var ptr2 = ret[0];
1045
+ var len2 = ret[1];
1046
+ if (ret[3]) {
1047
+ ptr2 = 0; len2 = 0;
1048
+ throw takeFromExternrefTable0(ret[2]);
1049
+ }
1050
+ deferred3_0 = ptr2;
1051
+ deferred3_1 = len2;
1052
+ return getStringFromWasm0(ptr2, len2);
1053
+ } finally {
1054
+ wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
1055
+ }
1056
+ }
1057
+ /**
1058
+ * Resume a devcontainer workspace from a κ snapshot [`suspend`](Workspace::suspend)
1059
+ * produced, instead of cold-booting it (`CC-30`). The running OS, its disk,
1060
+ * and the workspace files come back exactly — so a second launch skips the
1061
+ * boot entirely and the editor's content is intact. The snapshot's integrity
1062
+ * is the caller's to check by re-derivation before trusting it across a
1063
+ * session boundary (Law L5; ADR-019) — OPFS is durable but untrusted storage.
1064
+ * @param {Uint8Array} snapshot
1065
+ * @returns {Workspace}
1066
+ */
1067
+ static resume_devcontainer(snapshot) {
1068
+ const ptr0 = passArray8ToWasm0(snapshot, wasm.__wbindgen_malloc);
1069
+ const len0 = WASM_VECTOR_LEN;
1070
+ const ret = wasm.workspace_resume_devcontainer(ptr0, len0);
1071
+ if (ret[2]) {
1072
+ throw takeFromExternrefTable0(ret[1]);
1073
+ }
1074
+ return Workspace.__wrap(ret[0]);
1075
+ }
1076
+ /**
1077
+ * Resume a **streamed** devcontainer (`CC-30`, the instant warm start for the
1078
+ * lean off-heap κ-disk): like [`resume_devcontainer`](Workspace::resume_devcontainer),
1079
+ * but re-attach the lazy κ-disk's rootfs `loader` from a re-opened OPFS handle —
1080
+ * so the resumed machine pages its *untouched* sectors from the immutable rootfs
1081
+ * again. The content-addressed snapshot carried only the touched disk delta +
1082
+ * the non-zero RAM pages, so resume is O(working set), not O(disk+RAM).
1083
+ * @param {Uint8Array} snapshot
1084
+ * @param {FileSystemSyncAccessHandle} rootfs_handle
1085
+ * @returns {Workspace}
1086
+ */
1087
+ static resume_devcontainer_streamed(snapshot, rootfs_handle) {
1088
+ const ptr0 = passArray8ToWasm0(snapshot, wasm.__wbindgen_malloc);
1089
+ const len0 = WASM_VECTOR_LEN;
1090
+ const ret = wasm.workspace_resume_devcontainer_streamed(ptr0, len0, rootfs_handle);
1091
+ if (ret[2]) {
1092
+ throw takeFromExternrefTable0(ret[1]);
1093
+ }
1094
+ return Workspace.__wrap(ret[0]);
1095
+ }
1096
+ /**
1097
+ * Advance the running holospace by `budget` instructions (one chunk of the
1098
+ * boot or of servicing input). Returns `true` once the machine has halted
1099
+ * (powered off). Call repeatedly from a UI loop, rendering
1100
+ * [`terminal`](Workspace::terminal) between chunks.
1101
+ * @param {number} budget
1102
+ * @returns {boolean}
1103
+ */
1104
+ run(budget) {
1105
+ const ret = wasm.workspace_run(this.__wbg_ptr, budget);
1106
+ return ret !== 0;
1107
+ }
1108
+ /**
1109
+ * The **editor** surface: save a file's content (the operator's edit). The
1110
+ * content is κ-addressed into the substrate (Law L2), so the returned κ is
1111
+ * the file's new identity — an edit advances it (Law L1). The canonical edit
1112
+ * event for `path` is published on the channel.
1113
+ * @param {string} path
1114
+ * @param {Uint8Array} content
1115
+ * @returns {string}
1116
+ */
1117
+ save_file(path, content) {
1118
+ let deferred4_0;
1119
+ let deferred4_1;
1120
+ try {
1121
+ const ptr0 = passStringToWasm0(path, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
1122
+ const len0 = WASM_VECTOR_LEN;
1123
+ const ptr1 = passArray8ToWasm0(content, wasm.__wbindgen_malloc);
1124
+ const len1 = WASM_VECTOR_LEN;
1125
+ const ret = wasm.workspace_save_file(this.__wbg_ptr, ptr0, len0, ptr1, len1);
1126
+ var ptr3 = ret[0];
1127
+ var len3 = ret[1];
1128
+ if (ret[3]) {
1129
+ ptr3 = 0; len3 = 0;
1130
+ throw takeFromExternrefTable0(ret[2]);
1131
+ }
1132
+ deferred4_0 = ptr3;
1133
+ deferred4_1 = len3;
1134
+ return getStringFromWasm0(ptr3, len3);
1135
+ } finally {
1136
+ wasm.__wbindgen_free(deferred4_0, deferred4_1, 1);
1137
+ }
1138
+ }
1139
+ /**
1140
+ * Whether the terminal has rendered `marker` yet (e.g. the ready banner).
1141
+ * @param {string} marker
1142
+ * @returns {boolean}
1143
+ */
1144
+ shows(marker) {
1145
+ const ptr0 = passStringToWasm0(marker, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
1146
+ const len0 = WASM_VECTOR_LEN;
1147
+ const ret = wasm.workspace_shows(this.__wbg_ptr, ptr0, len0);
1148
+ return ret !== 0;
1149
+ }
1150
+ /**
1151
+ * The running holospace's κ snapshot — its canonical state (Law L1/L3/L5).
1152
+ * @returns {string}
1153
+ */
1154
+ state_kappa() {
1155
+ let deferred1_0;
1156
+ let deferred1_1;
1157
+ try {
1158
+ const ret = wasm.workspace_state_kappa(this.__wbg_ptr);
1159
+ deferred1_0 = ret[0];
1160
+ deferred1_1 = ret[1];
1161
+ return getStringFromWasm0(ret[0], ret[1]);
1162
+ } finally {
1163
+ wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
1164
+ }
1165
+ }
1166
+ /**
1167
+ * Suspend the running machine to a κ snapshot — the canonical,
1168
+ * content-addressed bytes of the whole machine: CPU, RAM, the rootfs disk,
1169
+ * and the *workspace files* (virtio-9p). The browser persists these (gzipped)
1170
+ * to OPFS so the next launch *resumes* instead of cold-booting (`CC-30`).
1171
+ * Most of guest RAM is zero, so the gzipped snapshot is a small fraction of
1172
+ * the machine size.
1173
+ * @returns {Uint8Array}
1174
+ */
1175
+ suspend() {
1176
+ const ret = wasm.workspace_suspend(this.__wbg_ptr);
1177
+ var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
1178
+ wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
1179
+ return v1;
1180
+ }
1181
+ /**
1182
+ * The rendered terminal — the console the running holospace has produced.
1183
+ * @returns {string}
1184
+ */
1185
+ terminal() {
1186
+ let deferred1_0;
1187
+ let deferred1_1;
1188
+ try {
1189
+ const ret = wasm.workspace_terminal(this.__wbg_ptr);
1190
+ deferred1_0 = ret[0];
1191
+ deferred1_1 = ret[1];
1192
+ return getStringFromWasm0(ret[0], ret[1]);
1193
+ } finally {
1194
+ wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
1195
+ }
1196
+ }
1197
+ /**
1198
+ * The console bytes produced **since the last call** (an internal cursor),
1199
+ * for the integrated terminal's render loop. Returning only the delta avoids
1200
+ * re-reading and re-encoding the whole console each tick — output stays O(new
1201
+ * bytes), not O(total) per frame. Returns raw bytes (the terminal decodes
1202
+ * them); [`Workspace::terminal`] still returns the full buffer for tests.
1203
+ * @returns {Uint8Array}
1204
+ */
1205
+ terminal_delta() {
1206
+ const ret = wasm.workspace_terminal_delta(this.__wbg_ptr);
1207
+ var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
1208
+ wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
1209
+ return v1;
1210
+ }
1211
+ /**
1212
+ * Type a line into the terminal: publish it as a canonical event on the
1213
+ * holospace's channel (Law L1/L2), feed the keystrokes to the running
1214
+ * machine, and run until the response settles. The holospace's κ snapshot
1215
+ * advances. Returns the event's κ.
1216
+ * @param {string} line
1217
+ * @returns {string}
1218
+ */
1219
+ type_line(line) {
1220
+ let deferred2_0;
1221
+ let deferred2_1;
1222
+ try {
1223
+ const ptr0 = passStringToWasm0(line, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
1224
+ const len0 = WASM_VECTOR_LEN;
1225
+ const ret = wasm.workspace_type_line(this.__wbg_ptr, ptr0, len0);
1226
+ deferred2_0 = ret[0];
1227
+ deferred2_1 = ret[1];
1228
+ return getStringFromWasm0(ret[0], ret[1]);
1229
+ } finally {
1230
+ wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
1231
+ }
1232
+ }
1233
+ /**
1234
+ * Delete a file or folder from the shared workspace (the workbench
1235
+ * `FileSystemProvider.delete`) — the editor removing content the OS sees
1236
+ * over `virtio-9p`. `true` if it existed.
1237
+ * @param {string} name
1238
+ * @returns {boolean}
1239
+ */
1240
+ ws_delete(name) {
1241
+ const ptr0 = passStringToWasm0(name, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
1242
+ const len0 = WASM_VECTOR_LEN;
1243
+ const ret = wasm.workspace_ws_delete(this.__wbg_ptr, ptr0, len0);
1244
+ return ret !== 0;
1245
+ }
1246
+ /**
1247
+ * The shared workspace's directory listing — a JSON array of
1248
+ * `{ name, dir, size }` over the running holospace's `virtio-9p` workspace
1249
+ * (the workbench `FileSystemProvider.readDirectory`).
1250
+ * @returns {string}
1251
+ */
1252
+ ws_list() {
1253
+ let deferred1_0;
1254
+ let deferred1_1;
1255
+ try {
1256
+ const ret = wasm.workspace_ws_list(this.__wbg_ptr);
1257
+ deferred1_0 = ret[0];
1258
+ deferred1_1 = ret[1];
1259
+ return getStringFromWasm0(ret[0], ret[1]);
1260
+ } finally {
1261
+ wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
1262
+ }
1263
+ }
1264
+ /**
1265
+ * Create a folder in the shared workspace (the workbench
1266
+ * `FileSystemProvider.createDirectory`).
1267
+ * @param {string} name
1268
+ */
1269
+ ws_mkdir(name) {
1270
+ const ptr0 = passStringToWasm0(name, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
1271
+ const len0 = WASM_VECTOR_LEN;
1272
+ wasm.workspace_ws_mkdir(this.__wbg_ptr, ptr0, len0);
1273
+ }
1274
+ /**
1275
+ * Read a file from the shared workspace (the workbench
1276
+ * `FileSystemProvider.readFile`) — the same content the OS reads over
1277
+ * `virtio-9p`. `undefined` if absent.
1278
+ * @param {string} name
1279
+ * @returns {Uint8Array | undefined}
1280
+ */
1281
+ ws_read(name) {
1282
+ const ptr0 = passStringToWasm0(name, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
1283
+ const len0 = WASM_VECTOR_LEN;
1284
+ const ret = wasm.workspace_ws_read(this.__wbg_ptr, ptr0, len0);
1285
+ let v2;
1286
+ if (ret[0] !== 0) {
1287
+ v2 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
1288
+ wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
1289
+ }
1290
+ return v2;
1291
+ }
1292
+ /**
1293
+ * Rename a file or folder in the shared workspace (the workbench
1294
+ * `FileSystemProvider.rename`). `true` if the source existed.
1295
+ * @param {string} from
1296
+ * @param {string} to
1297
+ * @returns {boolean}
1298
+ */
1299
+ ws_rename(from, to) {
1300
+ const ptr0 = passStringToWasm0(from, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
1301
+ const len0 = WASM_VECTOR_LEN;
1302
+ const ptr1 = passStringToWasm0(to, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
1303
+ const len1 = WASM_VECTOR_LEN;
1304
+ const ret = wasm.workspace_ws_rename(this.__wbg_ptr, ptr0, len0, ptr1, len1);
1305
+ return ret !== 0;
1306
+ }
1307
+ /**
1308
+ * Write a file into the shared workspace (the workbench
1309
+ * `FileSystemProvider.writeFile`) — the editor saving the *same content* the
1310
+ * OS reads over `virtio-9p` (one content, Law L1). Returns the content's κ
1311
+ * (its identity, Law L1/L2).
1312
+ * @param {string} name
1313
+ * @param {Uint8Array} content
1314
+ * @returns {string}
1315
+ */
1316
+ ws_write(name, content) {
1317
+ let deferred3_0;
1318
+ let deferred3_1;
1319
+ try {
1320
+ const ptr0 = passStringToWasm0(name, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
1321
+ const len0 = WASM_VECTOR_LEN;
1322
+ const ptr1 = passArray8ToWasm0(content, wasm.__wbindgen_malloc);
1323
+ const len1 = WASM_VECTOR_LEN;
1324
+ const ret = wasm.workspace_ws_write(this.__wbg_ptr, ptr0, len0, ptr1, len1);
1325
+ deferred3_0 = ret[0];
1326
+ deferred3_1 = ret[1];
1327
+ return getStringFromWasm0(ret[0], ret[1]);
1328
+ } finally {
1329
+ wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
1330
+ }
1331
+ }
1332
+ }
1333
+ if (Symbol.dispose) Workspace.prototype[Symbol.dispose] = Workspace.prototype.free;
1334
+
1335
+ /**
1336
+ * The κ-label of bytes on the substrate's default σ-axis (blake3) — the same
1337
+ * content address every peer computes (Law L1).
1338
+ * @param {Uint8Array} bytes
1339
+ * @returns {string}
1340
+ */
1341
+ export function kappa(bytes) {
1342
+ let deferred2_0;
1343
+ let deferred2_1;
1344
+ try {
1345
+ const ptr0 = passArray8ToWasm0(bytes, wasm.__wbindgen_malloc);
1346
+ const len0 = WASM_VECTOR_LEN;
1347
+ const ret = wasm.kappa(ptr0, len0);
1348
+ deferred2_0 = ret[0];
1349
+ deferred2_1 = ret[1];
1350
+ return getStringFromWasm0(ret[0], ret[1]);
1351
+ } finally {
1352
+ wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
1353
+ }
1354
+ }
1355
+
1356
+ /**
1357
+ * Run a `.holo` compute artifact in the browser via the hologram executor
1358
+ * compiled to wasm — the *browser `.holo` engine* (arc42 chapter 11, RT2;
1359
+ * conformance `CC-2`). Returns the κ-label of the first output. Because the
1360
+ * executor is deterministic and content-addressed, this κ equals the one the
1361
+ * native executor produces for the same `.holo` (the browser engine equals the
1362
+ * native one).
1363
+ * @param {Uint8Array} archive
1364
+ * @returns {string}
1365
+ */
1366
+ export function run_holo(archive) {
1367
+ let deferred3_0;
1368
+ let deferred3_1;
1369
+ try {
1370
+ const ptr0 = passArray8ToWasm0(archive, wasm.__wbindgen_malloc);
1371
+ const len0 = WASM_VECTOR_LEN;
1372
+ const ret = wasm.run_holo(ptr0, len0);
1373
+ var ptr2 = ret[0];
1374
+ var len2 = ret[1];
1375
+ if (ret[3]) {
1376
+ ptr2 = 0; len2 = 0;
1377
+ throw takeFromExternrefTable0(ret[2]);
1378
+ }
1379
+ deferred3_0 = ptr2;
1380
+ deferred3_1 = len2;
1381
+ return getStringFromWasm0(ptr2, len2);
1382
+ } finally {
1383
+ wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
1384
+ }
1385
+ }
1386
+
1387
+ /**
1388
+ * Validate that `module` is a recompiled userland fit for the *execution
1389
+ * surface* (ADR-008; `CC-6`): specification-valid WebAssembly that imports only
1390
+ * the substrate host ABI and presents the container ABI. This is the κ-boundary
1391
+ * contract the browser peer enforces before a userland may be a holospace's
1392
+ * code — ambient (WASI-style) imports and a missing container ABI are refused.
1393
+ * @param {Uint8Array} module
1394
+ */
1395
+ export function validate_userland(module) {
1396
+ const ptr0 = passArray8ToWasm0(module, wasm.__wbindgen_malloc);
1397
+ const len0 = WASM_VECTOR_LEN;
1398
+ const ret = wasm.validate_userland(ptr0, len0);
1399
+ if (ret[1]) {
1400
+ throw takeFromExternrefTable0(ret[0]);
1401
+ }
1402
+ }
1403
+
1404
+ /**
1405
+ * Verify bytes against a claimed κ-label by re-derivation (Law L5). This is
1406
+ * what makes content fetched from an untrusted gateway safe.
1407
+ * @param {Uint8Array} bytes
1408
+ * @param {string} kappa
1409
+ * @returns {boolean}
1410
+ */
1411
+ export function verify_kappa(bytes, kappa) {
1412
+ const ptr0 = passArray8ToWasm0(bytes, wasm.__wbindgen_malloc);
1413
+ const len0 = WASM_VECTOR_LEN;
1414
+ const ptr1 = passStringToWasm0(kappa, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
1415
+ const len1 = WASM_VECTOR_LEN;
1416
+ const ret = wasm.verify_kappa(ptr0, len0, ptr1, len1);
1417
+ if (ret[2]) {
1418
+ throw takeFromExternrefTable0(ret[1]);
1419
+ }
1420
+ return ret[0] !== 0;
1421
+ }
1422
+ function __wbg_get_imports() {
1423
+ const import0 = {
1424
+ __proto__: null,
1425
+ __wbg___wbindgen_debug_string_0accd80f45e5faa2: function(arg0, arg1) {
1426
+ const ret = debugString(arg1);
1427
+ const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
1428
+ const len1 = WASM_VECTOR_LEN;
1429
+ getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
1430
+ getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
1431
+ },
1432
+ __wbg___wbindgen_throw_1506f2235d1bdba0: function(arg0, arg1) {
1433
+ throw new Error(getStringFromWasm0(arg0, arg1));
1434
+ },
1435
+ __wbg__wbg_cb_unref_61db23ac97f16c31: function(arg0) {
1436
+ arg0._wbg_cb_unref();
1437
+ },
1438
+ __wbg_data_bd354b70c783c66e: function(arg0) {
1439
+ const ret = arg0.data;
1440
+ return ret;
1441
+ },
1442
+ __wbg_getSize_5db8adeff7a4553d: function() { return handleError(function (arg0) {
1443
+ const ret = arg0.getSize();
1444
+ return ret;
1445
+ }, arguments); },
1446
+ __wbg_length_4a591ecaa01354d9: function(arg0) {
1447
+ const ret = arg0.length;
1448
+ return ret;
1449
+ },
1450
+ __wbg_new_578aeef4b6b94378: function(arg0) {
1451
+ const ret = new Uint8Array(arg0);
1452
+ return ret;
1453
+ },
1454
+ __wbg_new_ce1ab61c1c2b300d: function() {
1455
+ const ret = new Object();
1456
+ return ret;
1457
+ },
1458
+ __wbg_new_d7e476b433a26bea: function() { return handleError(function (arg0, arg1) {
1459
+ const ret = new WebSocket(getStringFromWasm0(arg0, arg1));
1460
+ return ret;
1461
+ }, arguments); },
1462
+ __wbg_prototypesetcall_3249fc62a0fafa30: function(arg0, arg1, arg2) {
1463
+ Uint8Array.prototype.set.call(getArrayU8FromWasm0(arg0, arg1), arg2);
1464
+ },
1465
+ __wbg_read_34a0958bcc273c55: function() { return handleError(function (arg0, arg1, arg2, arg3) {
1466
+ const ret = arg0.read(getArrayU8FromWasm0(arg1, arg2), arg3);
1467
+ return ret;
1468
+ }, arguments); },
1469
+ __wbg_send_4a773f523104d75e: function() { return handleError(function (arg0, arg1, arg2) {
1470
+ arg0.send(getArrayU8FromWasm0(arg1, arg2));
1471
+ }, arguments); },
1472
+ __wbg_set_at_5b6d1bf4f66bd626: function(arg0, arg1) {
1473
+ arg0.at = arg1;
1474
+ },
1475
+ __wbg_set_binaryType_41994c453b95bdd2: function(arg0, arg1) {
1476
+ arg0.binaryType = __wbindgen_enum_BinaryType[arg1];
1477
+ },
1478
+ __wbg_set_onclose_13787fb31ae8aefd: function(arg0, arg1) {
1479
+ arg0.onclose = arg1;
1480
+ },
1481
+ __wbg_set_onmessage_9c6b4cb14e244b7f: function(arg0, arg1) {
1482
+ arg0.onmessage = arg1;
1483
+ },
1484
+ __wbg_set_onopen_db452f4233e99d7d: function(arg0, arg1) {
1485
+ arg0.onopen = arg1;
1486
+ },
1487
+ __wbg_truncate_6b32a1fa508f5f05: function() { return handleError(function (arg0, arg1) {
1488
+ arg0.truncate(arg1);
1489
+ }, arguments); },
1490
+ __wbg_write_948386f6a5cf303f: function() { return handleError(function (arg0, arg1, arg2, arg3) {
1491
+ const ret = arg0.write(getArrayU8FromWasm0(arg1, arg2), arg3);
1492
+ return ret;
1493
+ }, arguments); },
1494
+ __wbindgen_cast_0000000000000001: function(arg0, arg1) {
1495
+ // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 11, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
1496
+ const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h26fdb82a8d18ed9f);
1497
+ return ret;
1498
+ },
1499
+ __wbindgen_cast_0000000000000002: function(arg0, arg1) {
1500
+ // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("MessageEvent")], shim_idx: 11, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
1501
+ const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h26fdb82a8d18ed9f_1);
1502
+ return ret;
1503
+ },
1504
+ __wbindgen_cast_0000000000000003: function(arg0, arg1) {
1505
+ // Cast intrinsic for `Ref(String) -> Externref`.
1506
+ const ret = getStringFromWasm0(arg0, arg1);
1507
+ return ret;
1508
+ },
1509
+ __wbindgen_init_externref_table: function() {
1510
+ const table = wasm.__wbindgen_externrefs;
1511
+ const offset = table.grow(4);
1512
+ table.set(0, undefined);
1513
+ table.set(offset + 0, undefined);
1514
+ table.set(offset + 1, null);
1515
+ table.set(offset + 2, true);
1516
+ table.set(offset + 3, false);
1517
+ },
1518
+ };
1519
+ return {
1520
+ __proto__: null,
1521
+ "./holospaces_web_bg.js": import0,
1522
+ };
1523
+ }
1524
+
1525
+ function wasm_bindgen__convert__closures_____invoke__h26fdb82a8d18ed9f(arg0, arg1, arg2) {
1526
+ wasm.wasm_bindgen__convert__closures_____invoke__h26fdb82a8d18ed9f(arg0, arg1, arg2);
1527
+ }
1528
+
1529
+ function wasm_bindgen__convert__closures_____invoke__h26fdb82a8d18ed9f_1(arg0, arg1, arg2) {
1530
+ wasm.wasm_bindgen__convert__closures_____invoke__h26fdb82a8d18ed9f_1(arg0, arg1, arg2);
1531
+ }
1532
+
1533
+
1534
+ const __wbindgen_enum_BinaryType = ["blob", "arraybuffer"];
1535
+ const Aarch64WorkspaceFinalization = (typeof FinalizationRegistry === 'undefined')
1536
+ ? { register: () => {}, unregister: () => {} }
1537
+ : new FinalizationRegistry(ptr => wasm.__wbg_aarch64workspace_free(ptr, 1));
1538
+ const ConsoleFinalization = (typeof FinalizationRegistry === 'undefined')
1539
+ ? { register: () => {}, unregister: () => {} }
1540
+ : new FinalizationRegistry(ptr => wasm.__wbg_console_free(ptr, 1));
1541
+ const DevcontainerImageFinalization = (typeof FinalizationRegistry === 'undefined')
1542
+ ? { register: () => {}, unregister: () => {} }
1543
+ : new FinalizationRegistry(ptr => wasm.__wbg_devcontainerimage_free(ptr, 1));
1544
+ const UserlandWorkspaceFinalization = (typeof FinalizationRegistry === 'undefined')
1545
+ ? { register: () => {}, unregister: () => {} }
1546
+ : new FinalizationRegistry(ptr => wasm.__wbg_userlandworkspace_free(ptr, 1));
1547
+ const WorkspaceFinalization = (typeof FinalizationRegistry === 'undefined')
1548
+ ? { register: () => {}, unregister: () => {} }
1549
+ : new FinalizationRegistry(ptr => wasm.__wbg_workspace_free(ptr, 1));
1550
+
1551
+ function addToExternrefTable0(obj) {
1552
+ const idx = wasm.__externref_table_alloc();
1553
+ wasm.__wbindgen_externrefs.set(idx, obj);
1554
+ return idx;
1555
+ }
1556
+
1557
+ const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined')
1558
+ ? { register: () => {}, unregister: () => {} }
1559
+ : new FinalizationRegistry(state => wasm.__wbindgen_destroy_closure(state.a, state.b));
1560
+
1561
+ function debugString(val) {
1562
+ // primitive types
1563
+ const type = typeof val;
1564
+ if (type == 'number' || type == 'boolean' || val == null) {
1565
+ return `${val}`;
1566
+ }
1567
+ if (type == 'string') {
1568
+ return `"${val}"`;
1569
+ }
1570
+ if (type == 'symbol') {
1571
+ const description = val.description;
1572
+ if (description == null) {
1573
+ return 'Symbol';
1574
+ } else {
1575
+ return `Symbol(${description})`;
1576
+ }
1577
+ }
1578
+ if (type == 'function') {
1579
+ const name = val.name;
1580
+ if (typeof name == 'string' && name.length > 0) {
1581
+ return `Function(${name})`;
1582
+ } else {
1583
+ return 'Function';
1584
+ }
1585
+ }
1586
+ // objects
1587
+ if (Array.isArray(val)) {
1588
+ const length = val.length;
1589
+ let debug = '[';
1590
+ if (length > 0) {
1591
+ debug += debugString(val[0]);
1592
+ }
1593
+ for(let i = 1; i < length; i++) {
1594
+ debug += ', ' + debugString(val[i]);
1595
+ }
1596
+ debug += ']';
1597
+ return debug;
1598
+ }
1599
+ // Test for built-in
1600
+ const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val));
1601
+ let className;
1602
+ if (builtInMatches && builtInMatches.length > 1) {
1603
+ className = builtInMatches[1];
1604
+ } else {
1605
+ // Failed to match the standard '[object ClassName]'
1606
+ return toString.call(val);
1607
+ }
1608
+ if (className == 'Object') {
1609
+ // we're a user defined class or Object
1610
+ // JSON.stringify avoids problems with cycles, and is generally much
1611
+ // easier than looping through ownProperties of `val`.
1612
+ try {
1613
+ return 'Object(' + JSON.stringify(val) + ')';
1614
+ } catch (_) {
1615
+ return 'Object';
1616
+ }
1617
+ }
1618
+ // errors
1619
+ if (val instanceof Error) {
1620
+ return `${val.name}: ${val.message}\n${val.stack}`;
1621
+ }
1622
+ // TODO we could test for more things here, like `Set`s and `Map`s.
1623
+ return className;
1624
+ }
1625
+
1626
+ function getArrayJsValueFromWasm0(ptr, len) {
1627
+ ptr = ptr >>> 0;
1628
+ const mem = getDataViewMemory0();
1629
+ const result = [];
1630
+ for (let i = ptr; i < ptr + 4 * len; i += 4) {
1631
+ result.push(wasm.__wbindgen_externrefs.get(mem.getUint32(i, true)));
1632
+ }
1633
+ wasm.__externref_drop_slice(ptr, len);
1634
+ return result;
1635
+ }
1636
+
1637
+ function getArrayU8FromWasm0(ptr, len) {
1638
+ ptr = ptr >>> 0;
1639
+ return getUint8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len);
1640
+ }
1641
+
1642
+ let cachedDataViewMemory0 = null;
1643
+ function getDataViewMemory0() {
1644
+ if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) {
1645
+ cachedDataViewMemory0 = new DataView(wasm.memory.buffer);
1646
+ }
1647
+ return cachedDataViewMemory0;
1648
+ }
1649
+
1650
+ function getStringFromWasm0(ptr, len) {
1651
+ return decodeText(ptr >>> 0, len);
1652
+ }
1653
+
1654
+ let cachedUint8ArrayMemory0 = null;
1655
+ function getUint8ArrayMemory0() {
1656
+ if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) {
1657
+ cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
1658
+ }
1659
+ return cachedUint8ArrayMemory0;
1660
+ }
1661
+
1662
+ function handleError(f, args) {
1663
+ try {
1664
+ return f.apply(this, args);
1665
+ } catch (e) {
1666
+ const idx = addToExternrefTable0(e);
1667
+ wasm.__wbindgen_exn_store(idx);
1668
+ }
1669
+ }
1670
+
1671
+ function makeMutClosure(arg0, arg1, f) {
1672
+ const state = { a: arg0, b: arg1, cnt: 1 };
1673
+ const real = (...args) => {
1674
+
1675
+ // First up with a closure we increment the internal reference
1676
+ // count. This ensures that the Rust closure environment won't
1677
+ // be deallocated while we're invoking it.
1678
+ state.cnt++;
1679
+ const a = state.a;
1680
+ state.a = 0;
1681
+ try {
1682
+ return f(a, state.b, ...args);
1683
+ } finally {
1684
+ state.a = a;
1685
+ real._wbg_cb_unref();
1686
+ }
1687
+ };
1688
+ real._wbg_cb_unref = () => {
1689
+ if (--state.cnt === 0) {
1690
+ wasm.__wbindgen_destroy_closure(state.a, state.b);
1691
+ state.a = 0;
1692
+ CLOSURE_DTORS.unregister(state);
1693
+ }
1694
+ };
1695
+ CLOSURE_DTORS.register(real, state, state);
1696
+ return real;
1697
+ }
1698
+
1699
+ function passArray8ToWasm0(arg, malloc) {
1700
+ const ptr = malloc(arg.length * 1, 1) >>> 0;
1701
+ getUint8ArrayMemory0().set(arg, ptr / 1);
1702
+ WASM_VECTOR_LEN = arg.length;
1703
+ return ptr;
1704
+ }
1705
+
1706
+ function passStringToWasm0(arg, malloc, realloc) {
1707
+ if (realloc === undefined) {
1708
+ const buf = cachedTextEncoder.encode(arg);
1709
+ const ptr = malloc(buf.length, 1) >>> 0;
1710
+ getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);
1711
+ WASM_VECTOR_LEN = buf.length;
1712
+ return ptr;
1713
+ }
1714
+
1715
+ let len = arg.length;
1716
+ let ptr = malloc(len, 1) >>> 0;
1717
+
1718
+ const mem = getUint8ArrayMemory0();
1719
+
1720
+ let offset = 0;
1721
+
1722
+ for (; offset < len; offset++) {
1723
+ const code = arg.charCodeAt(offset);
1724
+ if (code > 0x7F) break;
1725
+ mem[ptr + offset] = code;
1726
+ }
1727
+ if (offset !== len) {
1728
+ if (offset !== 0) {
1729
+ arg = arg.slice(offset);
1730
+ }
1731
+ ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
1732
+ const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
1733
+ const ret = cachedTextEncoder.encodeInto(arg, view);
1734
+
1735
+ offset += ret.written;
1736
+ ptr = realloc(ptr, len, offset, 1) >>> 0;
1737
+ }
1738
+
1739
+ WASM_VECTOR_LEN = offset;
1740
+ return ptr;
1741
+ }
1742
+
1743
+ function takeFromExternrefTable0(idx) {
1744
+ const value = wasm.__wbindgen_externrefs.get(idx);
1745
+ wasm.__externref_table_dealloc(idx);
1746
+ return value;
1747
+ }
1748
+
1749
+ let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
1750
+ cachedTextDecoder.decode();
1751
+ const MAX_SAFARI_DECODE_BYTES = 2146435072;
1752
+ let numBytesDecoded = 0;
1753
+ function decodeText(ptr, len) {
1754
+ numBytesDecoded += len;
1755
+ if (numBytesDecoded >= MAX_SAFARI_DECODE_BYTES) {
1756
+ cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
1757
+ cachedTextDecoder.decode();
1758
+ numBytesDecoded = len;
1759
+ }
1760
+ return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
1761
+ }
1762
+
1763
+ const cachedTextEncoder = new TextEncoder();
1764
+
1765
+ if (!('encodeInto' in cachedTextEncoder)) {
1766
+ cachedTextEncoder.encodeInto = function (arg, view) {
1767
+ const buf = cachedTextEncoder.encode(arg);
1768
+ view.set(buf);
1769
+ return {
1770
+ read: arg.length,
1771
+ written: buf.length
1772
+ };
1773
+ };
1774
+ }
1775
+
1776
+ let WASM_VECTOR_LEN = 0;
1777
+
1778
+ let wasmModule, wasmInstance, wasm;
1779
+ function __wbg_finalize_init(instance, module) {
1780
+ wasmInstance = instance;
1781
+ wasm = instance.exports;
1782
+ wasmModule = module;
1783
+ cachedDataViewMemory0 = null;
1784
+ cachedUint8ArrayMemory0 = null;
1785
+ wasm.__wbindgen_start();
1786
+ return wasm;
1787
+ }
1788
+
1789
+ async function __wbg_load(module, imports) {
1790
+ if (typeof Response === 'function' && module instanceof Response) {
1791
+ if (typeof WebAssembly.instantiateStreaming === 'function') {
1792
+ try {
1793
+ return await WebAssembly.instantiateStreaming(module, imports);
1794
+ } catch (e) {
1795
+ const validResponse = module.ok && expectedResponseType(module.type);
1796
+
1797
+ if (validResponse && module.headers.get('Content-Type') !== 'application/wasm') {
1798
+ console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
1799
+
1800
+ } else { throw e; }
1801
+ }
1802
+ }
1803
+
1804
+ const bytes = await module.arrayBuffer();
1805
+ return await WebAssembly.instantiate(bytes, imports);
1806
+ } else {
1807
+ const instance = await WebAssembly.instantiate(module, imports);
1808
+
1809
+ if (instance instanceof WebAssembly.Instance) {
1810
+ return { instance, module };
1811
+ } else {
1812
+ return instance;
1813
+ }
1814
+ }
1815
+
1816
+ function expectedResponseType(type) {
1817
+ switch (type) {
1818
+ case 'basic': case 'cors': case 'default': return true;
1819
+ }
1820
+ return false;
1821
+ }
1822
+ }
1823
+
1824
+ function initSync(module) {
1825
+ if (wasm !== undefined) return wasm;
1826
+
1827
+
1828
+ if (module !== undefined) {
1829
+ if (Object.getPrototypeOf(module) === Object.prototype) {
1830
+ ({module} = module)
1831
+ } else {
1832
+ console.warn('using deprecated parameters for `initSync()`; pass a single object instead')
1833
+ }
1834
+ }
1835
+
1836
+ const imports = __wbg_get_imports();
1837
+ if (!(module instanceof WebAssembly.Module)) {
1838
+ module = new WebAssembly.Module(module);
1839
+ }
1840
+ const instance = new WebAssembly.Instance(module, imports);
1841
+ return __wbg_finalize_init(instance, module);
1842
+ }
1843
+
1844
+ async function __wbg_init(module_or_path) {
1845
+ if (wasm !== undefined) return wasm;
1846
+
1847
+
1848
+ if (module_or_path !== undefined) {
1849
+ if (Object.getPrototypeOf(module_or_path) === Object.prototype) {
1850
+ ({module_or_path} = module_or_path)
1851
+ } else {
1852
+ console.warn('using deprecated parameters for the initialization function; pass a single object instead')
1853
+ }
1854
+ }
1855
+
1856
+ if (module_or_path === undefined) {
1857
+ module_or_path = new URL('holospaces_web_bg.wasm', import.meta.url);
1858
+ }
1859
+ const imports = __wbg_get_imports();
1860
+
1861
+ if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) {
1862
+ module_or_path = fetch(module_or_path);
1863
+ }
1864
+
1865
+ const { instance, module } = await __wbg_load(await module_or_path, imports);
1866
+
1867
+ return __wbg_finalize_init(instance, module);
1868
+ }
1869
+
1870
+ export { initSync, __wbg_init as default };
b/8d34a1e9297fbaf472886031a90d3a06dfd4aa71c0621e4325e0112ef96fb07d ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // messenger-skin.mjs — bakes the SLACK re-tone as the messenger default (aubergine chrome + a floating,
2
+ // inset content pane; see the data-skin="slack" block in messenger-skins) and applies the hi-DPI /
3
+ // high-FPS perf layer. No picker / no toggle. Additive + guarded. Escape hatch: set localStorage
4
+ // "holo.skin" to graphite | warm | contrast | light (or "" to remove) to override the default.
5
+
6
+ (function () {
7
+ if (!document.querySelector("link[data-holo-skins]")) {
8
+ const l = document.createElement("link"); l.rel = "stylesheet"; l.href = new URL("./messenger-skins.css?v=7d883ae6cf3e", import.meta.url); l.setAttribute("data-holo-skins", "1"); document.head.appendChild(l);
9
+ }
10
+ let skin = "slack";
11
+ try { const s = localStorage.getItem("holo.skin"); if (s) skin = s; } catch {}
12
+ if (skin) document.documentElement.setAttribute("data-skin", skin); else document.documentElement.removeAttribute("data-skin");
13
+ // remove any lingering picker + its persisted flag from earlier ?skins=1 sessions
14
+ try { localStorage.removeItem("holo.skins"); } catch {}
15
+ try { const p = document.querySelector(".holo-skin-pick"); if (p) p.remove(); } catch {}
16
+ })();
b/9d90b4ea8bccee8c239073c7ec7dd5158c6d11f1f668487570d17475d79e661e ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9d90b4ea8bccee8c239073c7ec7dd5158c6d11f1f668487570d17475d79e661e
3
+ size 6469596
b/a2276a0af47a3bc0c9c83d8d370bcc85c07e9c0f516b196ac158e575d5277b02 ADDED
@@ -0,0 +1 @@
 
 
1
+ import{a as h,b as x,c as C,d as v}from"./chunk-VJZ6ZLRJ.js";import"./chunk-SQE76S5B.js";function y(e){return["L1",e.type,e.kappa,e.hashlock||"",e.preimage||"",String(e.amount??""),e.asset||"",e.fiat||"",String(e.expires||""),e.by||"",String(e.at||"")].join("|")}async function E(e,{claimSecret:n,signer:r,at:a}){let i={type:"claim",kappa:e.kappa,hashlock:e.hashlock,preimage:n,amount:e.amount,asset:e.asset,fiat:e.fiat||null,expires:e.expires,by:r.pub,at:a};return i.sig=await r.sign(y(i)),i.tid=await h(y(i)),i}async function M(e,{signer:n,at:r}){let a={type:"refund",kappa:e.kappa,expires:e.expires,by:n.pub,at:r};return a.sig=await n.sign(y(a)),a.tid=await h(y(a)),a}async function _(e,n){return!e||!e.type||!e.kappa||!e.by||!e.sig||!await C(e.by,e.sig,y(e))?!1:e.type==="claim"?!(!e.preimage||await h(e.preimage)!==e.hashlock||e.expires&&e.at>e.expires):e.type==="refund"?!!(e.expires&&n>e.expires):!1}async function S(e,n=Date.now()){let r=[];for(let o of e)await _(o,n)&&r.push(o);let a=r.filter(o=>o.type==="claim");if(a.length){a.sort((t,s)=>t.at-s.at||(t.tid<s.tid?-1:t.tid>s.tid?1:0));let o=a[0];return{state:"claimed",winner:o.tid,by:o.by,amount:o.amount,asset:o.asset,fiat:o.fiat}}let i=r.filter(o=>o.type==="refund");return i.length?(i.sort((o,t)=>o.at-t.at||(o.tid<t.tid?-1:1)),{state:"refunded",winner:i[0].tid}):{state:"open"}}function P(e){return e&&e.transitions?Object.values(e.transitions):[]}async function R(e,n,r=Date.now()){e.transitions=e.transitions||{};for(let a of n||[])e.transitions[a.tid]||await _(a,r)&&(e.transitions[a.tid]=a);return e}var D=()=>({funded:!1,mode:"custodial",committed:null,transitions:{}}),b=new Map;function d(){return{get:e=>b.get(e)||null,set:(e,n)=>b.set(e,n),has:e=>b.has(e)}}function L(e="holo-escrow"){if(typeof localStorage>"u")return d();let n=r=>e+":"+r;return{get:r=>{try{let a=localStorage.getItem(n(r));return a?JSON.parse(a):null}catch{return null}},set:(r,a)=>{try{localStorage.setItem(n(r),JSON.stringify(a))}catch{}},has:r=>{try{return localStorage.getItem(n(r))!=null}catch{return!1}}}}async function q(e,{store:n=null,wallet:r=null,chain:a="base",nowMs:i=null}={}){let o=n||d();if(!e||!e.kappa)return{ok:!1,error:"no payment to fund"};if(!await v(e))return{ok:!1,error:"this payment isn't authentically signed, refusing to fund"};if(!e.hashlock)return{ok:!1,error:"this payment has no escrow lock"};let t=i||Date.now();if(e.expires&&t>e.expires)return{ok:!1,error:"already expired"};let s=o.get(e.kappa);if(s&&s.funded)return{ok:!0,kappa:e.kappa,mode:s.mode,live:s.mode==="live",tx:s.tx,already:!0};let f=r&&typeof r.htlcFund=="function"?"live":"custodial",u=null,m=null;if(f==="live"){let l;try{l=await r.htlcFund({chain:a,amount:e.amount,token:e.asset,hashlock:e.hashlock,timeout:e.expires})}catch(k){return{ok:!1,error:String(k&&k.message||k)}}u=l&&(l.tx||l.hash)||null,m=l&&l.swapId||e.hashlock}let c=s||D();return c.funded=!0,c.mode=f,c.tx=u,c.swapId=m,c.chain=f==="live"?a:null,c.amount=e.amount,c.asset=e.asset,c.fiat=e.fiat||null,c.expires=e.expires,o.set(e.kappa,c),{ok:!0,kappa:e.kappa,mode:f,live:f==="live",tx:u,swapId:m}}async function z(e,{claimSecret:n=null,claimerPub:r=null,claimerSigner:a=null,store:i=null,nowMs:o=null,wallet:t=null}={}){let s=i||d(),f=o||Date.now();if(!e||!e.kappa)return{ok:!1,error:"not a payment"};if(!await v(e))return{ok:!1,error:"this link isn't authentically signed"};if(e.expires&&f>e.expires)return{ok:!1,error:"this payment has expired"};let u=n||e.claimSecret;if(!u)return{ok:!1,error:"this link has no claim key"};if(await h(u)!==e.hashlock)return{ok:!1,error:"this claim key doesn't match the payment"};let m=a||await x(),c=await E(e,{claimSecret:u,signer:m,at:f}),l=s.get(e.kappa)||D();l.funded||(l.offDevice=!0);let k=l.committed&&l.committed!==c.tid;if(l.transitions[c.tid]=c,l.committed||(l.committed=c.tid),s.set(e.kappa,l),k)return{ok:!1,error:"already claimed"};let j=null,g=l.mode==="live";if(t&&typeof t.htlcClaim=="function")try{let p=g?{chain:l.chain}:typeof t.htlcSwapExists=="function"?await t.htlcSwapExists({hashlock:e.hashlock}):null;if(p){g=!0,l.mode="live",l.chain=l.chain||p.chain||null,s.set(e.kappa,l);let w=await t.htlcClaim({chain:l.chain||p.chain,hashlock:e.hashlock,preimage:u});j=w&&(w.tx||w.hash)||null}}catch(p){return{ok:!1,error:"on-chain claim failed: "+String(p&&p.message||p)}}return{ok:!0,amount:e.amount,asset:e.asset,fiat:e.fiat||null,mode:l.mode,live:g,offDevice:!!l.offDevice,tid:c.tid,tx:j,claimerPub:r||m.pub,custodialWallet:g?null:"\u03BA:"+e.kappa.slice(0,16)}}async function A(e,{store:n=null,signer:r=null,nowMs:a=null}={}){let i=n||d(),o=a||Date.now();if(!e||!e.kappa)return{ok:!1,error:"not a payment"};let t=i.get(e.kappa);if(!t)return{ok:!1,error:"no escrow to refund"};let s=await S(Object.values(t.transitions),o);if(s.state==="claimed")return{ok:!1,error:"already claimed, can't refund"};if(s.state==="refunded")return{ok:!1,error:"already refunded"};if(!(t.expires&&o>t.expires))return{ok:!1,error:"not yet expired, can't refund"};let f=r||await x(),u=await M(e,{signer:f,at:o});return t.transitions[u.tid]=u,i.set(e.kappa,t),{ok:!0,amount:t.amount,asset:t.asset,mode:t.mode}}async function T(e,{store:n=null,nowMs:r=null}={}){let a=n||d(),i=r||Date.now(),o=typeof e=="string"?e:e&&e.kappa;if(!o)return{state:"none"};let t=a.get(o);if(!t)return{state:"none"};let s=await S(Object.values(t.transitions),i);return s.state==="open"?{state:t.funded?"open":"none",committed:t.committed||null,canonicalWinner:null,reconciled:!1}:{state:s.state,canonicalWinner:s.winner,committed:t.committed||null,reconciled:!!(t.committed&&s.state==="claimed"&&t.committed!==s.winner),by:s.by,amount:s.amount,asset:s.asset}}function B(e,{store:n=null}={}){let r=n||d(),a=typeof e=="string"?e:e&&e.kappa,i=r.get(a);return i?P(i):[]}async function G(e,n,{store:r=null,nowMs:a=null}={}){let i=r||d(),o=a||Date.now(),t=typeof e=="string"?e:e&&e.kappa,s=i.get(t)||D();return await R(s,n||[],o),i.set(t,s),T(t,{store:i,nowMs:o})}export{z as claimEscrow,B as exportPeer,q as fund,L as localStore,G as mergePeer,A as refund,T as status};
b/a5b5d9c559a1d2812c33e9d7a4b1c24bb03059482503a23356e4c9c32d4d8b88 ADDED
The diff for this file is too large to render. See raw diff
 
b/b9f8a887de588c5193dc65ca9ce206a70c6df53595b848e0ce0299c2d88a5753 ADDED
The diff for this file is too large to render. See raw diff
 
b/c9a284fdd54f5472ac7d7781d9ac2d289f7dbacb9c710b0315bee75cd68ed99e ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // holo-release-verify.mjs — downstream acceptance of a release pointer (S2/S3 of the downstream-holospace
2
+ // initiative). Isomorphic: the boot page, the service worker, and Node witnesses all use THIS one module.
3
+ //
4
+ // The rule (fail-closed, Law L5 + SEC-1/4/6):
5
+ // a head is accepted only if (1) its id re-derives from its body, (2) it is SIGNED and the signature
6
+ // verifies over the id, (3) authorship is CONTINUOUS with the pinned head (same pub key), and
7
+ // (4) it does not travel backwards (seq strictly grows; the same head re-presented is a no-op).
8
+ // With no pin yet (first boot) the head is trusted-on-first-use and becomes the pin — every later
9
+ // update must chain from it. Rollback is NOT a violation: re-pinning a parent is an explicit local
10
+ // act (the store still holds it), never something a remote pointer can cause.
11
+
12
+ import { verifyEntry } from "/usr/lib/holo/holo-strand.mjs";
13
+
14
+ // verifyChain(entries) — Law L5 over the whole strand: every entry re-derives + verifies its
15
+ // signature, seq is dense from 0, every prev is exactly the prior id. Mirrors strand.verify()
16
+ // for a plain array (the pointer file world has no backend).
17
+ export async function verifyChain(entries) {
18
+ let prev = null;
19
+ for (let i = 0; i < (entries || []).length; i++) {
20
+ const rec = entries[i];
21
+ const v = await verifyEntry(rec);
22
+ if (!v.ok) return { ok: false, brokeAt: i, why: v.why };
23
+ if (rec["holstr:seq"] !== i) return { ok: false, brokeAt: i, why: "seq-out-of-order" };
24
+ if (rec["holstr:prev"] !== prev) return { ok: false, brokeAt: i, why: "prev-link-broken" };
25
+ prev = rec.id;
26
+ }
27
+ return { ok: true, length: (entries || []).length, head: prev };
28
+ }
29
+
30
+ // acceptHead(head, pinned) → { ok, pin?, why?, unchanged? }
31
+ // head : the fetched release.json record (untrusted bytes from an untrusted origin)
32
+ // pinned : the locally durable { id, seq, pub } from the last accepted release (null on first boot)
33
+ export async function acceptHead(head, pinned = null) {
34
+ if (!head || typeof head !== "object") return { ok: false, why: "no-head" };
35
+ const v = await verifyEntry(head);
36
+ if (!v.ok) return { ok: false, why: v.why }; // tampered or forged (L5/SEC-1)
37
+ if (!v.signed) return { ok: false, why: "unsigned-release" }; // no signature → no update
38
+ const pub = head["holstr:pub"], seq = head["holstr:seq"];
39
+ if (pinned) {
40
+ if (pub !== pinned.pub) return { ok: false, why: "key-discontinuity" }; // a different author cannot replace the strand (SEC-4)
41
+ if (head.id === pinned.id) return { ok: true, unchanged: true, pin: pinned };
42
+ if (!(seq > pinned.seq)) return { ok: false, why: "replay-stale-head" }; // an old release re-presented as new is refused
43
+ }
44
+ return { ok: true, pin: { id: head.id, seq, pub } };
45
+ }
46
+
47
+ export default { verifyChain, acceptHead };
b/d1fdc38c3e96ab2c5c176a4e016516b27197ec0f8dde9467532e75703cacaa7e ADDED
The diff for this file is too large to render. See raw diff
 
b/d2e8a9ef211a9cf3d441297f50e5ef6949d8a7c43ebbe56705e75eb32e969937 ADDED
@@ -0,0 +1 @@
 
 
1
+ import{a as h,b as x,c as j,d as v}from"./chunk-VJZ6ZLRJ.js";import"./chunk-SQE76S5B.js";function y(e){return["L1",e.type,e.kappa,e.hashlock||"",e.preimage||"",String(e.amount??""),e.asset||"",e.fiat||"",String(e.expires||""),e.by||"",String(e.at||"")].join("|")}async function C(e,{claimSecret:i,signer:r,at:a}){let l={type:"claim",kappa:e.kappa,hashlock:e.hashlock,preimage:i,amount:e.amount,asset:e.asset,fiat:e.fiat||null,expires:e.expires,by:r.pub,at:a};return l.sig=await r.sign(y(l)),l.tid=await h(y(l)),l}async function E(e,{signer:i,at:r}){let a={type:"refund",kappa:e.kappa,expires:e.expires,by:i.pub,at:r};return a.sig=await i.sign(y(a)),a.tid=await h(y(a)),a}async function M(e,i){return!e||!e.type||!e.kappa||!e.by||!e.sig||!await j(e.by,e.sig,y(e))?!1:e.type==="claim"?!(!e.preimage||await h(e.preimage)!==e.hashlock||e.expires&&e.at>e.expires):e.type==="refund"?!!(e.expires&&i>e.expires):!1}async function S(e,i=Date.now()){let r=[];for(let n of e)await M(n,i)&&r.push(n);let a=r.filter(n=>n.type==="claim");if(a.length){a.sort((s,t)=>s.at-t.at||(s.tid<t.tid?-1:s.tid>t.tid?1:0));let n=a[0];return{state:"claimed",winner:n.tid,by:n.by,amount:n.amount,asset:n.asset,fiat:n.fiat}}let l=r.filter(n=>n.type==="refund");return l.length?(l.sort((n,s)=>n.at-s.at||(n.tid<s.tid?-1:1)),{state:"refunded",winner:l[0].tid}):{state:"open"}}function _(e){return e&&e.transitions?Object.values(e.transitions):[]}async function P(e,i,r=Date.now()){e.transitions=e.transitions||{};for(let a of i||[])e.transitions[a.tid]||await M(a,r)&&(e.transitions[a.tid]=a);return e}var D=()=>({funded:!1,mode:"custodial",committed:null,transitions:{}}),b=new Map;function m(){return{get:e=>b.get(e)||null,set:(e,i)=>b.set(e,i),has:e=>b.has(e)}}function L(e="holo-escrow"){if(typeof localStorage>"u")return m();let i=r=>e+":"+r;return{get:r=>{try{let a=localStorage.getItem(i(r));return a?JSON.parse(a):null}catch{return null}},set:(r,a)=>{try{localStorage.setItem(i(r),JSON.stringify(a))}catch{}},has:r=>{try{return localStorage.getItem(i(r))!=null}catch{return!1}}}}async function q(e,{store:i=null,wallet:r=null,chain:a="base",nowMs:l=null}={}){let n=i||m();if(!e||!e.kappa)return{ok:!1,error:"no payment to fund"};if(!await v(e))return{ok:!1,error:"this payment isn't authentically signed, refusing to fund"};if(!e.hashlock)return{ok:!1,error:"this payment has no escrow lock"};let s=l||Date.now();if(e.expires&&s>e.expires)return{ok:!1,error:"already expired"};let t=n.get(e.kappa);if(t&&t.funded)return{ok:!0,kappa:e.kappa,mode:t.mode,live:t.mode==="live",tx:t.tx,already:!0};let u=r&&typeof r.htlcFund=="function"?"live":"custodial",f=null,p=null;if(u==="live"){let o;try{o=await r.htlcFund({chain:a,amount:e.amount,token:e.asset,hashlock:e.hashlock,timeout:e.expires})}catch(k){return{ok:!1,error:String(k&&k.message||k)}}f=o&&(o.tx||o.hash)||null,p=o&&o.swapId||e.hashlock}let c=t||D();return c.funded=!0,c.mode=u,c.tx=f,c.swapId=p,c.chain=u==="live"?a:null,c.amount=e.amount,c.asset=e.asset,c.fiat=e.fiat||null,c.expires=e.expires,n.set(e.kappa,c),{ok:!0,kappa:e.kappa,mode:u,live:u==="live",tx:f,swapId:p}}async function z(e,{claimSecret:i=null,claimerPub:r=null,claimerSigner:a=null,store:l=null,nowMs:n=null,wallet:s=null}={}){let t=l||m(),u=n||Date.now();if(!e||!e.kappa)return{ok:!1,error:"not a payment"};if(!await v(e))return{ok:!1,error:"this link isn't authentically signed"};if(e.expires&&u>e.expires)return{ok:!1,error:"this payment has expired"};let f=i||e.claimSecret;if(!f)return{ok:!1,error:"this link has no claim key"};if(await h(f)!==e.hashlock)return{ok:!1,error:"this claim key doesn't match the payment"};let p=a||await x(),c=await C(e,{claimSecret:f,signer:p,at:u}),o=t.get(e.kappa)||D();o.funded||(o.offDevice=!0);let k=o.committed&&o.committed!==c.tid;if(o.transitions[c.tid]=c,o.committed||(o.committed=c.tid),t.set(e.kappa,o),k)return{ok:!1,error:"already claimed"};let R=null,g=o.mode==="live";if(s&&typeof s.htlcClaim=="function")try{let d=g?{chain:o.chain}:typeof s.htlcSwapExists=="function"?await s.htlcSwapExists({hashlock:e.hashlock}):null;if(d){g=!0,o.mode="live",o.chain=o.chain||d.chain||null,t.set(e.kappa,o);let w=await s.htlcClaim({chain:o.chain||d.chain,hashlock:e.hashlock,preimage:f});R=w&&(w.tx||w.hash)||null}}catch(d){return{ok:!1,error:"on-chain claim failed: "+String(d&&d.message||d)}}return{ok:!0,amount:e.amount,asset:e.asset,fiat:e.fiat||null,mode:o.mode,live:g,offDevice:!!o.offDevice,tid:c.tid,tx:R,claimerPub:r||p.pub,custodialWallet:g?null:"\u03BA:"+e.kappa.slice(0,16)}}async function A(e,{store:i=null,signer:r=null,nowMs:a=null,wallet:l=null}={}){let n=i||m(),s=a||Date.now();if(!e||!e.kappa)return{ok:!1,error:"not a payment"};let t=n.get(e.kappa);if(!t)return{ok:!1,error:"no escrow to refund"};let u=await S(Object.values(t.transitions),s);if(u.state==="claimed")return{ok:!1,error:"already claimed, can't refund"};if(u.state==="refunded")return{ok:!1,error:"already refunded"};if(!(t.expires&&s>t.expires))return{ok:!1,error:"not yet expired, can't refund"};let f=r||await x(),p=await E(e,{signer:f,at:s});t.transitions[p.tid]=p,n.set(e.kappa,t);let c=null;if(t.mode==="live"&&l&&typeof l.htlcRefund=="function")try{let o=await l.htlcRefund({chain:t.chain,hashlock:e.hashlock});c=o&&(o.tx||o.hash)||null}catch(o){return{ok:!1,error:"on-chain refund failed: "+String(o&&o.message||o)}}return{ok:!0,amount:t.amount,asset:t.asset,mode:t.mode,live:t.mode==="live",tx:c}}async function T(e,{store:i=null,nowMs:r=null}={}){let a=i||m(),l=r||Date.now(),n=typeof e=="string"?e:e&&e.kappa;if(!n)return{state:"none"};let s=a.get(n);if(!s)return{state:"none"};let t=await S(Object.values(s.transitions),l);return t.state==="open"?{state:s.funded?"open":"none",committed:s.committed||null,canonicalWinner:null,reconciled:!1}:{state:t.state,canonicalWinner:t.winner,committed:s.committed||null,reconciled:!!(s.committed&&t.state==="claimed"&&s.committed!==t.winner),by:t.by,amount:t.amount,asset:t.asset}}function B(e,{store:i=null}={}){let r=i||m(),a=typeof e=="string"?e:e&&e.kappa,l=r.get(a);return l?_(l):[]}async function G(e,i,{store:r=null,nowMs:a=null}={}){let l=r||m(),n=a||Date.now(),s=typeof e=="string"?e:e&&e.kappa,t=l.get(s)||D();return await P(t,i||[],n),l.set(s,t),T(s,{store:l,nowMs:n})}export{z as claimEscrow,B as exportPeer,q as fund,L as localStore,G as mergePeer,A as refund,T as status};
b/e0b2a13d7c3890a4ee3fc6120d2b42a3901ba068897aca8ce2080320f63446ee ADDED
The diff for this file is too large to render. See raw diff
 
b/e7b914a4e11aedb2e0239e7a84502a8241f2fff157d980741728810a1559ff41 ADDED
@@ -0,0 +1 @@
 
 
1
+ function l(e){let t=e.replace(/-/g,"+").replace(/_/g,"/"),n=decodeURIComponent(escape(atob(t)));return JSON.parse(n)}var i=["v","kind","amount","asset","fiat","to","toName","from","fromName","memo","created","expires","claimSecret","pub","sig"];function u(e){let t=l(e);if(!Array.isArray(t))return t;let n={};return i.forEach((a,o)=>{t[o]!=null&&(n[a]=t[o])}),n.memo==null&&(n.memo=""),n}async function p(e){let t=new TextEncoder().encode(e),n=await crypto.subtle.digest("SHA-256",t);return[...new Uint8Array(n)].map(a=>a.toString(16).padStart(2,"0")).join("")}var w=p;function r(e){let t="";for(let n of e)t+=String.fromCharCode(n);return btoa(t).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function s(e){let t=atob(e.replace(/-/g,"+").replace(/_/g,"/")),n=new Uint8Array(t.length);for(let a=0;a<t.length;a++)n[a]=t.charCodeAt(a);return n}async function y(){let e=await crypto.subtle.generateKey({name:"ECDSA",namedCurve:"P-256"},!0,["sign","verify"]),t=new Uint8Array(await crypto.subtle.exportKey("raw",e.publicKey));return{pub:r(t),sign:async n=>r(new Uint8Array(await crypto.subtle.sign({name:"ECDSA",hash:"SHA-256"},e.privateKey,new TextEncoder().encode(n))))}}async function c(e,t,n){try{let a=await crypto.subtle.importKey("raw",s(e),{name:"ECDSA",namedCurve:"P-256"},!1,["verify"]);return await crypto.subtle.verify({name:"ECDSA",hash:"SHA-256"},a,s(t),new TextEncoder().encode(n))}catch{return!1}}var h=c;async function b(e){return!e||!e.pub||!e.sig?!1:c(e.pub,e.sig,d(e))}function d(e){let t=[e.v,e.kind,e.amount,e.asset,e.fiat||"",e.to||"",e.toName||"",e.from||"",e.fromName||"",e.memo||"",e.created||"",e.expires||""];return(e.hashlock||e.pub)&&t.push(e.hashlock||"",e.pub||""),t.join("|")}var f={USD:"$",EUR:"\u20AC",GBP:"\xA3"};function g(e){let t=Number(e.amount);return e.fiat?`${f[e.fiat]||""}${t.toFixed(2)}`:`${t%1===0?t:t.toFixed(4).replace(/0+$/,"")} ${e.asset}`}var m=/(https?:\/\/\S*pay-claim\.html#([A-Za-z0-9_-]+))|(holo:\/\/pay\/\S*#([A-Za-z0-9_-]+))/;function S(e){let t=String(e||"").match(m);if(!t)return null;let n=t[2]||t[4],a=t[0];try{let o=u(n);if(o&&o.v===1&&Number(o.amount)>0)return{intent:o,url:a,payload:n}}catch{}return null}export{w as a,y as b,h as c,b as d,g as e,S as f};
b/f011367faa1643ec7403b71b5e585c7a85ede5bd4a08e8ad5e95c31bcdf1161a ADDED
@@ -0,0 +1 @@
 
 
1
+ function l(e){let t=e.replace(/-/g,"+").replace(/_/g,"/"),n=decodeURIComponent(escape(atob(t)));return JSON.parse(n)}var i=["v","kind","amount","asset","fiat","to","toName","from","fromName","memo","created","expires","claimSecret","pub","sig"];function u(e){let t=l(e);if(!Array.isArray(t))return t;let n={};return i.forEach((a,o)=>{t[o]!=null&&(n[a]=t[o])}),n.memo==null&&(n.memo=""),n}async function d(e){let t=new TextEncoder().encode(e),n=await crypto.subtle.digest("SHA-256",t);return[...new Uint8Array(n)].map(a=>a.toString(16).padStart(2,"0")).join("")}var w=d;function r(e){let t="";for(let n of e)t+=String.fromCharCode(n);return btoa(t).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function c(e){let t=atob(e.replace(/-/g,"+").replace(/_/g,"/")),n=new Uint8Array(t.length);for(let a=0;a<t.length;a++)n[a]=t.charCodeAt(a);return n}async function m(){let e=await crypto.subtle.generateKey({name:"ECDSA",namedCurve:"P-256"},!0,["sign","verify"]),t=new Uint8Array(await crypto.subtle.exportKey("raw",e.publicKey));return{pub:r(t),sign:async n=>r(new Uint8Array(await crypto.subtle.sign({name:"ECDSA",hash:"SHA-256"},e.privateKey,new TextEncoder().encode(n))))}}async function s(e,t,n){try{let a=await crypto.subtle.importKey("raw",c(e),{name:"ECDSA",namedCurve:"P-256"},!1,["verify"]);return await crypto.subtle.verify({name:"ECDSA",hash:"SHA-256"},a,c(t),new TextEncoder().encode(n))}catch{return!1}}var y=s;async function b(e){return!e||!e.pub||!e.sig?!1:s(e.pub,e.sig,p(e))}function p(e){let t=[e.v,e.kind,e.amount,e.asset,e.fiat||"",e.to||"",e.toName||"",e.from||"",e.fromName||"",e.memo||"",e.created||"",e.expires||""];return(e.hashlock||e.pub)&&t.push(e.hashlock||"",e.pub||""),t.join("|")}var f={USD:"$",EUR:"\u20AC",GBP:"\xA3"};function k(e){let t=Number(e.amount);return e.fiat?`${f[e.fiat]||""}${t.toFixed(2)}`:`${t%1===0?t:t.toFixed(4).replace(/0+$/,"")} ${e.asset}`}var h=/(https?:\/\/\S*pay-claim\.html#([A-Za-z0-9_-]+))|(holo:\/\/pay\/\S*#([A-Za-z0-9_-]+))/;function g(e){let t=String(e||"").match(h);if(!t)return null;let n=t[2]||t[4],a=t[0];try{let o=u(n);if(o&&o.v===1&&Number(o.amount)>0)return{intent:o,url:a,payload:n}}catch{}return null}export{w as a,m as b,y as c,b as d,k as e,g as f};
b/f4b80dc5a1ba9ad776b7b3793b5b9af2ed35592e45ce447eab9d571d5bc47693 ADDED
@@ -0,0 +1 @@
 
 
1
+ import{a as d,b as y,c as w,d as k}from"./chunk-VJZ6ZLRJ.js";import"./chunk-SQE76S5B.js";function m(e){return["L1",e.type,e.kappa,e.hashlock||"",e.preimage||"",String(e.amount??""),e.asset||"",e.fiat||"",String(e.expires||""),e.by||"",String(e.at||"")].join("|")}async function v(e,{claimSecret:s,signer:a,at:t}){let i={type:"claim",kappa:e.kappa,hashlock:e.hashlock,preimage:s,amount:e.amount,asset:e.asset,fiat:e.fiat||null,expires:e.expires,by:a.pub,at:t};return i.sig=await a.sign(m(i)),i.tid=await d(m(i)),i}async function S(e,{signer:s,at:a}){let t={type:"refund",kappa:e.kappa,expires:e.expires,by:s.pub,at:a};return t.sig=await s.sign(m(t)),t.tid=await d(m(t)),t}async function b(e,s){return!e||!e.type||!e.kappa||!e.by||!e.sig||!await w(e.by,e.sig,m(e))?!1:e.type==="claim"?!(!e.preimage||await d(e.preimage)!==e.hashlock||e.expires&&e.at>e.expires):e.type==="refund"?!!(e.expires&&s>e.expires):!1}async function g(e,s=Date.now()){let a=[];for(let o of e)await b(o,s)&&a.push(o);let t=a.filter(o=>o.type==="claim");if(t.length){t.sort((r,n)=>r.at-n.at||(r.tid<n.tid?-1:r.tid>n.tid?1:0));let o=t[0];return{state:"claimed",winner:o.tid,by:o.by,amount:o.amount,asset:o.asset,fiat:o.fiat}}let i=a.filter(o=>o.type==="refund");return i.length?(i.sort((o,r)=>o.at-r.at||(o.tid<r.tid?-1:1)),{state:"refunded",winner:i[0].tid}):{state:"open"}}function D(e){return e&&e.transitions?Object.values(e.transitions):[]}async function j(e,s,a=Date.now()){e.transitions=e.transitions||{};for(let t of s||[])e.transitions[t.tid]||await b(t,a)&&(e.transitions[t.tid]=t);return e}var x=()=>({funded:!1,mode:"custodial",committed:null,transitions:{}}),h=new Map;function p(){return{get:e=>h.get(e)||null,set:(e,s)=>h.set(e,s),has:e=>h.has(e)}}function C(e="holo-escrow"){if(typeof localStorage>"u")return p();let s=a=>e+":"+a;return{get:a=>{try{let t=localStorage.getItem(s(a));return t?JSON.parse(t):null}catch{return null}},set:(a,t)=>{try{localStorage.setItem(s(a),JSON.stringify(t))}catch{}},has:a=>{try{return localStorage.getItem(s(a))!=null}catch{return!1}}}}async function E(e,{store:s=null,wallet:a=null,chain:t="base",nowMs:i=null}={}){let o=s||p();if(!e||!e.kappa)return{ok:!1,error:"no payment to fund"};if(!await k(e))return{ok:!1,error:"this payment isn't authentically signed, refusing to fund"};if(!e.hashlock)return{ok:!1,error:"this payment has no escrow lock"};let r=i||Date.now();if(e.expires&&r>e.expires)return{ok:!1,error:"already expired"};let n=o.get(e.kappa);if(n&&n.funded)return{ok:!0,kappa:e.kappa,mode:n.mode,live:n.mode==="live",tx:n.tx,already:!0};let c=a&&typeof a.htlcFund=="function"?"live":"custodial",f=null;if(c==="live")try{f=await a.htlcFund({chain:t,amount:e.amount,token:e.asset,hashlock:e.hashlock,timeout:e.expires})}catch(l){return{ok:!1,error:String(l&&l.message||l)}}let u=n||x();return u.funded=!0,u.mode=c,u.tx=f,u.amount=e.amount,u.asset=e.asset,u.fiat=e.fiat||null,u.expires=e.expires,o.set(e.kappa,u),{ok:!0,kappa:e.kappa,mode:c,live:c==="live",tx:f}}async function F(e,{claimSecret:s=null,claimerPub:a=null,claimerSigner:t=null,store:i=null,nowMs:o=null}={}){let r=i||p(),n=o||Date.now();if(!e||!e.kappa)return{ok:!1,error:"not a payment"};if(!await k(e))return{ok:!1,error:"this link isn't authentically signed"};if(e.expires&&n>e.expires)return{ok:!1,error:"this payment has expired"};let c=s||e.claimSecret;if(!c)return{ok:!1,error:"this link has no claim key"};if(await d(c)!==e.hashlock)return{ok:!1,error:"this claim key doesn't match the payment"};let f=t||await y(),u=await v(e,{claimSecret:c,signer:f,at:n}),l=r.get(e.kappa)||x();l.funded||(l.offDevice=!0);let M=l.committed&&l.committed!==u.tid;return l.transitions[u.tid]=u,l.committed||(l.committed=u.tid),r.set(e.kappa,l),M?{ok:!1,error:"already claimed"}:{ok:!0,amount:e.amount,asset:e.asset,fiat:e.fiat||null,mode:l.mode,live:l.mode==="live",offDevice:!!l.offDevice,tid:u.tid,claimerPub:a||f.pub,custodialWallet:"\u03BA:"+e.kappa.slice(0,16)}}async function J(e,{store:s=null,signer:a=null,nowMs:t=null}={}){let i=s||p(),o=t||Date.now();if(!e||!e.kappa)return{ok:!1,error:"not a payment"};let r=i.get(e.kappa);if(!r)return{ok:!1,error:"no escrow to refund"};let n=await g(Object.values(r.transitions),o);if(n.state==="claimed")return{ok:!1,error:"already claimed, can't refund"};if(n.state==="refunded")return{ok:!1,error:"already refunded"};if(!(r.expires&&o>r.expires))return{ok:!1,error:"not yet expired, can't refund"};let c=a||await y(),f=await S(e,{signer:c,at:o});return r.transitions[f.tid]=f,i.set(e.kappa,r),{ok:!0,amount:r.amount,asset:r.asset,mode:r.mode}}async function _(e,{store:s=null,nowMs:a=null}={}){let t=s||p(),i=a||Date.now(),o=typeof e=="string"?e:e&&e.kappa;if(!o)return{state:"none"};let r=t.get(o);if(!r)return{state:"none"};let n=await g(Object.values(r.transitions),i);return n.state==="open"?{state:r.funded?"open":"none",committed:r.committed||null,canonicalWinner:null,reconciled:!1}:{state:n.state,canonicalWinner:n.winner,committed:r.committed||null,reconciled:!!(r.committed&&n.state==="claimed"&&r.committed!==n.winner),by:n.by,amount:n.amount,asset:n.asset}}function N(e,{store:s=null}={}){let a=s||p(),t=typeof e=="string"?e:e&&e.kappa,i=a.get(t);return i?D(i):[]}async function L(e,s,{store:a=null,nowMs:t=null}={}){let i=a||p(),o=t||Date.now(),r=typeof e=="string"?e:e&&e.kappa,n=i.get(r)||x();return await j(n,s||[],o),i.set(r,n),_(r,{store:i,nowMs:o})}export{F as claimEscrow,N as exportPeer,E as fund,C as localStore,L as mergePeer,J as refund,_ as status};