@stacksjs/desktop-build 0.75.38 → 0.75.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  // @bun
2
- import{closeSync as w,existsSync as u,openSync as y,readSync as x,statSync as P}from"fs";import{dirname as T,join as a}from"path";import o from"process";import{fileURLToPath as z}from"url";import{craftBinaryNotFoundMessage as N,resolveCraftBinary as _}from"craft-native";function E(e,t,r={}){if(!t.trim())throw Error("Invite token is required");let n=new URL("/invite",/^https?:\/\//.test(e)?e:`https://${e}`);if(n.searchParams.set("token",t),r.email)n.searchParams.set("email",r.email);if(r.team)n.searchParams.set("team",r.team);if(r.role)n.searchParams.set("role",r.role);if(r.expiresAt){let s=r.expiresAt instanceof Date?r.expiresAt:new Date(r.expiresAt);if(Number.isNaN(s.valueOf()))throw Error("Invite expiration must be a valid date");n.searchParams.set("expires",s.toISOString())}return n.toString()}async function Y(e,t){return Promise.all(e.map(async(r)=>{let n=typeof r==="string"?{email:r}:r;try{R(n.email);let s=await t.createToken(n),i=E(t.baseUrl,s,{email:n.email,team:n.team,role:n.role,expiresAt:t.expiresAt});return await t.send({recipient:n,url:i}),{recipient:n,url:i,delivered:!0}}catch(s){return{recipient:n,url:"",delivered:!1,error:s instanceof Error?s.message:String(s)}}}))}function R(e){if(!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e))throw Error(`Invalid invite email: ${e}`)}import{createHash as S,createPublicKey as I,verify as A}from"crypto";import{mkdir as C,rename as B,writeFile as U}from"fs/promises";import{dirname as F}from"path";async function re(e){k(e.manifestUrl);let t=await(e.fetcher||fetch)(e.manifestUrl,{headers:{Accept:"application/json"}});if(!t.ok)throw Error(`Desktop update check failed with HTTP ${t.status}`);let r=m(await t.json());return h(r,e.trustedKeys),L(r.version,e.currentVersion)>0?r:null}async function ne(e,t,r=fetch,n){if(m(e),!n)throw Error("Desktop update staging requires trusted signing keys");h(e,n),k(e.url);let s=await r(e.url);if(!s.ok)throw Error(`Desktop update download failed with HTTP ${s.status}`);let i=new Uint8Array(await s.arrayBuffer());if(i.byteLength!==e.size)throw Error(`Desktop update size mismatch: expected ${e.size}, received ${i.byteLength}`);if(S("sha256").update(i).digest("hex")!==e.sha256.toLowerCase())throw Error("Desktop update checksum mismatch");await C(F(t),{recursive:!0});let f=`${t}.download-${crypto.randomUUID()}`;return await U(f,i,{mode:448}),await B(f,t),{manifest:e,path:t,bytes:i.byteLength}}function L(e,t){let r=c(e),n=c(t);for(let s=0;s<3;s++){let i=r[s]-n[s];if(i!==0)return Math.sign(i)}return 0}function m(e){if(!e||typeof e!=="object")throw Error("Desktop update manifest must be an object");let t=e;if(c(t.version||""),typeof t.url!=="string")throw Error("Desktop update manifest requires a URL");if(typeof t.sha256!=="string"||!/^[a-f0-9]{64}$/i.test(t.sha256))throw Error("Desktop update manifest requires a SHA-256 checksum");if(!Number.isInteger(t.size)||t.size<0)throw Error("Desktop update manifest requires a non-negative integer size");if(typeof t.sourceRevision!=="string"||!/^[0-9a-f]{40}$/.test(t.sourceRevision))throw Error("Desktop update manifest requires a full source revision");if(typeof t.platform!=="string"||!["darwin","linux","win32"].includes(t.platform))throw Error("Desktop update manifest requires a supported platform identifier");if(typeof t.architecture!=="string"||!/^[a-z0-9_-]+$/.test(t.architecture))throw Error("Desktop update manifest requires an architecture");if(typeof t.signingKeyId!=="string"||!/^[a-zA-Z0-9._-]+$/.test(t.signingKeyId))throw Error("Desktop update manifest requires a signing key ID");if(typeof t.signature!=="string"||!/^[A-Za-z0-9_-]+$/.test(t.signature))throw Error("Desktop update manifest requires an Ed25519 signature");return t}function j(e){return Buffer.from(JSON.stringify({version:e.version,url:e.url,sha256:e.sha256.toLowerCase(),size:e.size,sourceRevision:e.sourceRevision,platform:e.platform,architecture:e.architecture,signingKeyId:e.signingKeyId,publishedAt:e.publishedAt||null,notes:e.notes||null}),"utf8")}function h(e,t){let r=t[e.signingKeyId];if(!r)throw Error(`Desktop update signing key is not trusted: ${e.signingKeyId}`);let n="ed25519-public:";if(!r.startsWith(n))throw Error("Desktop update signing key has an invalid encoding");try{let s=I({key:Buffer.from(r.slice(n.length),"base64url"),format:"der",type:"spki"}),i=Buffer.from(e.signature,"base64url");if(s.asymmetricKeyType!=="ed25519"||i.length!==64||!A(null,j(e),s,i))throw Error("invalid")}catch{throw Error("Desktop update manifest signature verification failed")}}function c(e){let t=e.match(/^v?(\d+)\.(\d+)\.(\d+)(?:-[0-9A-Za-z.-]+)?$/);if(!t)throw Error(`Invalid semantic version: ${e}`);return[Number(t[1]),Number(t[2]),Number(t[3])]}function k(e){let t=new URL(e),r=["localhost","127.0.0.1","::1"].includes(t.hostname);if(t.protocol!=="https:"&&!(t.protocol==="http:"&&r))throw Error("Desktop updates require HTTPS outside loopback development")}var O=[{platform:"darwin",architecture:"arm64",status:"experimental",osVersions:["macOS 15 runner"],packageFormat:"DMG + PKG",signing:"pending",notarization:"pending",installLaunchEvidence:"https://github.com/stacksjs/stacks/actions/runs/29890804159",updateRollbackEvidence:"https://github.com/stacksjs/stacks/actions/runs/29890804159",packagingEvidence:"https://github.com/stacksjs/protocol/blob/main/evidence/craft.json",blockingIssues:["https://github.com/stacksjs/stacks/issues/2062","https://github.com/home-lang/craft/issues/11"],limitations:["Lifecycle fixtures are unsigned while platform identities remain unprovisioned.","Craft v0.0.48 is source-tagged, but its upstream release workflow is blocked by repository Actions policy."]},{platform:"darwin",architecture:"x64",status:"experimental",osVersions:["macOS 15 Intel runner"],packageFormat:"DMG + PKG",signing:"pending",notarization:"pending",installLaunchEvidence:"https://github.com/stacksjs/stacks/actions/runs/29890804159",updateRollbackEvidence:"https://github.com/stacksjs/stacks/actions/runs/29890804159",packagingEvidence:"https://github.com/stacksjs/protocol/blob/main/evidence/craft.json",blockingIssues:["https://github.com/stacksjs/stacks/issues/2062","https://github.com/home-lang/craft/issues/11"],limitations:["Lifecycle fixtures are unsigned while platform identities remain unprovisioned.","Craft v0.0.48 is source-tagged, but its upstream release workflow is blocked by repository Actions policy."]},{platform:"linux",architecture:"x64",status:"experimental",osVersions:["Ubuntu 24.04 runner"],packageFormat:"DEB",signing:"pending",notarization:"not-applicable",installLaunchEvidence:"https://github.com/stacksjs/stacks/actions/runs/29890804159",updateRollbackEvidence:"https://github.com/stacksjs/stacks/actions/runs/29890804159",packagingEvidence:"https://github.com/stacksjs/protocol/blob/main/evidence/craft.json",blockingIssues:["https://github.com/stacksjs/stacks/issues/2062","https://github.com/home-lang/craft/issues/11"],limitations:["The lifecycle fixture is unsigned and no package repository support policy is published.","Craft v0.0.48 is source-tagged, but its upstream release workflow is blocked by repository Actions policy."]},{platform:"win32",architecture:"x64",status:"experimental",osVersions:["Windows Server 2025 runner"],packageFormat:"MSI + ZIP",signing:"pending",notarization:"not-applicable",installLaunchEvidence:"https://github.com/stacksjs/stacks/actions/runs/29890804159",updateRollbackEvidence:"https://github.com/stacksjs/stacks/actions/runs/29890804159",packagingEvidence:"https://github.com/stacksjs/protocol/blob/main/evidence/craft.json",blockingIssues:["https://github.com/stacksjs/stacks/issues/2062","https://github.com/home-lang/craft/issues/11"],limitations:["The lifecycle fixture is not Authenticode-signed.","Craft v0.0.48 is source-tagged, but its upstream release workflow is blocked by repository Actions policy."]}];function M(e=process.platform,t=process.arch){return O.find((r)=>r.platform===e&&r.architecture===t)}function ie(e,t=process.platform,r=process.arch){let n=M(t,r);if(!n)throw Error(`Desktop target ${t}/${r} is unsupported`);if(e==="stable"&&(n.status!=="stable"||n.signing!=="enforced"||n.platform==="darwin"&&n.notarization!=="enforced"||!n.installLaunchEvidence||!n.updateRollbackEvidence))throw Error(`Desktop target ${t}/${r} cannot be released as stable; complete stacksjs/stacks#2059, #2062, and #2063 first`);return n}function v(e=o.env.CRAFT_BIN){return _(e)}function de(e=o.env.CRAFT_BIN,t=(r)=>Bun.which(r)){let r=v(e);if(r!=="craft")return r;let n=t(r);if(!n)throw Error(N(r));return n}var D="app/Desktop/launcher.ts";function ge(e=o.cwd()){return u(a(e,D))}function fe(e){return["build","--compile",...e.minify===!1?[]:["--minify"],"--no-compile-autoload-bunfig","--no-compile-autoload-dotenv",e.entry,"--outfile",e.outfile]}function me(e=o.cwd()){let t=a(e,D);if(u(t))return t;let r=T(z(import.meta.url)),s=[a(r,"launcher.ts"),a(r,"launcher.js")].find((i)=>u(i));if(!s)throw Error("Desktop launcher entrypoint not found. Reinstall @stacksjs/desktop - the package must ship dist/launcher.js.");return s}var b=new Set(["CFBundleName","CFBundleDisplayName","CFBundleIdentifier","CFBundleVersion","CFBundleShortVersionString","CFBundleExecutable","CFBundlePackageType","CFBundleInfoDictionaryVersion","CFBundleIconFile"]);function p(e){return e.replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;").replaceAll("'","&apos;")}function l(e,t=""){if(typeof e==="string")return`<string>${p(e)}</string>`;if(typeof e==="boolean")return e?"<true/>":"<false/>";if(typeof e==="number"){if(!Number.isFinite(e))throw Error(`Unsupported Info.plist number: ${e}`);return Number.isInteger(e)?`<integer>${e}</integer>`:`<real>${e}</real>`}if(Array.isArray(e)){if(e.length===0)return"<array/>";return`<array>
3
- ${e.map((n)=>`${t} ${l(n,`${t} `)}`).join(`
2
+ var c=(e,t,r)=>()=>{if(e)try{t=e(e=0)}catch(n){r=[n]}if(r)throw r[0];return t};function C(e,t,r={}){if(!t.trim())throw Error("Invite token is required");let n=new URL("/invite",/^https?:\/\//.test(e)?e:`https://${e}`);if(n.searchParams.set("token",t),r.email)n.searchParams.set("email",r.email);if(r.team)n.searchParams.set("team",r.team);if(r.role)n.searchParams.set("role",r.role);if(r.expiresAt){let i=r.expiresAt instanceof Date?r.expiresAt:new Date(r.expiresAt);if(Number.isNaN(i.valueOf()))throw Error("Invite expiration must be a valid date");n.searchParams.set("expires",i.toISOString())}return n.toString()}async function ne(e,t){return Promise.all(e.map(async(r)=>{let n=typeof r==="string"?{email:r}:r;try{A(n.email);let i=await t.createToken(n),s=C(t.baseUrl,i,{email:n.email,team:n.team,role:n.role,expiresAt:t.expiresAt});return await t.send({recipient:n,url:s}),{recipient:n,url:s,delivered:!0}}catch(i){return{recipient:n,url:"",delivered:!1,error:i instanceof Error?i.message:String(i)}}}))}function A(e){if(!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e))throw Error(`Invalid invite email: ${e}`)}import{createHash as B,createPublicKey as U,verify as L}from"crypto";import{mkdir as F,rename as j,writeFile as O}from"fs/promises";import{dirname as M}from"path";async function ce(e){b(e.manifestUrl);let t=await(e.fetcher||fetch)(e.manifestUrl,{headers:{Accept:"application/json"}});if(!t.ok)throw Error(`Desktop update check failed with HTTP ${t.status}`);let r=m(await t.json());return k(r,e.trustedKeys),P(r.version,e.currentVersion)>0?r:null}async function ue(e,t,r=fetch,n){if(m(e),!n)throw Error("Desktop update staging requires trusted signing keys");k(e,n),b(e.url);let i=await r(e.url);if(!i.ok)throw Error(`Desktop update download failed with HTTP ${i.status}`);let s=new Uint8Array(await i.arrayBuffer());if(s.byteLength!==e.size)throw Error(`Desktop update size mismatch: expected ${e.size}, received ${s.byteLength}`);if(B("sha256").update(s).digest("hex")!==e.sha256.toLowerCase())throw Error("Desktop update checksum mismatch");await F(M(t),{recursive:!0});let h=`${t}.download-${crypto.randomUUID()}`;return await O(h,s,{mode:448}),await j(h,t),{manifest:e,path:t,bytes:s.byteLength}}function P(e,t){let r=u(e),n=u(t);for(let i=0;i<3;i++){let s=r[i]-n[i];if(s!==0)return Math.sign(s)}return 0}function m(e){if(!e||typeof e!=="object")throw Error("Desktop update manifest must be an object");let t=e;if(u(t.version||""),typeof t.url!=="string")throw Error("Desktop update manifest requires a URL");if(typeof t.sha256!=="string"||!/^[a-f0-9]{64}$/i.test(t.sha256))throw Error("Desktop update manifest requires a SHA-256 checksum");if(!Number.isInteger(t.size)||t.size<0)throw Error("Desktop update manifest requires a non-negative integer size");if(typeof t.sourceRevision!=="string"||!/^[0-9a-f]{40}$/.test(t.sourceRevision))throw Error("Desktop update manifest requires a full source revision");if(typeof t.platform!=="string"||!["darwin","linux","win32"].includes(t.platform))throw Error("Desktop update manifest requires a supported platform identifier");if(typeof t.architecture!=="string"||!/^[a-z0-9_-]+$/.test(t.architecture))throw Error("Desktop update manifest requires an architecture");if(typeof t.signingKeyId!=="string"||!/^[a-zA-Z0-9._-]+$/.test(t.signingKeyId))throw Error("Desktop update manifest requires a signing key ID");if(typeof t.signature!=="string"||!/^[A-Za-z0-9_-]+$/.test(t.signature))throw Error("Desktop update manifest requires an Ed25519 signature");return t}function z(e){return Buffer.from(JSON.stringify({version:e.version,url:e.url,sha256:e.sha256.toLowerCase(),size:e.size,sourceRevision:e.sourceRevision,platform:e.platform,architecture:e.architecture,signingKeyId:e.signingKeyId,publishedAt:e.publishedAt||null,notes:e.notes||null}),"utf8")}function k(e,t){let r=t[e.signingKeyId];if(!r)throw Error(`Desktop update signing key is not trusted: ${e.signingKeyId}`);let n="ed25519-public:";if(!r.startsWith(n))throw Error("Desktop update signing key has an invalid encoding");try{let i=U({key:Buffer.from(r.slice(n.length),"base64url"),format:"der",type:"spki"}),s=Buffer.from(e.signature,"base64url");if(i.asymmetricKeyType!=="ed25519"||s.length!==64||!L(null,z(e),i,s))throw Error("invalid")}catch{throw Error("Desktop update manifest signature verification failed")}}function u(e){let t=e.match(/^v?(\d+)\.(\d+)\.(\d+)(?:-[0-9A-Za-z.-]+)?$/);if(!t)throw Error(`Invalid semantic version: ${e}`);return[Number(t[1]),Number(t[2]),Number(t[3])]}function b(e){let t=new URL(e),r=["localhost","127.0.0.1","::1"].includes(t.hostname);if(t.protocol!=="https:"&&!(t.protocol==="http:"&&r))throw Error("Desktop updates require HTTPS outside loopback development")}var w=()=>{};function N(e=process.platform,t=process.arch){return T.find((r)=>r.platform===e&&r.architecture===t)}function le(e,t=process.platform,r=process.arch){let n=N(t,r);if(!n)throw Error(`Desktop target ${t}/${r} is unsupported`);if(e==="stable"&&(n.status!=="stable"||n.signing!=="enforced"||n.platform==="darwin"&&n.notarization!=="enforced"||!n.installLaunchEvidence||!n.updateRollbackEvidence))throw Error(`Desktop target ${t}/${r} cannot be released as stable; complete stacksjs/stacks#2059, #2062, and #2063 first`);return n}var T;var y=c(()=>{T=[{platform:"darwin",architecture:"arm64",status:"experimental",osVersions:["macOS 15 runner"],packageFormat:"DMG + PKG",signing:"pending",notarization:"pending",installLaunchEvidence:"https://github.com/stacksjs/stacks/actions/runs/29890804159",updateRollbackEvidence:"https://github.com/stacksjs/stacks/actions/runs/29890804159",packagingEvidence:"https://github.com/stacksjs/protocol/blob/main/evidence/craft.json",blockingIssues:["https://github.com/stacksjs/stacks/issues/2062"],limitations:["Lifecycle fixtures are unsigned while platform identities remain unprovisioned.","Lifecycle evidence was produced against Craft v0.0.48 built from source (bf75807). Craft now publishes a prebuilt craft-darwin-arm64.zip with every release, and no Stacks lifecycle run has re-verified a prebuilt release yet."]},{platform:"darwin",architecture:"x64",status:"experimental",osVersions:["macOS 15 Intel runner"],packageFormat:"DMG + PKG",signing:"pending",notarization:"pending",installLaunchEvidence:"https://github.com/stacksjs/stacks/actions/runs/29890804159",updateRollbackEvidence:"https://github.com/stacksjs/stacks/actions/runs/29890804159",packagingEvidence:"https://github.com/stacksjs/protocol/blob/main/evidence/craft.json",blockingIssues:["https://github.com/stacksjs/stacks/issues/2062"],limitations:["Lifecycle fixtures are unsigned while platform identities remain unprovisioned.","Lifecycle evidence was produced against Craft v0.0.48 built from source (bf75807). Craft now publishes a prebuilt craft-darwin-x64.zip with every release, and no Stacks lifecycle run has re-verified a prebuilt release yet."]},{platform:"linux",architecture:"x64",status:"experimental",osVersions:["Ubuntu 24.04 runner"],packageFormat:"DEB",signing:"pending",notarization:"not-applicable",installLaunchEvidence:"https://github.com/stacksjs/stacks/actions/runs/29890804159",updateRollbackEvidence:"https://github.com/stacksjs/stacks/actions/runs/29890804159",packagingEvidence:"https://github.com/stacksjs/protocol/blob/main/evidence/craft.json",blockingIssues:["https://github.com/stacksjs/stacks/issues/2062"],limitations:["The lifecycle fixture is unsigned and no package repository support policy is published.","Lifecycle evidence was produced against Craft v0.0.48 built from source (bf75807). Craft now publishes a prebuilt craft-linux-x64.zip with every release, and no Stacks lifecycle run has re-verified a prebuilt release yet."]},{platform:"win32",architecture:"x64",status:"experimental",osVersions:["Windows Server 2025 runner"],packageFormat:"MSI + ZIP",signing:"pending",notarization:"not-applicable",installLaunchEvidence:"https://github.com/stacksjs/stacks/actions/runs/29890804159",updateRollbackEvidence:"https://github.com/stacksjs/stacks/actions/runs/29890804159",packagingEvidence:"https://github.com/stacksjs/protocol/blob/main/evidence/craft.json",blockingIssues:["https://github.com/stacksjs/stacks/issues/2062"],limitations:["The lifecycle fixture is not Authenticode-signed.","Lifecycle evidence was produced against Craft v0.0.48 built from source (bf75807). Craft now publishes a prebuilt craft-windows-x64.zip with every release, and no Stacks lifecycle run has re-verified a prebuilt release yet."]}]});import{closeSync as x,existsSync as p,openSync as D,readSync as E,statSync as _}from"fs";import{dirname as K,join as a}from"path";import o from"process";import{fileURLToPath as q}from"url";import{craftBinaryNotFoundMessage as V,resolveCraftBinary as W}from"craft-native";function R(e=o.env.CRAFT_BIN){return W(e)}function be(e=o.env.CRAFT_BIN,t=(r)=>Bun.which(r)){let r=R(e);if(r!=="craft")return r;let n=t(r);if(!n)throw Error(V(r));return n}function we(e=o.cwd()){return p(a(e,S))}function ye(e){return["build","--compile",...e.minify===!1?[]:["--minify"],"--no-compile-autoload-bunfig","--no-compile-autoload-dotenv",e.entry,"--outfile",e.outfile]}function ve(e=o.cwd()){let t=a(e,S);if(p(t))return t;let r=K(q(import.meta.url)),i=[a(r,"launcher.ts"),a(r,"launcher.js")].find((s)=>p(s));if(!i)throw Error("Desktop launcher entrypoint not found. Reinstall @stacksjs/desktop - the package must ship dist/launcher.js.");return i}function l(e){return e.replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;").replaceAll("'","&apos;")}function d(e,t=""){if(typeof e==="string")return`<string>${l(e)}</string>`;if(typeof e==="boolean")return e?"<true/>":"<false/>";if(typeof e==="number"){if(!Number.isFinite(e))throw Error(`Unsupported Info.plist number: ${e}`);return Number.isInteger(e)?`<integer>${e}</integer>`:`<real>${e}</real>`}if(Array.isArray(e)){if(e.length===0)return"<array/>";return`<array>
3
+ ${e.map((n)=>`${t} ${d(n,`${t} `)}`).join(`
4
4
  `)}
5
5
  ${t}</array>`}if(e&&typeof e==="object"){let r=Object.entries(e);if(r.length===0)return"<dict/>";return`<dict>
6
- ${r.map(([s,i])=>`${t} <key>${p(s)}</key>${l(i,`${t} `)}`).join(`
6
+ ${r.map(([i,s])=>`${t} <key>${l(i)}</key>${d(s,`${t} `)}`).join(`
7
7
  `)}
8
- ${t}</dict>`}throw Error(`Unsupported Info.plist value: ${JSON.stringify(e)??String(e)}`)}function he(e){let t=Object.keys(e).filter((s)=>b.has(s)),r=Object.entries(e).filter(([s])=>!b.has(s));if(r.length===0)return{xml:"",ignored:t};return{xml:`
9
- ${r.map(([s,i])=>` <key>${p(s)}</key>${l(i," ")}`).join(`
10
- `)}`,ignored:t}}function K(e,t={}){if(!Number.isInteger(e)||e<1||e>65535)throw RangeError(`Invalid desktop development port: ${e}`);let r=t.url||o.env.APP_URL;if(!r)return"https://stacks.test";let n=/^https?:\/\//.test(r)?r:`https://${r}`;return new URL(n).toString().replace(/\/$/,"")}function q(e,t={}){let r=[v(t.craftBin),K(e,t),"--title",t.title||"Stacks","--width",String(t.width||1400),"--height",String(t.height||900)];if(t.hotReload!==!1)r.push("--hot-reload");if(t.devTools===!1)r.push("--no-devtools");if(t.darkMode)r.push("--dark");if(t.systemTray)r.push("--system-tray");if(t.hideDockIcon)r.push("--hide-dock-icon");if(t.menubarOnly)r.push("--menubar-only");return r}function ke(e,t="stable"){if(!/^[a-z0-9-]+$/i.test(t))throw Error("Update channel may only contain letters, numbers, and hyphens");return new URL(`/desktop/updates/${t}.json`,/^https?:\/\//.test(e)?e:`https://${e}`).toString()}async function V(e){Bun.spawn(e,{stdin:"inherit",stdout:"inherit",stderr:"inherit"}).unref()}async function be(e,t={},r=V){return await r(q(e,t)),!0}var W="---- Bun!",H=8388608,d=41943040,G=new Set([4277009102,3472551422,4277009103,3489328638,3405691582,3199925962]);function Z(e){try{let t=y(e,"r");try{let r=Buffer.alloc(4);if(x(t,r,0,4,0)<4)return!1;return G.has(r.readUInt32BE(0))}finally{w(t)}}catch{return!1}}function we(e){return Z(e)?"MacOS":"Resources"}function ye(e){return["codesign","--force",e.identity==="-"?"--timestamp=none":"--timestamp","--options","runtime",...e.entitlements?["--entitlements",e.entitlements]:[],"--sign",e.identity,e.target]}function xe(e,t=J){let r=t(e);if(!r||r.size<d)return!1;return r.tail.includes(W)}function J(e){try{let{size:t}=P(e);if(t<d)return{size:t,tail:""};let r=y(e,"r");try{let n=Math.min(H,t),s=Buffer.alloc(n);return x(r,s,0,n,t-n),{size:t,tail:s.toString("latin1")}}finally{w(r)}}catch{return null}}function ve(e){if(e.length<2)return null;let t=[...e].sort((i,g)=>g.bytes-i.bytes),n=t.slice(1).length*d,s=t.map((i)=>`${i.name} (${(i.bytes/1024/1024).toFixed(1)} MB)`).join(", ");return`This bundle ships ${e.length} Bun-compiled executables: ${s}. Each embeds a full copy of the Bun runtime, so at least ${(n/1024/1024).toFixed(0)} MB of the bundle is the same runtime repeated. `+"Compiling one binary that dispatches on a subcommand \u2014 `MyApp agent`, `MyApp worker` \u2014 keeps the helpers as separate processes and pays for the runtime once."}export{W as BUN_EXECUTABLE_MARKER,b as RESERVED_PLIST_KEYS,D as USERLAND_LAUNCHER,ie as assertDesktopReleaseChannel,we as bundleDirectoryFor,re as checkForDesktopUpdate,ye as codesignArgs,L as compareVersions,q as craftDevCommand,E as createInviteLink,ke as createUpdateManifestUrl,ve as describeRuntimeDuplication,fe as desktopLauncherCompileArgs,M as desktopSupport,O as desktopSupportMatrix,ge as hasUserlandDesktopLauncher,Z as isMachO,xe as looksLikeBunExecutable,be as openDevWindow,l as plistValue,he as renderUserlandPlistEntries,v as resolveCraftBinary,de as resolveCraftExecutable,me as resolveDesktopLauncher,K as resolveDevWindowUrl,Y as sendDesktopInvites,ne as stageDesktopUpdate,j as updateManifestPayload,m as validateUpdateManifest,h as verifyUpdateManifestSignature,p as xmlEscape};
8
+ ${t}</dict>`}throw Error(`Unsupported Info.plist value: ${JSON.stringify(e)??String(e)}`)}function xe(e){let t=Object.keys(e).filter((i)=>v.has(i)),r=Object.entries(e).filter(([i])=>!v.has(i));if(r.length===0)return{xml:"",ignored:t};return{xml:`
9
+ ${r.map(([i,s])=>` <key>${l(i)}</key>${d(s," ")}`).join(`
10
+ `)}`,ignored:t}}function H(e,t={}){if(!Number.isInteger(e)||e<1||e>65535)throw RangeError(`Invalid desktop development port: ${e}`);let r=t.url||o.env.APP_URL;if(!r)return"https://stacks.test";let n=/^https?:\/\//.test(r)?r:`https://${r}`;return new URL(n).toString().replace(/\/$/,"")}function G(e,t={}){let r=[R(t.craftBin),H(e,t),"--title",t.title||"Stacks","--width",String(t.width||1400),"--height",String(t.height||900)];if(t.hotReload!==!1)r.push("--hot-reload");if(t.devTools===!1)r.push("--no-devtools");if(t.darkMode)r.push("--dark");if(t.systemTray)r.push("--system-tray");if(t.hideDockIcon)r.push("--hide-dock-icon");if(t.menubarOnly)r.push("--menubar-only");return r}function De(e,t="stable"){if(!/^[a-z0-9-]+$/i.test(t))throw Error("Update channel may only contain letters, numbers, and hyphens");return new URL(`/desktop/updates/${t}.json`,/^https?:\/\//.test(e)?e:`https://${e}`).toString()}async function Z(e){Bun.spawn(e,{stdin:"inherit",stdout:"inherit",stderr:"inherit"}).unref()}async function Ee(e,t={},r=Z){return await r(G(e,t)),!0}function Q(e){try{let t=D(e,"r");try{let r=Buffer.alloc(4);if(E(t,r,0,4,0)<4)return!1;return X.has(r.readUInt32BE(0))}finally{x(t)}}catch{return!1}}function Re(e){return Q(e)?"MacOS":"Resources"}function Se(e){return["codesign","--force",e.identity==="-"?"--timestamp=none":"--timestamp","--options","runtime",...e.entitlements?["--entitlements",e.entitlements]:[],"--sign",e.identity,e.target]}function Ie(e,t=ee){let r=t(e);if(!r||r.size<f)return!1;return r.tail.includes(J)}function ee(e){try{let{size:t}=_(e);if(t<f)return{size:t,tail:""};let r=D(e,"r");try{let n=Math.min(Y,t),i=Buffer.alloc(n);return E(r,i,0,n,t-n),{size:t,tail:i.toString("latin1")}}finally{x(r)}}catch{return null}}function Ce(e){if(e.length<2)return null;let t=[...e].sort((s,g)=>g.bytes-s.bytes),n=t.slice(1).length*f,i=t.map((s)=>`${s.name} (${(s.bytes/1024/1024).toFixed(1)} MB)`).join(", ");return`This bundle ships ${e.length} Bun-compiled executables: ${i}. Each embeds a full copy of the Bun runtime, so at least ${(n/1024/1024).toFixed(0)} MB of the bundle is the same runtime repeated. `+"Compiling one binary that dispatches on a subcommand \u2014 `MyApp agent`, `MyApp worker` \u2014 keeps the helpers as separate processes and pays for the runtime once."}var S="app/Desktop/launcher.ts",v,J="---- Bun!",Y=8388608,f=41943040,X;var te=c(()=>{w();y();v=new Set(["CFBundleName","CFBundleDisplayName","CFBundleIdentifier","CFBundleVersion","CFBundleShortVersionString","CFBundleExecutable","CFBundlePackageType","CFBundleInfoDictionaryVersion","CFBundleIconFile"]);X=new Set([4277009102,3472551422,4277009103,3489328638,3405691582,3199925962])});te();export{J as BUN_EXECUTABLE_MARKER,v as RESERVED_PLIST_KEYS,S as USERLAND_LAUNCHER,le as assertDesktopReleaseChannel,Re as bundleDirectoryFor,ce as checkForDesktopUpdate,Se as codesignArgs,P as compareVersions,G as craftDevCommand,C as createInviteLink,De as createUpdateManifestUrl,Ce as describeRuntimeDuplication,ye as desktopLauncherCompileArgs,N as desktopSupport,T as desktopSupportMatrix,we as hasUserlandDesktopLauncher,Q as isMachO,Ie as looksLikeBunExecutable,Ee as openDevWindow,d as plistValue,xe as renderUserlandPlistEntries,R as resolveCraftBinary,be as resolveCraftExecutable,ve as resolveDesktopLauncher,H as resolveDevWindowUrl,ne as sendDesktopInvites,ue as stageDesktopUpdate,z as updateManifestPayload,m as validateUpdateManifest,k as verifyUpdateManifestSignature,l as xmlEscape};
@@ -0,0 +1,2 @@
1
+ import type { ProbePageConfig } from './payload';
2
+ export declare function probePageHtml(script: string, config: ProbePageConfig): string;
@@ -0,0 +1,10 @@
1
+ // The interactive-content probe (stacksjs/stacks#877): the record schema, the
2
+ // statistics behind it, and the capability-matrix section rendered from it.
3
+ // The runner and the page script are entrypoints of their own, not exports:
4
+ // one opens a native window and the other only runs in a browser.
5
+ export * from './payload';
6
+ export * from './record';
7
+ export * from './records';
8
+ export * from './render';
9
+ export * from './schema';
10
+ export * from './stats';
@@ -0,0 +1,7 @@
1
+ // @bun
2
+ var re={idleMs:3000,loadMs:6000,cpuLoadMsPerFrame:6,instances:40000,interactionWindowMs:20000,tryNativeFullscreen:!0};var k=1,M=["available","unavailable","measured","requires-interaction","denied","error"],f=[["webgl2","WebGL2"],["webgpu","WebGPU"],["framePacing","Frame pacing"],["inputLatency","Input latency"],["audioLatency","Audio latency"],["gamepad","Gamepad API"],["fullscreen","Fullscreen"],["pointerLock","Pointer lock"]];function l(t){return typeof t==="object"&&t!==null&&!Array.isArray(t)}function m(t){return typeof t==="string"&&t.trim().length>0}function T(t){let e=[];if(!l(t))return["record is not an object"];if(t.schema!==1)e.push(`schema must be 1, got ${JSON.stringify(t.schema)}`);if(!m(t.measuredAt)||Number.isNaN(Date.parse(t.measuredAt)))e.push("measuredAt must be an ISO 8601 timestamp");let n=t.host;if(!l(n))e.push("host is missing");else{if(n.kind!=="craft"&&n.kind!=="browser")e.push(`host.kind must be craft or browser, got ${JSON.stringify(n.kind)}`);for(let s of["name","version","userAgent"])if(!m(n[s]))e.push(`host.${s} is required`);if(n.kind==="craft"&&m(n.version)&&!/^\d+\.\d+\.\d+/.test(n.version))e.push(`host.version must be a Craft release version, got ${JSON.stringify(n.version)}`)}let r=t.os;if(!l(r))e.push("os is missing");else for(let s of["platform","arch","name","version"])if(!m(r[s]))e.push(`os.${s} is required`);let o=t.machine;if(!l(o))e.push("machine is missing");else{for(let u of["model","cpu"])if(!m(o[u]))e.push(`machine.${u} is required`);let s=o.display;if(!l(s))e.push("machine.display is missing");else{if(s.refreshHz!==null&&!(typeof s.refreshHz==="number"&&s.refreshHz>0))e.push("machine.display.refreshHz must be a positive number or null");if(!(typeof s.devicePixelRatio==="number"&&s.devicePixelRatio>0))e.push("machine.display.devicePixelRatio must be a positive number")}}let a=t.run;if(!l(a))e.push("run is missing");else{for(let s of["idleMs","loadMs","cpuLoadMsPerFrame","triangles","interactionWindowMs"])if(!(typeof a[s]==="number"&&a[s]>=0))e.push(`run.${s} must be a non-negative number`);if(typeof a.sawRealInput!=="boolean")e.push("run.sawRealInput must be a boolean")}let i=t.probes;if(!l(i))return e.push("probes is missing"),e;for(let[s]of f){let u=i[s];if(!l(u)){e.push(`probes.${s} is missing`);continue}if(!M.includes(u.status))e.push(`probes.${s}.status must be one of ${M.join(", ")}, got ${JSON.stringify(u.status)}`);if(!m(u.note))e.push(`probes.${s}.note is required: say what was observed, even on success`);if(u.data!==void 0&&!l(u.data))e.push(`probes.${s}.data must be an object when present`);if(u.status==="measured"&&!l(u.data))e.push(`probes.${s} is measured but carries no data`)}for(let s of Object.keys(i))if(!f.some(([u])=>u===s))e.push(`probes.${s} is not a known probe`);return e}function S(t,e="probe record"){let n=T(t);if(n.length>0)throw Error(`${e} is invalid:
3
+ - ${n.join(`
4
+ - `)}`);return t}var C=[24,30,48,50,60,72,75,90,100,120,144,165,240];function d(t,e){if(t.length===0)return null;if(!Number.isFinite(e)||e<0||e>100)throw RangeError(`percentile must be between 0 and 100, got ${e}`);let n=[...t].sort((u,c)=>u-c),r=e/100*(n.length-1),o=Math.floor(r),a=Math.ceil(r),i=n[o],s=n[a];return i+(s-i)*(r-o)}function R(t){let e=[];for(let n=1;n<t.length;n++)e.push(t[n]-t[n-1]);return e}function H(t,e=1,n=50){if(e<=0||!Number.isFinite(e))throw RangeError(`bucketMs must be positive, got ${e}`);if(!Number.isInteger(n)||n<1)throw RangeError(`buckets must be a positive integer, got ${n}`);let r=Array.from({length:n},()=>0),o=0;for(let a of t){let i=Math.floor(Math.max(0,a)/e);if(i>=n)o++;else r[i]++}return{bucketMs:e,counts:r,overflow:o}}function A(t,e=0.03){if(!(t>0))throw RangeError(`period must be positive, got ${t}`);let n=1000/t,r=n,o=Number.POSITIVE_INFINITY;for(let a of C){let i=Math.abs(n-a)/a;if(i<o)r=a,o=i}return o<=e?r:Math.round(n*100)/100}function q(t,e){if(!(e>0))throw RangeError(`period must be positive, got ${e}`);let n=0;for(let r of t)n+=Math.max(0,Math.round(r/e)-1);return n}function h(t,e){if(t.length<3)return null;if(!(e>0))throw RangeError(`targetHz must be positive, got ${e}`);let n=R(t),r=t[t.length-1]-t[0],o=1000/e,a=(i)=>Math.round(i*100)/100;return{frames:t.length,durationMs:a(r),fps:a(r>0?n.length*1000/r:0),p50Ms:a(d(n,50)),p95Ms:a(d(n,95)),p99Ms:a(d(n,99)),maxMs:a(Math.max(...n)),targetPeriodMs:a(o),targetHz:e,droppedFrames:q(n,o),longFrames:n.filter((i)=>i>o*1.5).length,histogram:H(n)}}function w(t){let e=t.filter((r)=>Number.isFinite(r)&&r>=0);if(e.length===0)return null;let n=(r)=>Math.round(r*100)/100;return{samples:e.length,p50Ms:n(d(e,50)),p95Ms:n(d(e,95)),maxMs:n(Math.max(...e))}}function ae(t){let e=t?.match(/@\s*([\d.]+)\s*Hz/i);if(!e)return null;let n=Number(e[1]);return Number.isFinite(n)&&n>0?n:null}function le(t){let e=[["Edge",/Edg\/([\d.]+)/],["Firefox",/Firefox\/([\d.]+)/],["Chrome",/Chrome\/([\d.]+)/],["Safari",/Version\/([\d.]+).*Safari\//],["WebKit",/AppleWebKit\/([\d.]+)/]];for(let[n,r]of e){let o=t.match(r);if(o)return{name:n,version:o[1]}}return{name:"unknown",version:"unknown"}}function F(t){return typeof t==="number"?`${Math.round(t*1e4)/10} ms`:"not reported"}function g(t){return t.detail?`${t.outcome} (${t.detail})`:t.outcome}function _(t){let e=t.webgl2;if(!e.ok)return{status:"unavailable",note:`No WebGL2 context: ${e.error??"unknown reason"}.`,data:{...e}};let n=e.unmaskedRenderer||e.renderer;return{status:"available",note:`WebGL2 context created (${e.version}); renderer "${n}", max texture ${e.maxTextureSize}, MSAA up to ${e.maxSamples}x.`,data:{...e}}}function z(t){let e=t.webgpu;if(!e.present)return{status:"unavailable",note:"navigator.gpu is undefined.",data:{...e}};if(!e.adapter||!e.device)return{status:"unavailable",note:`navigator.gpu exists but ${e.adapter?"requestDevice()":"requestAdapter()"} failed: ${e.error??"no reason given"}.`,data:{...e}};return{status:"available",note:`Adapter (${[...new Set([e.vendor,e.architecture].filter(Boolean))].join(" ")||"unnamed adapter"}) and device created; maxTextureDimension2D ${e.maxTextureDimension2D??"unknown"}.`,data:{...e}}}function j(t,e){let{frames:n,config:r}=t;if(n.idle.length<3||n.load.length<3)return{status:"error",note:`Too few animation frames to measure (${n.idle.length} idle, ${n.load.length} under load).`};let o=A(d(R(n.idle),50)),a=e.machine.display.refreshHz??o,i=h(n.idle,a),s=h(n.load,a),u=h(n.load,o),c={rafHz:o,displayHz:a,timerResolutionMs:t.timerResolutionMs,triangles:n.triangles,cpuLoadMsPerFrame:r.cpuLoadMsPerFrame,hiddenDuringSampling:n.hiddenDuringSampling,idle:i,load:s,loadAtRafRate:{droppedFrames:u.droppedFrames,longFrames:u.longFrames}};if(n.hiddenDuringSampling)return{status:"error",note:"The page was hidden while sampling, which throttles requestAnimationFrame; these numbers are not representative. Re-run with the window visible.",data:c};let P=o<a-1?`requestAnimationFrame ran at ${o} Hz on a ${a} Hz display, so a game cannot reach the panel's rate. `:`requestAnimationFrame ran at the display's ${a} Hz. `,b=n.triangles>0?`${n.triangles.toLocaleString("en-US")} triangles`:"a 2D canvas fallback";return{status:"measured",note:`${P}Under load (${b} + ${r.cpuLoadMsPerFrame} ms CPU per frame): p50 ${s.p50Ms} ms, p95 ${s.p95Ms} ms, p99 ${s.p99Ms} ms; ${u.droppedFrames} frames dropped at ${o} Hz and ${s.droppedFrames} at the display's ${a} Hz over ${Math.round(s.durationMs/100)/10} s.${t.timerResolutionMs>=0.5?` The page clock is clamped to ${t.timerResolutionMs} ms, so intervals read as whole milliseconds.`:""}`,data:c}}var E=5;function D(t){let{input:e}=t,n={eventTimeStampComparable:t.eventTimeStampComparable,eventTimingSupported:e.eventTimingSupported,timerResolutionMs:t.timerResolutionMs};if(!t.eventTimeStampComparable)return{status:"error",note:"event.timeStamp does not share performance.now()'s time origin, so event-to-frame time cannot be computed.",data:n};let r={pointerdown:e.pointerdown,pointermove:e.pointermove,keydown:e.keydown},o=Object.values(r).reduce((s,u)=>s+u.length,0);if(o===0)return{status:"requires-interaction",note:`${t.interaction.skipped?"The interactive window was skipped":`No trusted input reached the page in the ${t.interaction.windowMs/1000} s window`}, and synthetic events are not counted. Observable: event.timeStamp is on the performance.now() clock, timer resolution ${t.timerResolutionMs} ms, Event Timing API ${e.eventTimingSupported?"supported":"not supported"}.`,data:n};let a={...n},i=[];for(let[s,u]of Object.entries(r)){let c=w(u.map((b)=>b.toFrameMs)),P=w(u.map((b)=>b.toPostFrameMs));if(a[s]={toFrame:c,toPostFrame:P},c)i.push(`${s} p50 ${c.p50Ms} ms to next frame (${c.samples} ${c.samples===1?"sample":"samples"})`)}if(a.eventTiming=w(e.eventTimingDurations),o<E)return{status:"requires-interaction",note:`Only ${o} trusted input ${o===1?"event":"events"} arrived, fewer than the ${E} needed to summarise. Seen: ${i.join("; ")}. Re-run and use the panel's buttons and keys.`,data:a};return{status:"measured",note:`Event timestamp to the next rAF callback: ${i.join("; ")}. Real input from the OS; no synthetic events.`,data:a}}function B(t){let e=t.audio;if(!e.present)return{status:"unavailable",note:"window.AudioContext is undefined."};if(e.error||!e.initial)return{status:"error",note:`Creating an AudioContext failed: ${e.error??"unknown reason"}.`};let n=e.afterGesturedResume??e.afterUngesturedResume??e.initial,r={initial:e.initial,afterUngesturedResume:e.afterUngesturedResume??null,afterGesturedResume:e.afterGesturedResume??null},o=`baseLatency ${F(n.baseLatency)}, outputLatency ${F(n.outputLatency)} at ${n.sampleRate} Hz`,a=e.afterUngesturedResume?.state==="running"?"The context started without a user gesture":"The context stayed suspended without a user gesture";if(n.state!=="running")return{status:"requires-interaction",note:`${a}, so device latency was not observable. Reported before running: ${o}.`,data:r};if(n.baseLatency===null&&n.outputLatency===null)return{status:"unavailable",note:`${a}, but neither baseLatency nor outputLatency is implemented.`,data:r};return{status:"measured",note:`${a}. ${o}${n.outputLatency===null?" (outputLatency not implemented)":""}.`,data:r}}function U(t){let e=t.gamepad;if(!e.present)return{status:"unavailable",note:"navigator.getGamepads is undefined."};return{status:"available",note:e.connected>0?`navigator.getGamepads() is present and reported ${e.connected} controller(s).`:"navigator.getGamepads() is present; no controller was connected, so axis and button input was not exercised.",data:{...e,secureContext:t.isSecureContext}}}function G(t){let e=t.fullscreen,n=e.nativeBridge.outcome==="not-attempted"||e.nativeBridge.outcome==="no-api"?"":` Craft's own window.craft.window.setFullscreen(true), which needs no gesture: ${g(e.nativeBridge)}.`,r={...e};if(!e.elementApi&&!e.webkitElementApi)return{status:"unavailable",note:`Element.requestFullscreen and webkitRequestFullscreen are both undefined, so the standard Fullscreen API engines call does nothing.${n}`,data:r};let o=`Without a gesture: ${g(e.ungestured)}.`;switch(e.gestured.outcome){case"entered":return{status:"available",note:`requestFullscreen() from a real click entered fullscreen. ${o}${n}`,data:r};case"rejected":case"timeout":return{status:"denied",note:`requestFullscreen() from a real click: ${g(e.gestured)}. ${o}${n}`,data:r};default:return{status:"requires-interaction",note:`The API exists (fullscreenEnabled ${String(e.enabled)}) but needs a real click to test. ${o}${n}`,data:r}}}function W(t){let e=t.pointerLock,n={...e};if(!e.api)return{status:"unavailable",note:"Element.requestPointerLock is undefined.",data:n};let r=`Without a gesture: ${g(e.ungestured)}.`;switch(e.gestured.outcome){case"entered":return{status:"available",note:`requestPointerLock() from a real click locked the pointer. ${r}`,data:n};case"rejected":case"timeout":return{status:"denied",note:`requestPointerLock() from a real click: ${g(e.gestured)}. ${r}`,data:n};default:return{status:"requires-interaction",note:`The API exists but needs a real click to test. ${r}`,data:n}}}function de(t,e){return{schema:k,measuredAt:e.measuredAt,host:{...e.host,userAgent:t.userAgent},os:e.os,machine:{model:e.machine.model,cpu:e.machine.cpu,display:{...e.machine.display,devicePixelRatio:t.devicePixelRatio}},run:{idleMs:t.config.idleMs,loadMs:t.config.loadMs,cpuLoadMsPerFrame:t.config.cpuLoadMsPerFrame,triangles:t.frames.triangles,interactionWindowMs:t.interaction.windowMs,sawRealInput:t.interaction.sawRealInput},probes:{webgl2:_(t),webgpu:z(t),framePacing:j(t,e),inputLatency:D(t),audioLatency:B(t),gamepad:U(t),fullscreen:G(t),pointerLock:W(t)}}}import{readdirSync as V,readFileSync as J}from"fs";import{join as K}from"path";import{fileURLToPath as Y}from"url";var Q=Y(new URL("./results",import.meta.url));function v(t){return t.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")}function he(t){return`${t.host.kind==="craft"?"craft":`browser-${v(t.host.name)}`}-${v(t.os.platform)}-${v(t.os.arch)}.json`}function we(t=Q){let e;try{e=V(t).filter((n)=>n.endsWith(".json")).sort()}catch{return[]}return e.map((n)=>{let r=K(t,n);return S(JSON.parse(J(r,"utf8")),r)})}var X="<!-- interactive-capability:begin -->",I="<!-- interactive-capability:end -->",y="buddy desktop:probe",Z={darwin:"macOS",linux:"Linux",win32:"Windows"};function L(t){return t.host.kind==="craft"?`Craft ${t.host.version}`:`${t.host.name} ${t.host.version}`}function N(t){return`${Z[t.os.platform]??t.os.name} ${t.os.version} ${t.os.arch}, ${t.machine.cpu} (${t.machine.model})`}function ee(t){return[...t].sort((e,n)=>{if(e.host.kind!==n.host.kind)return e.host.kind==="craft"?-1:1;return`${e.host.name} ${e.os.platform} ${e.os.arch}`.localeCompare(`${n.host.name} ${n.os.platform} ${n.os.arch}`)})}function p(t){return typeof t==="number"&&Number.isFinite(t)?t:null}function O(t,...e){let n=t;for(let r of e){if(typeof n!=="object"||n===null)return;n=n[r]}return n}function te(t,e){let n=e.data;if(e.status!=="measured"||!n)return null;switch(t){case"framePacing":{let r=p(n.rafHz),o=p(n.displayHz),a=p(O(n,"load","p99Ms"));if(r===null||o===null||a===null)return null;return`${r} Hz rAF on a ${o} Hz display, p99 ${a} ms under load`}case"inputLatency":{for(let r of["pointerdown","keydown","pointermove"]){let o=p(O(n,r,"toFrame","p50Ms"));if(o!==null)return`${r} p50 ${o} ms to next frame`}return null}case"audioLatency":{let r=n.afterGesturedResume??n.afterUngesturedResume,o=p(r?.baseLatency),a=p(r?.outputLatency),i=(s)=>s===null?"n/a":`${Math.round(s*1e4)/10} ms`;return`base ${i(o)}, output ${i(a)}`}default:return null}}function ne(t,e){if(!e)return"not probed";let n=te(t,e);return n?`**${e.status}** - ${n}`:`**${e.status}**`}function x(t){return t.replaceAll("|","\\|").replaceAll(`
5
+ `," ")}function Re(t){let e=ee(t),n=[X,"","<!-- Generated by `buddy docs:capabilities` from the records in"," storage/framework/core/desktop/src/probe/results/. Re-measure with",` \`${y} --record\`; do not edit this by hand. -->`,"","Whether a Craft window can host a game or other frame-driven content that somebody else","builds, measured by the probe harness in `@stacksjs/desktop-build`","([stacksjs/stacks#877](https://github.com/stacksjs/stacks/issues/877)). Ordinary browsers","on the same machine are measured alongside it for comparison.","","> **How current is this?** Unlike the driver tables above, these are measurements, not","> claims CI can re-check. Each column is one run on one machine, and says when, against","> which host version, on which OS and hardware. If your Craft is newer than the column,",`> the numbers describe something you are not running: re-measure with \`${y} --record\`.`,""];if(e.length===0)return n.push(`No measurement has been recorded yet. Run \`${y} --record\`.`,"",I),n.join(`
6
+ `);n.push(`| Probe | ${e.map((r)=>x(L(r))).join(" | ")} |`,`|---|${e.map(()=>"---").join("|")}|`);for(let[r,o]of f)n.push(`| ${o} | ${e.map((a)=>x(ne(r,a.probes[r]))).join(" | ")} |`);n.push(`| Measured on | ${e.map((r)=>x(N(r))).join(" | ")} |`,`| Measured at | ${e.map((r)=>r.measuredAt.slice(0,10)).join(" | ")} |`,"","- **available** / **measured** - the API exists and the probe exercised it.","- **unavailable** - the API is missing, or creating it failed.","- **requires-interaction** - needs a real click, key press or controller that the run did not have. What was still observable is in the notes; no number is invented.","- **denied** - requested from a real click and refused.","");for(let r of e){let o=r.machine.display.refreshHz===null?"unknown refresh rate":`${r.machine.display.refreshHz} Hz display`;n.push(`**${L(r)}** - ${N(r)}, ${o}`,"");for(let[a,i]of f){let s=r.probes[a];if(s)n.push(`- **${i}** - ${s.note}`)}n.push("")}return n.push("**Out of scope.** Stacks is not shipping a game engine, a physics engine, an audio mixer or","an asset pipeline. This section establishes what a Craft window gives an engine somebody","else wrote; it is not a plan to write one.","",I),n.join(`
7
+ `)}export{C as COMMON_REFRESH_RATES,re as DEFAULT_PROBE_PAGE_CONFIG,X as INTERACTIVE_BEGIN,I as INTERACTIVE_END,E as MIN_INPUT_SAMPLES,y as PROBE_COMMAND,f as PROBE_NAMES,Q as PROBE_RESULTS_DIR,k as PROBE_SCHEMA_VERSION,M as PROBE_STATUSES,S as assertProbeRecord,le as browserFromUserAgent,de as buildProbeRecord,q as droppedFrames,h as framePacing,te as headline,H as histogram,R as intervals,w as latency,we as loadProbeRecords,ee as orderRecords,ae as parseRefreshHz,d as percentile,he as probeRecordFileName,L as recordLabel,Re as renderInteractiveCapabilities,A as snapRefreshRate,T as validateProbeRecord};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,23 @@
1
+ // @bun
2
+ var D=document.getElementById("probe-config"),l=JSON.parse(D?.textContent||"{}"),C=document.getElementById("status"),k=document.getElementById("interactive"),d=document.getElementById("scene"),A=[];function g(t){C.textContent=t,fetch("/progress",{method:"POST",body:t}).catch(()=>{})}function f(t){if(t instanceof Error)return`${t.name}: ${t.message}`;return String(t)}function w(t){return new Promise((e)=>setTimeout(e,t))}function T(){return new Promise((t)=>requestAnimationFrame(t))}function P(t,e,n){return Promise.race([t,w(e).then(()=>n)])}function q(){let t=Number.POSITIVE_INFINITY,e=performance.now(),n=e+50;while(e<n){let r=performance.now(),o=r-e;if(o>0&&o<t)t=o;e=r}return Math.round(t*1000)/1000}function _(){let t=new Event("probe");return Math.abs(t.timeStamp-performance.now())<1e4}function O(){try{let t=document.createElement("canvas").getContext("webgl2");if(!t)return{ok:!1,error:'getContext("webgl2") returned null'};let e=t.getExtension("WEBGL_debug_renderer_info");return{ok:!0,version:String(t.getParameter(t.VERSION)),shadingLanguageVersion:String(t.getParameter(t.SHADING_LANGUAGE_VERSION)),vendor:String(t.getParameter(t.VENDOR)),renderer:String(t.getParameter(t.RENDERER)),unmaskedVendor:e?String(t.getParameter(e.UNMASKED_VENDOR_WEBGL)):void 0,unmaskedRenderer:e?String(t.getParameter(e.UNMASKED_RENDERER_WEBGL)):void 0,maxTextureSize:Number(t.getParameter(t.MAX_TEXTURE_SIZE)),maxSamples:Number(t.getParameter(t.MAX_SAMPLES)),extensions:t.getSupportedExtensions()?.length??0}}catch(t){return{ok:!1,error:f(t)}}}async function G(){let t=navigator.gpu;if(!t)return{present:!1,adapter:!1,device:!1};try{let e=await P(t.requestAdapter(),5000,null);if(!e)return{present:!0,adapter:!1,device:!1,error:"requestAdapter() resolved null or timed out"};let n=e.info??{},r=!1,o;try{let i=await P(e.requestDevice(),5000,null);if(r=i!==null,i?.destroy?.(),!r)o="requestDevice() timed out"}catch(i){o=f(i)}return{present:!0,adapter:!0,device:r,error:o,vendor:n.vendor||void 0,architecture:n.architecture||void 0,description:n.description||void 0,maxTextureDimension2D:e.limits?.maxTextureDimension2D,features:e.features?.size}}catch(e){return{present:!0,adapter:!1,device:!1,error:f(e)}}}var N=`#version 300 es
3
+ layout(location = 0) in vec3 position;
4
+ layout(location = 1) in vec3 normal;
5
+ layout(location = 2) in vec4 offset;
6
+ uniform float time;
7
+ uniform float aspect;
8
+ out vec3 shade;
9
+ mat3 rotation(float a, float b) {
10
+ float ca = cos(a), sa = sin(a), cb = cos(b), sb = sin(b);
11
+ return mat3(cb, 0.0, -sb, sa * sb, ca, sa * cb, ca * sb, -sa, ca * cb);
12
+ }
13
+ void main() {
14
+ mat3 r = rotation(time + offset.w, time * 0.7 + offset.w * 2.0);
15
+ vec3 p = r * position * 0.012 + offset.xyz;
16
+ vec3 n = r * normal;
17
+ shade = vec3(0.35, 0.55, 0.95) * (0.35 + 0.65 * max(dot(n, normalize(vec3(0.4, 0.8, 0.6))), 0.0));
18
+ gl_Position = vec4(p.x / aspect, p.y, p.z * 0.5, 1.0);
19
+ }`,W=`#version 300 es
20
+ precision mediump float;
21
+ in vec3 shade;
22
+ out vec4 color;
23
+ void main() { color = vec4(shade, 1.0); }`;function U(){let t=[[[1,0,0],[0,1,0],[0,0,1]],[[-1,0,0],[0,0,1],[0,1,0]],[[0,1,0],[0,0,1],[1,0,0]],[[0,-1,0],[1,0,0],[0,0,1]],[[0,0,1],[1,0,0],[0,1,0]],[[0,0,-1],[0,1,0],[1,0,0]]],e=[];for(let[n,r,o]of t){let i=(c,s)=>[0,1,2].map((p)=>n[p]+c*r[p]+s*o[p]),a=[i(-1,-1),i(1,-1),i(1,1),i(-1,-1),i(1,1),i(-1,1)];for(let c of a)e.push(...c,...n)}return new Float32Array(e)}function V(t){let e=d.getContext("webgl2",{antialias:!0,powerPreference:"high-performance"});if(!e)return null;let n=(m,v)=>{let u=e.createShader(m);if(e.shaderSource(u,v),e.compileShader(u),!e.getShaderParameter(u,e.COMPILE_STATUS))throw Error(e.getShaderInfoLog(u)||"shader compile failed");return u},r=e.createProgram();if(e.attachShader(r,n(e.VERTEX_SHADER,N)),e.attachShader(r,n(e.FRAGMENT_SHADER,W)),e.linkProgram(r),!e.getProgramParameter(r,e.LINK_STATUS))throw Error(e.getProgramInfoLog(r)||"program link failed");let o=e.createVertexArray();e.bindVertexArray(o);let i=U();e.bindBuffer(e.ARRAY_BUFFER,e.createBuffer()),e.bufferData(e.ARRAY_BUFFER,i,e.STATIC_DRAW),e.enableVertexAttribArray(0),e.vertexAttribPointer(0,3,e.FLOAT,!1,24,0),e.enableVertexAttribArray(1),e.vertexAttribPointer(1,3,e.FLOAT,!1,24,12);let a=new Float32Array(t*4);for(let m=0;m<t;m++)a[m*4]=Math.random()*2-1,a[m*4+1]=Math.random()*2-1,a[m*4+2]=Math.random()*2-1,a[m*4+3]=Math.random()*Math.PI*2;e.bindBuffer(e.ARRAY_BUFFER,e.createBuffer()),e.bufferData(e.ARRAY_BUFFER,a,e.STATIC_DRAW),e.enableVertexAttribArray(2),e.vertexAttribPointer(2,4,e.FLOAT,!1,16,0),e.vertexAttribDivisor(2,1),e.useProgram(r),e.enable(e.DEPTH_TEST);let c=e.getUniformLocation(r,"time"),s=e.getUniformLocation(r,"aspect"),p=i.length/6;return{triangles:p/3*t,draw(m){let v=Math.floor(d.clientWidth*devicePixelRatio),u=Math.floor(d.clientHeight*devicePixelRatio);if(d.width!==v||d.height!==u)d.width=v,d.height=u;e.viewport(0,0,v,u),e.clearColor(0.04,0.05,0.08,1),e.clear(e.COLOR_BUFFER_BIT|e.DEPTH_BUFFER_BIT),e.uniform1f(c,m/1000),e.uniform1f(s,v/Math.max(1,u)),e.drawArraysInstanced(e.TRIANGLES,0,p,t)}}}function S(){let t=d.getContext("2d");return{triangles:0,draw(e){d.width=d.clientWidth,d.height=d.clientHeight,t.fillStyle="#0a0d14",t.fillRect(0,0,d.width,d.height),t.fillStyle="#5b8cf2";for(let n=0;n<2000;n++)t.fillRect((n*37+e/4)%d.width,n*53%d.height,6,6)}}}function H(t){let e=performance.now()+t;while(performance.now()<e);}async function R(t,e){let n=[],r=document.visibilityState!=="visible",o=()=>{if(document.visibilityState!=="visible")r=!0};document.addEventListener("visibilitychange",o),await T();let i=performance.now();return await new Promise((a)=>{let c=(s)=>{if(n.push(s),e?.(s),performance.now()-i<t)requestAnimationFrame(c);else a()};requestAnimationFrame(c)}),document.removeEventListener("visibilitychange",o),{timestamps:n,hidden:r}}var h=null;function E(t){return{state:t.state,baseLatency:typeof t.baseLatency==="number"?t.baseLatency:null,outputLatency:typeof t.outputLatency==="number"?t.outputLatency:null,sampleRate:t.sampleRate}}async function j(){let t=window.AudioContext;if(!t)return{present:!1};try{h=new t({latencyHint:"interactive"});let e=E(h);return await P(h.resume().catch(()=>{}),1500,void 0),await w(300),{present:!0,initial:e,afterUngesturedResume:E(h)}}catch(e){return{present:!0,error:f(e)}}}function F(t){let{requestFullscreen:e,webkitRequestFullscreen:n}=t;if(!e&&!n)return Promise.resolve({outcome:"no-api"});return new Promise((r)=>{let o=!1,i=(s)=>{if(o)return;o=!0,r(s)},a=()=>i({outcome:"entered",detail:"fullscreenchange"}),c=()=>i({outcome:"rejected",detail:"fullscreenerror"});document.addEventListener("fullscreenchange",a,{once:!0}),document.addEventListener("webkitfullscreenchange",a,{once:!0}),document.addEventListener("fullscreenerror",c,{once:!0}),document.addEventListener("webkitfullscreenerror",c,{once:!0});try{if(e)e.call(t).then(()=>i({outcome:"entered",detail:"promise resolved"}),(s)=>i({outcome:"rejected",detail:f(s)}));else n.call(t)}catch(s){i({outcome:"rejected",detail:f(s)})}setTimeout(()=>i({outcome:"timeout"}),3000)})}async function x(){let t=document.exitFullscreen;if(document.fullscreenElement&&t)await t.call(document).catch(()=>{})}function M(t){if(typeof t.requestPointerLock!=="function")return Promise.resolve({outcome:"no-api"});return new Promise((e)=>{let n=!1,r=(a)=>{if(n)return;n=!0,document.removeEventListener("pointerlockchange",o),document.removeEventListener("pointerlockerror",i),e(a)},o=()=>{if(document.pointerLockElement===t)r({outcome:"entered",detail:"pointerlockchange"})},i=()=>r({outcome:"rejected",detail:"pointerlockerror"});document.addEventListener("pointerlockchange",o),document.addEventListener("pointerlockerror",i);try{t.requestPointerLock()?.then?.(()=>o(),(c)=>r({outcome:"rejected",detail:f(c)}))}catch(a){r({outcome:"rejected",detail:f(a)})}setTimeout(()=>r({outcome:"timeout"}),3000)})}async function z(){let t=window.craft,e=t?.window?.setFullscreen;if(typeof e!=="function")return{outcome:"no-api"};let n=()=>`${innerWidth}x${innerHeight}`,r=n();try{await P(Promise.resolve(e.call(t.window,!0)),3000,void 0),await w(2000);let o=n(),i=o!==r&&innerWidth>=screen.width-2;return await P(Promise.resolve(e.call(t.window,!1)),3000,void 0),await w(1500),{outcome:i?"entered":"rejected",detail:i?"viewport grew to the screen":"viewport did not change",sizeBefore:r,sizeAfter:o}}catch(o){return{outcome:"rejected",detail:f(o),sizeBefore:r}}}var b={pointerdown:[],pointermove:[],keydown:[],eventTimingSupported:typeof PerformanceObserver<"u"&&(PerformanceObserver.supportedEntryTypes??[]).includes("event"),eventTimingDurations:[]},I=!1,L=!1;function y(t,e){if(!t.isTrusted)return;I=!0;let n=t.timeStamp;requestAnimationFrame(()=>{let r=performance.now()-n,o=new MessageChannel;o.port1.onmessage=()=>{e.push({toFrameMs:r,toPostFrameMs:performance.now()-n}),L=!1},o.port2.postMessage(0)})}function X(){let t=(o)=>y(o,b.pointerdown),e=(o)=>y(o,b.keydown),n=(o)=>{if(L||!o.isTrusted)return;L=!0,y(o,b.pointermove)};addEventListener("pointerdown",t,!0),addEventListener("keydown",e,!0),addEventListener("pointermove",n,!0);let r=null;if(b.eventTimingSupported){r=new PerformanceObserver((o)=>{for(let i of o.getEntries())b.eventTimingDurations.push(i.duration)});try{r.observe({type:"event",buffered:!0,durationThreshold:16})}catch{r=null}}return()=>{removeEventListener("pointerdown",t,!0),removeEventListener("keydown",e,!0),removeEventListener("pointermove",n,!0),r?.disconnect()}}async function Y(t){if(l.interactionWindowMs<=0){t.interaction.skipped=!0;return}k.hidden=!1;let e=document.getElementById("fullscreen-button"),n=document.getElementById("lock-button"),r=document.getElementById("done-button"),o=document.getElementById("countdown"),i=!1,a=!1,c=()=>{},s=new Promise((u)=>c=u),p=()=>{if(i&&a&&b.keydown.length>=5)c()};e.addEventListener("click",async(u)=>{if(!u.isTrusted||i)return;i=!0,e.setAttribute("disabled","");let B=h?.resume().catch(()=>{});if(t.fullscreen.gestured=await F(document.documentElement),await B,await w(300),h)t.audio.afterGesturedResume=E(h);await w(700),await x(),p()}),n.addEventListener("click",async(u)=>{if(!u.isTrusted||a)return;if(a=!0,n.setAttribute("disabled",""),t.pointerLock.gestured=await M(d),await w(500),document.pointerLockElement)document.exitPointerLock();p()}),addEventListener("keydown",()=>setTimeout(p,100)),r.addEventListener("click",(u)=>u.isTrusted&&c());let m=performance.now()+l.interactionWindowMs,v=setInterval(()=>{let u=Math.max(0,Math.ceil((m-performance.now())/1000));o.textContent=`${u}s left, ${b.keydown.length} key presses so far`},250);await Promise.race([s,w(l.interactionWindowMs)]),clearInterval(v),await w(200),k.hidden=!0}async function K(){let t={outcome:"not-attempted"},e=window.craft,n={config:l,userAgent:navigator.userAgent,devicePixelRatio,screen:{width:screen.width,height:screen.height},isSecureContext,timerResolutionMs:q(),eventTimeStampComparable:_(),bridge:{present:Boolean(e),setFullscreen:typeof e?.window?.setFullscreen==="function"},webgl2:{ok:!1},webgpu:{present:!1,adapter:!1,device:!1},frames:{idle:[],load:[],triangles:0,hiddenDuringSampling:!1,focused:!1},input:b,audio:{present:!1},gamepad:{present:typeof navigator.getGamepads==="function",connected:0,ids:[],connectedEventSeen:!1},fullscreen:{elementApi:typeof document.documentElement.requestFullscreen==="function",webkitElementApi:typeof document.documentElement.webkitRequestFullscreen==="function",enabled:typeof document.fullscreenEnabled==="boolean"?document.fullscreenEnabled:null,ungestured:t,gestured:t,nativeBridge:t},pointerLock:{api:typeof d.requestPointerLock==="function",ungestured:t,gestured:t},interaction:{windowMs:l.interactionWindowMs,sawRealInput:!1,skipped:!1},errors:A};addEventListener("gamepadconnected",()=>{n.gamepad.connectedEventSeen=!0});let r=X();g("Probing WebGL2 and WebGPU"),n.webgl2=O(),n.webgpu=await G(),g("Probing audio"),n.audio=await j(),g("Requesting fullscreen and pointer lock without a gesture (expected to be refused)"),n.fullscreen.ungestured=await F(document.documentElement),await x(),n.pointerLock.ungestured=await M(d),g(`Frame pacing: idle for ${l.idleMs/1000}s`);let o=await R(l.idleMs),i;try{i=V(l.instances)??S()}catch(s){A.push(`load scene: ${f(s)}`),i=S()}for(let s=0;s<10;s++)i.draw(await T());g(`Frame pacing: ${i.triangles.toLocaleString()} triangles + ${l.cpuLoadMsPerFrame} ms CPU per frame for ${l.loadMs/1000}s`);let a=await R(l.loadMs,(s)=>{H(l.cpuLoadMsPerFrame),i.draw(s)});n.frames={idle:o.timestamps,load:a.timestamps,triangles:i.triangles,hiddenDuringSampling:o.hidden||a.hidden,focused:document.hasFocus()},g("Waiting for real input (see the panel), or for the window to time out"),await Y(n),r();let c=typeof navigator.getGamepads==="function"?[...navigator.getGamepads()].filter(Boolean):[];if(n.gamepad.connected=c.length,n.gamepad.ids=c.map((s)=>s.id),l.tryNativeFullscreen&&n.bridge.setFullscreen)g("Trying Craft's native window fullscreen through window.craft"),n.fullscreen.nativeBridge=await z();n.interaction.sawRealInput=I,await h?.close().catch(()=>{}),g("Sending results"),await fetch("/results",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(n)}),g("Done. Results were sent to the runner; this window can close.")}K().catch((t)=>{let e=f(t);g(`Probe failed: ${e}`),fetch("/failed",{method:"POST",body:e}).catch(()=>{})});
@@ -0,0 +1,113 @@
1
+ export declare const DEFAULT_PROBE_PAGE_CONFIG: ProbePageConfig;
2
+ /**
3
+ * What the probe page posts back to its runner (stacksjs/stacks#877).
4
+ *
5
+ * Raw observations only. The page does no statistics and makes no judgement:
6
+ * `buildProbeRecord` in `record.ts` turns this into a published record, which is
7
+ * where the arithmetic and the status rules live and are unit-tested.
8
+ */
9
+ /** Configuration the runner embeds in the page. */
10
+ export declare interface ProbePageConfig {
11
+ idleMs: number
12
+ loadMs: number
13
+ cpuLoadMsPerFrame: number
14
+ instances: number
15
+ interactionWindowMs: number
16
+ tryNativeFullscreen: boolean
17
+ }
18
+ /** The result of asking for fullscreen or pointer lock. */
19
+ export declare interface RequestOutcome {
20
+ outcome: 'entered' | 'rejected' | 'no-api' | 'timeout' | 'not-attempted'
21
+ detail?: string
22
+ }
23
+ /** One input event, timed to the frame that could first show it. */
24
+ export declare interface InputSample {
25
+ toFrameMs: number
26
+ toPostFrameMs: number
27
+ }
28
+ export declare interface AudioSnapshot {
29
+ state: string
30
+ baseLatency: number | null
31
+ outputLatency: number | null
32
+ sampleRate: number
33
+ }
34
+ export declare interface ProbePagePayload {
35
+ config: ProbePageConfig
36
+ userAgent: string
37
+ devicePixelRatio: number
38
+ screen: { width: number, height: number }
39
+ isSecureContext: boolean
40
+ timerResolutionMs: number
41
+ eventTimeStampComparable: boolean
42
+ bridge: { present: boolean, setFullscreen: boolean }
43
+ webgl2: {
44
+ ok: boolean
45
+ error?: string
46
+ version?: string
47
+ shadingLanguageVersion?: string
48
+ vendor?: string
49
+ renderer?: string
50
+ unmaskedVendor?: string
51
+ unmaskedRenderer?: string
52
+ maxTextureSize?: number
53
+ maxSamples?: number
54
+ extensions?: number
55
+ }
56
+ webgpu: {
57
+ present: boolean
58
+ adapter: boolean
59
+ device: boolean
60
+ error?: string
61
+ vendor?: string
62
+ architecture?: string
63
+ description?: string
64
+ maxTextureDimension2D?: number
65
+ features?: number
66
+ }
67
+ frames: {
68
+ idle: number[]
69
+ load: number[]
70
+ triangles: number
71
+ hiddenDuringSampling: boolean
72
+ focused: boolean
73
+ }
74
+ input: {
75
+ pointerdown: InputSample[]
76
+ pointermove: InputSample[]
77
+ keydown: InputSample[]
78
+ eventTimingSupported: boolean
79
+ eventTimingDurations: number[]
80
+ }
81
+ audio: {
82
+ present: boolean
83
+ error?: string
84
+ initial?: AudioSnapshot
85
+ afterUngesturedResume?: AudioSnapshot
86
+ afterGesturedResume?: AudioSnapshot
87
+ }
88
+ gamepad: {
89
+ present: boolean
90
+ connected: number
91
+ ids: string[]
92
+ connectedEventSeen: boolean
93
+ }
94
+ fullscreen: {
95
+ elementApi: boolean
96
+ webkitElementApi: boolean
97
+ enabled: boolean | null
98
+ ungestured: RequestOutcome
99
+ gestured: RequestOutcome
100
+ nativeBridge: RequestOutcome & { sizeBefore?: string, sizeAfter?: string }
101
+ }
102
+ pointerLock: {
103
+ api: boolean
104
+ ungestured: RequestOutcome
105
+ gestured: RequestOutcome
106
+ }
107
+ interaction: {
108
+ windowMs: number
109
+ sawRealInput: boolean
110
+ skipped: boolean
111
+ }
112
+ errors: string[]
113
+ }
@@ -0,0 +1,19 @@
1
+ import type { InteractiveProbeRecord } from './schema';
2
+ import type { ProbePagePayload } from './payload';
3
+ /** The browser and version a user agent string names, best effort. */
4
+ export declare function browserFromUserAgent(userAgent: string): { name: string, version: string };
5
+ /** Combine the page's observations with the runner's host facts into a record. */
6
+ export declare function buildProbeRecord(payload: ProbePagePayload, facts: ProbeHostFacts): InteractiveProbeRecord;
7
+ /** Fewer trusted input events than this is reported, not summarised. */
8
+ export declare const MIN_INPUT_SAMPLES: 5;
9
+ /** What the runner knows about the host that the page cannot see. */
10
+ export declare interface ProbeHostFacts {
11
+ measuredAt: string
12
+ host: { kind: 'craft' | 'browser', name: string, version: string }
13
+ os: InteractiveProbeRecord['os']
14
+ machine: {
15
+ model: string
16
+ cpu: string
17
+ display: { name: string, resolution: string, refreshHz: number | null }
18
+ }
19
+ }
@@ -0,0 +1,7 @@
1
+ import type { InteractiveProbeRecord } from './schema';
2
+ /** The file a record is stored under. Version is deliberately not part of it. */
3
+ export declare function probeRecordFileName(record: Pick<InteractiveProbeRecord, 'host' | 'os'>): string;
4
+ /** Every record in `dir`, validated, in file-name order. Throws naming the bad file. */
5
+ export declare function loadProbeRecords(dir?: string): InteractiveProbeRecord[];
6
+ /** The checked-in records, beside this module in the framework repository. */
7
+ export declare const PROBE_RESULTS_DIR: string;
@@ -0,0 +1,13 @@
1
+ import type { InteractiveProbeRecord, ProbeName, ProbeResult } from './schema';
2
+ /** The column heading for one record: the host and its version. */
3
+ export declare function recordLabel(record: InteractiveProbeRecord): string;
4
+ /** Records in the order the page shows them: Craft first, then browsers, each by name. */
5
+ export declare function orderRecords(records: readonly InteractiveProbeRecord[]): InteractiveProbeRecord[];
6
+ /** The one figure worth putting in a table cell, when the probe has one. */
7
+ export declare function headline(name: ProbeName, probe: ProbeResult): string | null;
8
+ /** The generated block, markers included. */
9
+ export declare function renderInteractiveCapabilities(records: readonly InteractiveProbeRecord[]): string;
10
+ export declare const INTERACTIVE_BEGIN: '<!-- interactive-capability:begin -->';
11
+ export declare const INTERACTIVE_END: '<!-- interactive-capability:end -->';
12
+ /** The command that produces a record, named once so the page and the CLI agree. */
13
+ export declare const PROBE_COMMAND: 'buddy desktop:probe';
@@ -0,0 +1,27 @@
1
+ import type { InteractiveProbeRecord } from './schema';
2
+ import type { ProbeHostFacts } from './record';
3
+ import type { ProbePageConfig } from './payload';
4
+ /** `craft version 0.0.105` (the first line of `craft --version`) to `0.0.105`. */
5
+ export declare function parseCraftVersion(output: string | null): string | null;
6
+ /**
7
+ * The main display from `system_profiler SPDisplaysDataType -json`, without
8
+ * the serial number and vendor identifiers it also reports.
9
+ */
10
+ export declare function mainDisplayFromProfiler(json: string | null): DisplayFacts | null;
11
+ /** Everything about the host the page cannot see. */
12
+ export declare function collectHostFacts(mode: 'craft' | 'browser', craftVersion: string | null, userAgent: string): ProbeHostFacts;
13
+ /** Serve the page, run it, and return the validated record. */
14
+ export declare function runProbe(options: ProbeRunOptions): Promise<InteractiveProbeRecord>;
15
+ /** A plain-text summary, one line per probe. */
16
+ export declare function summarize(record: InteractiveProbeRecord): string;
17
+ export declare function run(): Promise<void>;
18
+ export declare interface ProbeRunOptions {
19
+ mode: 'craft' | 'browser'
20
+ craftBin?: string
21
+ open?: boolean | string
22
+ page?: Partial<ProbePageConfig>
23
+ timeoutMs?: number
24
+ port?: number
25
+ log?: (line: string) => void
26
+ }
27
+ declare interface DisplayFacts { name: string, resolution: string, refreshHz: number | null }
@@ -0,0 +1,66 @@
1
+ // @bun
2
+ var O=(e,t,n)=>()=>{if(e)try{t=e(e=0)}catch(r){n=[r]}if(n)throw n[0];return t};function Ke(e,t,n={}){if(!t.trim())throw Error("Invite token is required");let r=new URL("/invite",/^https?:\/\//.test(e)?e:`https://${e}`);if(r.searchParams.set("token",t),n.email)r.searchParams.set("email",n.email);if(n.team)r.searchParams.set("team",n.team);if(n.role)r.searchParams.set("role",n.role);if(n.expiresAt){let s=n.expiresAt instanceof Date?n.expiresAt:new Date(n.expiresAt);if(Number.isNaN(s.valueOf()))throw Error("Invite expiration must be a valid date");r.searchParams.set("expires",s.toISOString())}return r.toString()}async function Zt(e,t){return Promise.all(e.map(async(n)=>{let r=typeof n==="string"?{email:n}:n;try{Je(r.email);let s=await t.createToken(r),o=Ke(t.baseUrl,s,{email:r.email,team:r.team,role:r.role,expiresAt:t.expiresAt});return await t.send({recipient:r,url:o}),{recipient:r,url:o,delivered:!0}}catch(s){return{recipient:r,url:"",delivered:!1,error:s instanceof Error?s.message:String(s)}}}))}function Je(e){if(!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e))throw Error(`Invalid invite email: ${e}`)}import{createHash as Ze,createPublicKey as Ye,verify as Xe}from"crypto";import{mkdir as Qe,rename as et,writeFile as tt}from"fs/promises";import{dirname as nt}from"path";async function tn(e){ce(e.manifestUrl);let t=await(e.fetcher||fetch)(e.manifestUrl,{headers:{Accept:"application/json"}});if(!t.ok)throw Error(`Desktop update check failed with HTTP ${t.status}`);let n=ae(await t.json());return ue(n,e.trustedKeys),rt(n.version,e.currentVersion)>0?n:null}async function nn(e,t,n=fetch,r){if(ae(e),!r)throw Error("Desktop update staging requires trusted signing keys");ue(e,r),ce(e.url);let s=await n(e.url);if(!s.ok)throw Error(`Desktop update download failed with HTTP ${s.status}`);let o=new Uint8Array(await s.arrayBuffer());if(o.byteLength!==e.size)throw Error(`Desktop update size mismatch: expected ${e.size}, received ${o.byteLength}`);if(Ze("sha256").update(o).digest("hex")!==e.sha256.toLowerCase())throw Error("Desktop update checksum mismatch");await Qe(nt(t),{recursive:!0});let i=`${t}.download-${crypto.randomUUID()}`;return await tt(i,o,{mode:448}),await et(i,t),{manifest:e,path:t,bytes:o.byteLength}}function rt(e,t){let n=_(e),r=_(t);for(let s=0;s<3;s++){let o=n[s]-r[s];if(o!==0)return Math.sign(o)}return 0}function ae(e){if(!e||typeof e!=="object")throw Error("Desktop update manifest must be an object");let t=e;if(_(t.version||""),typeof t.url!=="string")throw Error("Desktop update manifest requires a URL");if(typeof t.sha256!=="string"||!/^[a-f0-9]{64}$/i.test(t.sha256))throw Error("Desktop update manifest requires a SHA-256 checksum");if(!Number.isInteger(t.size)||t.size<0)throw Error("Desktop update manifest requires a non-negative integer size");if(typeof t.sourceRevision!=="string"||!/^[0-9a-f]{40}$/.test(t.sourceRevision))throw Error("Desktop update manifest requires a full source revision");if(typeof t.platform!=="string"||!["darwin","linux","win32"].includes(t.platform))throw Error("Desktop update manifest requires a supported platform identifier");if(typeof t.architecture!=="string"||!/^[a-z0-9_-]+$/.test(t.architecture))throw Error("Desktop update manifest requires an architecture");if(typeof t.signingKeyId!=="string"||!/^[a-zA-Z0-9._-]+$/.test(t.signingKeyId))throw Error("Desktop update manifest requires a signing key ID");if(typeof t.signature!=="string"||!/^[A-Za-z0-9_-]+$/.test(t.signature))throw Error("Desktop update manifest requires an Ed25519 signature");return t}function st(e){return Buffer.from(JSON.stringify({version:e.version,url:e.url,sha256:e.sha256.toLowerCase(),size:e.size,sourceRevision:e.sourceRevision,platform:e.platform,architecture:e.architecture,signingKeyId:e.signingKeyId,publishedAt:e.publishedAt||null,notes:e.notes||null}),"utf8")}function ue(e,t){let n=t[e.signingKeyId];if(!n)throw Error(`Desktop update signing key is not trusted: ${e.signingKeyId}`);let r="ed25519-public:";if(!n.startsWith(r))throw Error("Desktop update signing key has an invalid encoding");try{let s=Ye({key:Buffer.from(n.slice(r.length),"base64url"),format:"der",type:"spki"}),o=Buffer.from(e.signature,"base64url");if(s.asymmetricKeyType!=="ed25519"||o.length!==64||!Xe(null,st(e),s,o))throw Error("invalid")}catch{throw Error("Desktop update manifest signature verification failed")}}function _(e){let t=e.match(/^v?(\d+)\.(\d+)\.(\d+)(?:-[0-9A-Za-z.-]+)?$/);if(!t)throw Error(`Invalid semantic version: ${e}`);return[Number(t[1]),Number(t[2]),Number(t[3])]}function ce(e){let t=new URL(e),n=["localhost","127.0.0.1","::1"].includes(t.hostname);if(t.protocol!=="https:"&&!(t.protocol==="http:"&&n))throw Error("Desktop updates require HTTPS outside loopback development")}var le=()=>{};function it(e=process.platform,t=process.arch){return ot.find((n)=>n.platform===e&&n.architecture===t)}function sn(e,t=process.platform,n=process.arch){let r=it(t,n);if(!r)throw Error(`Desktop target ${t}/${n} is unsupported`);if(e==="stable"&&(r.status!=="stable"||r.signing!=="enforced"||r.platform==="darwin"&&r.notarization!=="enforced"||!r.installLaunchEvidence||!r.updateRollbackEvidence))throw Error(`Desktop target ${t}/${n} cannot be released as stable; complete stacksjs/stacks#2059, #2062, and #2063 first`);return r}var ot;var de=O(()=>{ot=[{platform:"darwin",architecture:"arm64",status:"experimental",osVersions:["macOS 15 runner"],packageFormat:"DMG + PKG",signing:"pending",notarization:"pending",installLaunchEvidence:"https://github.com/stacksjs/stacks/actions/runs/29890804159",updateRollbackEvidence:"https://github.com/stacksjs/stacks/actions/runs/29890804159",packagingEvidence:"https://github.com/stacksjs/protocol/blob/main/evidence/craft.json",blockingIssues:["https://github.com/stacksjs/stacks/issues/2062"],limitations:["Lifecycle fixtures are unsigned while platform identities remain unprovisioned.","Lifecycle evidence was produced against Craft v0.0.48 built from source (bf75807). Craft now publishes a prebuilt craft-darwin-arm64.zip with every release, and no Stacks lifecycle run has re-verified a prebuilt release yet."]},{platform:"darwin",architecture:"x64",status:"experimental",osVersions:["macOS 15 Intel runner"],packageFormat:"DMG + PKG",signing:"pending",notarization:"pending",installLaunchEvidence:"https://github.com/stacksjs/stacks/actions/runs/29890804159",updateRollbackEvidence:"https://github.com/stacksjs/stacks/actions/runs/29890804159",packagingEvidence:"https://github.com/stacksjs/protocol/blob/main/evidence/craft.json",blockingIssues:["https://github.com/stacksjs/stacks/issues/2062"],limitations:["Lifecycle fixtures are unsigned while platform identities remain unprovisioned.","Lifecycle evidence was produced against Craft v0.0.48 built from source (bf75807). Craft now publishes a prebuilt craft-darwin-x64.zip with every release, and no Stacks lifecycle run has re-verified a prebuilt release yet."]},{platform:"linux",architecture:"x64",status:"experimental",osVersions:["Ubuntu 24.04 runner"],packageFormat:"DEB",signing:"pending",notarization:"not-applicable",installLaunchEvidence:"https://github.com/stacksjs/stacks/actions/runs/29890804159",updateRollbackEvidence:"https://github.com/stacksjs/stacks/actions/runs/29890804159",packagingEvidence:"https://github.com/stacksjs/protocol/blob/main/evidence/craft.json",blockingIssues:["https://github.com/stacksjs/stacks/issues/2062"],limitations:["The lifecycle fixture is unsigned and no package repository support policy is published.","Lifecycle evidence was produced against Craft v0.0.48 built from source (bf75807). Craft now publishes a prebuilt craft-linux-x64.zip with every release, and no Stacks lifecycle run has re-verified a prebuilt release yet."]},{platform:"win32",architecture:"x64",status:"experimental",osVersions:["Windows Server 2025 runner"],packageFormat:"MSI + ZIP",signing:"pending",notarization:"not-applicable",installLaunchEvidence:"https://github.com/stacksjs/stacks/actions/runs/29890804159",updateRollbackEvidence:"https://github.com/stacksjs/stacks/actions/runs/29890804159",packagingEvidence:"https://github.com/stacksjs/protocol/blob/main/evidence/craft.json",blockingIssues:["https://github.com/stacksjs/stacks/issues/2062"],limitations:["The lifecycle fixture is not Authenticode-signed.","Lifecycle evidence was produced against Craft v0.0.48 built from source (bf75807). Craft now publishes a prebuilt craft-windows-x64.zip with every release, and no Stacks lifecycle run has re-verified a prebuilt release yet."]}]});import{closeSync as me,existsSync as z,openSync as fe,readSync as ge,statSync as at}from"fs";import{dirname as ut,join as F}from"path";import E from"process";import{fileURLToPath as ct}from"url";import{craftBinaryNotFoundMessage as lt,resolveCraftBinary as dt}from"craft-native";function he(e=E.env.CRAFT_BIN){return dt(e)}function be(e=E.env.CRAFT_BIN,t=(n)=>Bun.which(n)){let n=he(e);if(n!=="craft")return n;let r=t(n);if(!r)throw Error(lt(n));return r}function pn(e=E.cwd()){return z(F(e,we))}function mn(e){return["build","--compile",...e.minify===!1?[]:["--minify"],"--no-compile-autoload-bunfig","--no-compile-autoload-dotenv",e.entry,"--outfile",e.outfile]}function fn(e=E.cwd()){let t=F(e,we);if(z(t))return t;let n=ut(ct(import.meta.url)),s=[F(n,"launcher.ts"),F(n,"launcher.js")].find((o)=>z(o));if(!s)throw Error("Desktop launcher entrypoint not found. Reinstall @stacksjs/desktop - the package must ship dist/launcher.js.");return s}function H(e){return e.replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;").replaceAll("'","&apos;")}function q(e,t=""){if(typeof e==="string")return`<string>${H(e)}</string>`;if(typeof e==="boolean")return e?"<true/>":"<false/>";if(typeof e==="number"){if(!Number.isFinite(e))throw Error(`Unsupported Info.plist number: ${e}`);return Number.isInteger(e)?`<integer>${e}</integer>`:`<real>${e}</real>`}if(Array.isArray(e)){if(e.length===0)return"<array/>";return`<array>
3
+ ${e.map((r)=>`${t} ${q(r,`${t} `)}`).join(`
4
+ `)}
5
+ ${t}</array>`}if(e&&typeof e==="object"){let n=Object.entries(e);if(n.length===0)return"<dict/>";return`<dict>
6
+ ${n.map(([s,o])=>`${t} <key>${H(s)}</key>${q(o,`${t} `)}`).join(`
7
+ `)}
8
+ ${t}</dict>`}throw Error(`Unsupported Info.plist value: ${JSON.stringify(e)??String(e)}`)}function gn(e){let t=Object.keys(e).filter((s)=>pe.has(s)),n=Object.entries(e).filter(([s])=>!pe.has(s));if(n.length===0)return{xml:"",ignored:t};return{xml:`
9
+ ${n.map(([s,o])=>` <key>${H(s)}</key>${q(o," ")}`).join(`
10
+ `)}`,ignored:t}}function pt(e,t={}){if(!Number.isInteger(e)||e<1||e>65535)throw RangeError(`Invalid desktop development port: ${e}`);let n=t.url||E.env.APP_URL;if(!n)return"https://stacks.test";let r=/^https?:\/\//.test(n)?n:`https://${n}`;return new URL(r).toString().replace(/\/$/,"")}function mt(e,t={}){let n=[he(t.craftBin),pt(e,t),"--title",t.title||"Stacks","--width",String(t.width||1400),"--height",String(t.height||900)];if(t.hotReload!==!1)n.push("--hot-reload");if(t.devTools===!1)n.push("--no-devtools");if(t.darkMode)n.push("--dark");if(t.systemTray)n.push("--system-tray");if(t.hideDockIcon)n.push("--hide-dock-icon");if(t.menubarOnly)n.push("--menubar-only");return n}function hn(e,t="stable"){if(!/^[a-z0-9-]+$/i.test(t))throw Error("Update channel may only contain letters, numbers, and hyphens");return new URL(`/desktop/updates/${t}.json`,/^https?:\/\//.test(e)?e:`https://${e}`).toString()}async function ft(e){Bun.spawn(e,{stdin:"inherit",stdout:"inherit",stderr:"inherit"}).unref()}async function bn(e,t={},n=ft){return await n(mt(e,t)),!0}function wt(e){try{let t=fe(e,"r");try{let n=Buffer.alloc(4);if(ge(t,n,0,4,0)<4)return!1;return bt.has(n.readUInt32BE(0))}finally{me(t)}}catch{return!1}}function wn(e){return wt(e)?"MacOS":"Resources"}function yn(e){return["codesign","--force",e.identity==="-"?"--timestamp=none":"--timestamp","--options","runtime",...e.entitlements?["--entitlements",e.entitlements]:[],"--sign",e.identity,e.target]}function kn(e,t=yt){let n=t(e);if(!n||n.size<W)return!1;return n.tail.includes(gt)}function yt(e){try{let{size:t}=at(e);if(t<W)return{size:t,tail:""};let n=fe(e,"r");try{let r=Math.min(ht,t),s=Buffer.alloc(r);return ge(n,s,0,r,t-r),{size:t,tail:s.toString("latin1")}}finally{me(n)}}catch{return null}}function vn(e){if(e.length<2)return null;let t=[...e].sort((o,a)=>a.bytes-o.bytes),r=t.slice(1).length*W,s=t.map((o)=>`${o.name} (${(o.bytes/1024/1024).toFixed(1)} MB)`).join(", ");return`This bundle ships ${e.length} Bun-compiled executables: ${s}. Each embeds a full copy of the Bun runtime, so at least ${(r/1024/1024).toFixed(0)} MB of the bundle is the same runtime repeated. `+"Compiling one binary that dispatches on a subcommand \u2014 `MyApp agent`, `MyApp worker` \u2014 keeps the helpers as separate processes and pays for the runtime once."}var we="app/Desktop/launcher.ts",pe,gt="---- Bun!",ht=8388608,W=41943040,bt;var ye=O(()=>{le();de();pe=new Set(["CFBundleName","CFBundleDisplayName","CFBundleIdentifier","CFBundleVersion","CFBundleShortVersionString","CFBundleExecutable","CFBundlePackageType","CFBundleInfoDictionaryVersion","CFBundleIconFile"]);bt=new Set([4277009102,3472551422,4277009103,3489328638,3405691582,3199925962])});import{existsSync as Re,mkdirSync as kt,writeFileSync as ke}from"fs";import ve from"os";import{dirname as vt,join as L,relative as xe,resolve as xt}from"path";import l from"process";import{fileURLToPath as Pt}from"url";function Se(e){return e.replaceAll("</script","<\\/script")}function V(e,t){return`<!doctype html>
11
+ <html lang="en">
12
+ <head>
13
+ <meta charset="utf-8">
14
+ <meta name="viewport" content="width=device-width, initial-scale=1">
15
+ <title>Stacks interactive probe</title>
16
+ <style>
17
+ :root { color-scheme: dark; --bg: #0a0d14; --panel: #141a26; --text: #e6e9f0; --muted: #9aa3b5; --accent: #5b8cf2; }
18
+ html, body { margin: 0; height: 100%; background: var(--bg); color: var(--text); font: 14px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
19
+ #scene { position: fixed; inset: 0; width: 100%; height: 100%; display: block; }
20
+ #hud { position: fixed; left: 16px; right: 16px; top: 16px; padding: 12px 16px; background: color-mix(in srgb, var(--panel) 88%, transparent); border-radius: 10px; }
21
+ #hud h1 { margin: 0 0 4px; font-size: 15px; font-weight: 600; }
22
+ #status { color: var(--muted); margin: 0; }
23
+ #interactive { position: fixed; left: 16px; right: 16px; bottom: 16px; padding: 16px; background: var(--panel); border-radius: 10px; }
24
+ #interactive[hidden] { display: none; }
25
+ #interactive p { margin: 0 0 12px; }
26
+ #interactive ol { margin: 0 0 12px; padding-left: 20px; color: var(--muted); }
27
+ button { font: inherit; padding: 8px 14px; margin-right: 8px; border-radius: 8px; border: 1px solid var(--accent); background: transparent; color: var(--text); cursor: pointer; }
28
+ button[disabled] { opacity: 0.4; cursor: default; }
29
+ #countdown { color: var(--muted); margin-top: 10px; }
30
+ </style>
31
+ </head>
32
+ <body>
33
+ <canvas id="scene"></canvas>
34
+ <div id="hud">
35
+ <h1>Stacks interactive-content probe</h1>
36
+ <p id="status">Starting</p>
37
+ </div>
38
+ <div id="interactive" hidden>
39
+ <p>These probes need a real person. Synthetic events are ignored, so skipping them records them as needing interaction rather than inventing a number.</p>
40
+ <ol>
41
+ <li>Click <strong>Fullscreen + audio</strong>. It enters fullscreen briefly and resumes audio.</li>
42
+ <li>Click <strong>Pointer lock</strong>. It locks the pointer for half a second.</li>
43
+ <li>Press any key five times, and move the mouse over the window.</li>
44
+ </ol>
45
+ <button id="fullscreen-button" type="button">Fullscreen + audio</button>
46
+ <button id="lock-button" type="button">Pointer lock</button>
47
+ <button id="done-button" type="button">Done</button>
48
+ <div id="countdown"></div>
49
+ </div>
50
+ <script type="application/json" id="probe-config">${JSON.stringify(t).replaceAll("<","\\u003c")}</script>
51
+ <script type="module">${Se(e)}</script>
52
+ </body>
53
+ </html>
54
+ `}var G={idleMs:3000,loadMs:6000,cpuLoadMsPerFrame:6,instances:40000,interactionWindowMs:20000,tryNativeFullscreen:!0};var J=1,K=["available","unavailable","measured","requires-interaction","denied","error"],b=[["webgl2","WebGL2"],["webgpu","WebGPU"],["framePacing","Frame pacing"],["inputLatency","Input latency"],["audioLatency","Audio latency"],["gamepad","Gamepad API"],["fullscreen","Fullscreen"],["pointerLock","Pointer lock"]];function f(e){return typeof e==="object"&&e!==null&&!Array.isArray(e)}function x(e){return typeof e==="string"&&e.trim().length>0}function Ee(e){let t=[];if(!f(e))return["record is not an object"];if(e.schema!==1)t.push(`schema must be 1, got ${JSON.stringify(e.schema)}`);if(!x(e.measuredAt)||Number.isNaN(Date.parse(e.measuredAt)))t.push("measuredAt must be an ISO 8601 timestamp");let n=e.host;if(!f(n))t.push("host is missing");else{if(n.kind!=="craft"&&n.kind!=="browser")t.push(`host.kind must be craft or browser, got ${JSON.stringify(n.kind)}`);for(let i of["name","version","userAgent"])if(!x(n[i]))t.push(`host.${i} is required`);if(n.kind==="craft"&&x(n.version)&&!/^\d+\.\d+\.\d+/.test(n.version))t.push(`host.version must be a Craft release version, got ${JSON.stringify(n.version)}`)}let r=e.os;if(!f(r))t.push("os is missing");else for(let i of["platform","arch","name","version"])if(!x(r[i]))t.push(`os.${i} is required`);let s=e.machine;if(!f(s))t.push("machine is missing");else{for(let u of["model","cpu"])if(!x(s[u]))t.push(`machine.${u} is required`);let i=s.display;if(!f(i))t.push("machine.display is missing");else{if(i.refreshHz!==null&&!(typeof i.refreshHz==="number"&&i.refreshHz>0))t.push("machine.display.refreshHz must be a positive number or null");if(!(typeof i.devicePixelRatio==="number"&&i.devicePixelRatio>0))t.push("machine.display.devicePixelRatio must be a positive number")}}let o=e.run;if(!f(o))t.push("run is missing");else{for(let i of["idleMs","loadMs","cpuLoadMsPerFrame","triangles","interactionWindowMs"])if(!(typeof o[i]==="number"&&o[i]>=0))t.push(`run.${i} must be a non-negative number`);if(typeof o.sawRealInput!=="boolean")t.push("run.sawRealInput must be a boolean")}let a=e.probes;if(!f(a))return t.push("probes is missing"),t;for(let[i]of b){let u=a[i];if(!f(u)){t.push(`probes.${i} is missing`);continue}if(!K.includes(u.status))t.push(`probes.${i}.status must be one of ${K.join(", ")}, got ${JSON.stringify(u.status)}`);if(!x(u.note))t.push(`probes.${i}.note is required: say what was observed, even on success`);if(u.data!==void 0&&!f(u.data))t.push(`probes.${i}.data must be an object when present`);if(u.status==="measured"&&!f(u.data))t.push(`probes.${i} is measured but carries no data`)}for(let i of Object.keys(a))if(!b.some(([u])=>u===i))t.push(`probes.${i} is not a known probe`);return t}function A(e,t="probe record"){let n=Ee(e);if(n.length>0)throw Error(`${t} is invalid:
55
+ - ${n.join(`
56
+ - `)}`);return e}var Me=[24,30,48,50,60,72,75,90,100,120,144,165,240];function y(e,t){if(e.length===0)return null;if(!Number.isFinite(t)||t<0||t>100)throw RangeError(`percentile must be between 0 and 100, got ${t}`);let n=[...e].sort((u,c)=>u-c),r=t/100*(n.length-1),s=Math.floor(r),o=Math.ceil(r),a=n[s],i=n[o];return a+(i-a)*(r-s)}function N(e){let t=[];for(let n=1;n<e.length;n++)t.push(e[n]-e[n-1]);return t}function Ae(e,t=1,n=50){if(t<=0||!Number.isFinite(t))throw RangeError(`bucketMs must be positive, got ${t}`);if(!Number.isInteger(n)||n<1)throw RangeError(`buckets must be a positive integer, got ${n}`);let r=Array.from({length:n},()=>0),s=0;for(let o of e){let a=Math.floor(Math.max(0,o)/t);if(a>=n)s++;else r[a]++}return{bucketMs:t,counts:r,overflow:s}}function Z(e,t=0.03){if(!(e>0))throw RangeError(`period must be positive, got ${e}`);let n=1000/e,r=n,s=Number.POSITIVE_INFINITY;for(let o of Me){let a=Math.abs(n-o)/o;if(a<s)r=o,s=a}return s<=t?r:Math.round(n*100)/100}function Ie(e,t){if(!(t>0))throw RangeError(`period must be positive, got ${t}`);let n=0;for(let r of e)n+=Math.max(0,Math.round(r/t)-1);return n}function I(e,t){if(e.length<3)return null;if(!(t>0))throw RangeError(`targetHz must be positive, got ${t}`);let n=N(e),r=e[e.length-1]-e[0],s=1000/t,o=(a)=>Math.round(a*100)/100;return{frames:e.length,durationMs:o(r),fps:o(r>0?n.length*1000/r:0),p50Ms:o(y(n,50)),p95Ms:o(y(n,95)),p99Ms:o(y(n,99)),maxMs:o(Math.max(...n)),targetPeriodMs:o(s),targetHz:t,droppedFrames:Ie(n,s),longFrames:n.filter((a)=>a>s*1.5).length,histogram:Ae(n)}}function D(e){let t=e.filter((r)=>Number.isFinite(r)&&r>=0);if(t.length===0)return null;let n=(r)=>Math.round(r*100)/100;return{samples:t.length,p50Ms:n(y(t,50)),p95Ms:n(y(t,95)),maxMs:n(Math.max(...t))}}function Y(e){let t=e?.match(/@\s*([\d.]+)\s*Hz/i);if(!t)return null;let n=Number(t[1]);return Number.isFinite(n)&&n>0?n:null}function ee(e){let t=[["Edge",/Edg\/([\d.]+)/],["Firefox",/Firefox\/([\d.]+)/],["Chrome",/Chrome\/([\d.]+)/],["Safari",/Version\/([\d.]+).*Safari\//],["WebKit",/AppleWebKit\/([\d.]+)/]];for(let[n,r]of t){let s=e.match(r);if(s)return{name:n,version:s[1]}}return{name:"unknown",version:"unknown"}}function X(e){return typeof e==="number"?`${Math.round(e*1e4)/10} ms`:"not reported"}function R(e){return e.detail?`${e.outcome} (${e.detail})`:e.outcome}function De(e){let t=e.webgl2;if(!t.ok)return{status:"unavailable",note:`No WebGL2 context: ${t.error??"unknown reason"}.`,data:{...t}};let n=t.unmaskedRenderer||t.renderer;return{status:"available",note:`WebGL2 context created (${t.version}); renderer "${n}", max texture ${t.maxTextureSize}, MSAA up to ${t.maxSamples}x.`,data:{...t}}}function Fe(e){let t=e.webgpu;if(!t.present)return{status:"unavailable",note:"navigator.gpu is undefined.",data:{...t}};if(!t.adapter||!t.device)return{status:"unavailable",note:`navigator.gpu exists but ${t.adapter?"requestDevice()":"requestAdapter()"} failed: ${t.error??"no reason given"}.`,data:{...t}};return{status:"available",note:`Adapter (${[...new Set([t.vendor,t.architecture].filter(Boolean))].join(" ")||"unnamed adapter"}) and device created; maxTextureDimension2D ${t.maxTextureDimension2D??"unknown"}.`,data:{...t}}}function Ce(e,t){let{frames:n,config:r}=e;if(n.idle.length<3||n.load.length<3)return{status:"error",note:`Too few animation frames to measure (${n.idle.length} idle, ${n.load.length} under load).`};let s=Z(y(N(n.idle),50)),o=t.machine.display.refreshHz??s,a=I(n.idle,o),i=I(n.load,o),u=I(n.load,s),c={rafHz:s,displayHz:o,timerResolutionMs:e.timerResolutionMs,triangles:n.triangles,cpuLoadMsPerFrame:r.cpuLoadMsPerFrame,hiddenDuringSampling:n.hiddenDuringSampling,idle:a,load:i,loadAtRafRate:{droppedFrames:u.droppedFrames,longFrames:u.longFrames}};if(n.hiddenDuringSampling)return{status:"error",note:"The page was hidden while sampling, which throttles requestAnimationFrame; these numbers are not representative. Re-run with the window visible.",data:c};let d=s<o-1?`requestAnimationFrame ran at ${s} Hz on a ${o} Hz display, so a game cannot reach the panel's rate. `:`requestAnimationFrame ran at the display's ${o} Hz. `,p=n.triangles>0?`${n.triangles.toLocaleString("en-US")} triangles`:"a 2D canvas fallback";return{status:"measured",note:`${d}Under load (${p} + ${r.cpuLoadMsPerFrame} ms CPU per frame): p50 ${i.p50Ms} ms, p95 ${i.p95Ms} ms, p99 ${i.p99Ms} ms; ${u.droppedFrames} frames dropped at ${s} Hz and ${i.droppedFrames} at the display's ${o} Hz over ${Math.round(i.durationMs/100)/10} s.${e.timerResolutionMs>=0.5?` The page clock is clamped to ${e.timerResolutionMs} ms, so intervals read as whole milliseconds.`:""}`,data:c}}var Q=5;function Le(e){let{input:t}=e,n={eventTimeStampComparable:e.eventTimeStampComparable,eventTimingSupported:t.eventTimingSupported,timerResolutionMs:e.timerResolutionMs};if(!e.eventTimeStampComparable)return{status:"error",note:"event.timeStamp does not share performance.now()'s time origin, so event-to-frame time cannot be computed.",data:n};let r={pointerdown:t.pointerdown,pointermove:t.pointermove,keydown:t.keydown},s=Object.values(r).reduce((i,u)=>i+u.length,0);if(s===0)return{status:"requires-interaction",note:`${e.interaction.skipped?"The interactive window was skipped":`No trusted input reached the page in the ${e.interaction.windowMs/1000} s window`}, and synthetic events are not counted. Observable: event.timeStamp is on the performance.now() clock, timer resolution ${e.timerResolutionMs} ms, Event Timing API ${t.eventTimingSupported?"supported":"not supported"}.`,data:n};let o={...n},a=[];for(let[i,u]of Object.entries(r)){let c=D(u.map((p)=>p.toFrameMs)),d=D(u.map((p)=>p.toPostFrameMs));if(o[i]={toFrame:c,toPostFrame:d},c)a.push(`${i} p50 ${c.p50Ms} ms to next frame (${c.samples} ${c.samples===1?"sample":"samples"})`)}if(o.eventTiming=D(t.eventTimingDurations),s<Q)return{status:"requires-interaction",note:`Only ${s} trusted input ${s===1?"event":"events"} arrived, fewer than the ${Q} needed to summarise. Seen: ${a.join("; ")}. Re-run and use the panel's buttons and keys.`,data:o};return{status:"measured",note:`Event timestamp to the next rAF callback: ${a.join("; ")}. Real input from the OS; no synthetic events.`,data:o}}function Oe(e){let t=e.audio;if(!t.present)return{status:"unavailable",note:"window.AudioContext is undefined."};if(t.error||!t.initial)return{status:"error",note:`Creating an AudioContext failed: ${t.error??"unknown reason"}.`};let n=t.afterGesturedResume??t.afterUngesturedResume??t.initial,r={initial:t.initial,afterUngesturedResume:t.afterUngesturedResume??null,afterGesturedResume:t.afterGesturedResume??null},s=`baseLatency ${X(n.baseLatency)}, outputLatency ${X(n.outputLatency)} at ${n.sampleRate} Hz`,o=t.afterUngesturedResume?.state==="running"?"The context started without a user gesture":"The context stayed suspended without a user gesture";if(n.state!=="running")return{status:"requires-interaction",note:`${o}, so device latency was not observable. Reported before running: ${s}.`,data:r};if(n.baseLatency===null&&n.outputLatency===null)return{status:"unavailable",note:`${o}, but neither baseLatency nor outputLatency is implemented.`,data:r};return{status:"measured",note:`${o}. ${s}${n.outputLatency===null?" (outputLatency not implemented)":""}.`,data:r}}function Ne(e){let t=e.gamepad;if(!t.present)return{status:"unavailable",note:"navigator.getGamepads is undefined."};return{status:"available",note:t.connected>0?`navigator.getGamepads() is present and reported ${t.connected} controller(s).`:"navigator.getGamepads() is present; no controller was connected, so axis and button input was not exercised.",data:{...t,secureContext:e.isSecureContext}}}function Te(e){let t=e.fullscreen,n=t.nativeBridge.outcome==="not-attempted"||t.nativeBridge.outcome==="no-api"?"":` Craft's own window.craft.window.setFullscreen(true), which needs no gesture: ${R(t.nativeBridge)}.`,r={...t};if(!t.elementApi&&!t.webkitElementApi)return{status:"unavailable",note:`Element.requestFullscreen and webkitRequestFullscreen are both undefined, so the standard Fullscreen API engines call does nothing.${n}`,data:r};let s=`Without a gesture: ${R(t.ungestured)}.`;switch(t.gestured.outcome){case"entered":return{status:"available",note:`requestFullscreen() from a real click entered fullscreen. ${s}${n}`,data:r};case"rejected":case"timeout":return{status:"denied",note:`requestFullscreen() from a real click: ${R(t.gestured)}. ${s}${n}`,data:r};default:return{status:"requires-interaction",note:`The API exists (fullscreenEnabled ${String(t.enabled)}) but needs a real click to test. ${s}${n}`,data:r}}}function Be(e){let t=e.pointerLock,n={...t};if(!t.api)return{status:"unavailable",note:"Element.requestPointerLock is undefined.",data:n};let r=`Without a gesture: ${R(t.ungestured)}.`;switch(t.gestured.outcome){case"entered":return{status:"available",note:`requestPointerLock() from a real click locked the pointer. ${r}`,data:n};case"rejected":case"timeout":return{status:"denied",note:`requestPointerLock() from a real click: ${R(t.gestured)}. ${r}`,data:n};default:return{status:"requires-interaction",note:`The API exists but needs a real click to test. ${r}`,data:n}}}function te(e,t){return{schema:J,measuredAt:t.measuredAt,host:{...t.host,userAgent:e.userAgent},os:t.os,machine:{model:t.machine.model,cpu:t.machine.cpu,display:{...t.machine.display,devicePixelRatio:e.devicePixelRatio}},run:{idleMs:e.config.idleMs,loadMs:e.config.loadMs,cpuLoadMsPerFrame:e.config.cpuLoadMsPerFrame,triangles:e.frames.triangles,interactionWindowMs:e.interaction.windowMs,sawRealInput:e.interaction.sawRealInput},probes:{webgl2:De(e),webgpu:Fe(e),framePacing:Ce(e,t),inputLatency:Le(e),audioLatency:Oe(e),gamepad:Ne(e),fullscreen:Te(e),pointerLock:Be(e)}}}import{readdirSync as je,readFileSync as Ue}from"fs";import{join as _e}from"path";import{fileURLToPath as ze}from"url";var S=ze(new URL("./results",import.meta.url));function T(e){return e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")}function ne(e){return`${e.host.kind==="craft"?"craft":`browser-${T(e.host.name)}`}-${T(e.os.platform)}-${T(e.os.arch)}.json`}function Wt(e=S){let t;try{t=je(e).filter((n)=>n.endsWith(".json")).sort()}catch{return[]}return t.map((n)=>{let r=_e(e,n);return A(JSON.parse(Ue(r,"utf8")),r)})}var He="<!-- interactive-capability:begin -->",re="<!-- interactive-capability:end -->",B="buddy desktop:probe",qe={darwin:"macOS",linux:"Linux",win32:"Windows"};function se(e){return e.host.kind==="craft"?`Craft ${e.host.version}`:`${e.host.name} ${e.host.version}`}function oe(e){return`${qe[e.os.platform]??e.os.name} ${e.os.version} ${e.os.arch}, ${e.machine.cpu} (${e.machine.model})`}function We(e){return[...e].sort((t,n)=>{if(t.host.kind!==n.host.kind)return t.host.kind==="craft"?-1:1;return`${t.host.name} ${t.os.platform} ${t.os.arch}`.localeCompare(`${n.host.name} ${n.os.platform} ${n.os.arch}`)})}function P(e){return typeof e==="number"&&Number.isFinite(e)?e:null}function ie(e,...t){let n=e;for(let r of t){if(typeof n!=="object"||n===null)return;n=n[r]}return n}function U(e,t){let n=t.data;if(t.status!=="measured"||!n)return null;switch(e){case"framePacing":{let r=P(n.rafHz),s=P(n.displayHz),o=P(ie(n,"load","p99Ms"));if(r===null||s===null||o===null)return null;return`${r} Hz rAF on a ${s} Hz display, p99 ${o} ms under load`}case"inputLatency":{for(let r of["pointerdown","keydown","pointermove"]){let s=P(ie(n,r,"toFrame","p50Ms"));if(s!==null)return`${r} p50 ${s} ms to next frame`}return null}case"audioLatency":{let r=n.afterGesturedResume??n.afterUngesturedResume,s=P(r?.baseLatency),o=P(r?.outputLatency),a=(i)=>i===null?"n/a":`${Math.round(i*1e4)/10} ms`;return`base ${a(s)}, output ${a(o)}`}default:return null}}function Ve(e,t){if(!t)return"not probed";let n=U(e,t);return n?`**${t.status}** - ${n}`:`**${t.status}**`}function j(e){return e.replaceAll("|","\\|").replaceAll(`
57
+ `," ")}function Kt(e){let t=We(e),n=[He,"","<!-- Generated by `buddy docs:capabilities` from the records in"," storage/framework/core/desktop/src/probe/results/. Re-measure with",` \`${B} --record\`; do not edit this by hand. -->`,"","Whether a Craft window can host a game or other frame-driven content that somebody else","builds, measured by the probe harness in `@stacksjs/desktop-build`","([stacksjs/stacks#877](https://github.com/stacksjs/stacks/issues/877)). Ordinary browsers","on the same machine are measured alongside it for comparison.","","> **How current is this?** Unlike the driver tables above, these are measurements, not","> claims CI can re-check. Each column is one run on one machine, and says when, against","> which host version, on which OS and hardware. If your Craft is newer than the column,",`> the numbers describe something you are not running: re-measure with \`${B} --record\`.`,""];if(t.length===0)return n.push(`No measurement has been recorded yet. Run \`${B} --record\`.`,"",re),n.join(`
58
+ `);n.push(`| Probe | ${t.map((r)=>j(se(r))).join(" | ")} |`,`|---|${t.map(()=>"---").join("|")}|`);for(let[r,s]of b)n.push(`| ${s} | ${t.map((o)=>j(Ve(r,o.probes[r]))).join(" | ")} |`);n.push(`| Measured on | ${t.map((r)=>j(oe(r))).join(" | ")} |`,`| Measured at | ${t.map((r)=>r.measuredAt.slice(0,10)).join(" | ")} |`,"","- **available** / **measured** - the API exists and the probe exercised it.","- **unavailable** - the API is missing, or creating it failed.","- **requires-interaction** - needs a real click, key press or controller that the run did not have. What was still observable is in the notes; no number is invented.","- **denied** - requested from a real click and refused.","");for(let r of t){let s=r.machine.display.refreshHz===null?"unknown refresh rate":`${r.machine.display.refreshHz} Hz display`;n.push(`**${se(r)}** - ${oe(r)}, ${s}`,"");for(let[o,a]of b){let i=r.probes[o];if(i)n.push(`- **${a}** - ${i.note}`)}n.push("")}return n.push("**Out of scope.** Stacks is not shipping a game engine, a physics engine, an audio mixer or","an asset pipeline. This section establishes what a Craft window gives an engine somebody","else wrote; it is not a plan to write one.","",re),n.join(`
59
+ `)}function k(e){try{let t=Bun.spawnSync(e,{stdout:"pipe",stderr:"ignore"});return t.exitCode===0?t.stdout.toString().trim():null}catch{return null}}function Rt(e){return e?.match(/craft version\s+(\d+\.\d+\.\d+\S*)/i)?.[1]??null}function $t(e){if(!e)return null;try{let n=(JSON.parse(e).SPDisplaysDataType??[]).flatMap((o)=>o.spdisplays_ndrvs??[]),r=n.find((o)=>o.spdisplays_main==="spdisplays_yes")??n[0];if(!r)return null;let s=r._spdisplays_resolution??"unknown";return{name:r._name??"unknown",resolution:s,refreshHz:Y(s)}}catch{return null}}function St(e,t,n){let r=l.platform,s=Et[r]??r,o=ve.release(),a,i="unknown",u=ve.cpus()[0]?.model?.trim()||"unknown",c={name:"unknown",resolution:"unknown",refreshHz:null};if(r==="darwin")s="macOS",o=k(["sw_vers","-productVersion"])??o,a=k(["sw_vers","-buildVersion"])??void 0,i=k(["sysctl","-n","hw.model"])??i,u=k(["sysctl","-n","machdep.cpu.brand_string"])??u,c=$t(k(["system_profiler","SPDisplaysDataType","-json"]))??c;else if(r==="linux")i=k(["cat","/sys/devices/virtual/dmi/id/product_name"])??i;let d=ee(n);return{measuredAt:new Date().toISOString(),host:e==="craft"?{kind:"craft",name:"craft",version:t??"unknown"}:{kind:"browser",name:d.name,version:d.version},os:{platform:r,arch:l.arch,name:s,version:o,...a?{build:a}:{}},machine:{model:i,cpu:u,display:c}}}var Et={darwin:"macOS",linux:"Linux",win32:"Windows"};async function Mt(){let e=vt(Pt(import.meta.url)),t=[L(e,"page.ts"),L(e,"page.js")].find(Re);if(!t)throw Error("The probe page script is missing; reinstall @stacksjs/desktop-build.");let n=await Bun.build({entrypoints:[t],target:"browser",format:"esm",minify:!0});if(!n.success)throw Error(`Bundling the probe page failed:
60
+ ${n.logs.map(String).join(`
61
+ `)}`);return await n.outputs[0].text()}function At(e,t){if(t===!1)return null;if(l.platform==="darwin")return typeof t==="string"?["open","-a",t,e]:["open",e];if(l.platform==="win32")return["cmd","/c","start","",e];return["xdg-open",e]}async function It(e){let t=e.log??((m)=>console.log(m)),n={...G,...e.page};if(e.mode==="browser")n.tryNativeFullscreen=!1;let r=null,s=null;if(e.mode==="craft"){await Promise.resolve().then(() => ye());if(r=be(e.craftBin),s=Rt(k([r,"--version"])),!s)throw Error(`Could not read a version from \`${r} --version\`; is it a Craft binary?`)}let o=V(await Mt(),n),a,i,u=new Promise((m,h)=>{a=m,i=h}),c=Bun.serve({hostname:"127.0.0.1",port:e.port??0,async fetch(m){let{pathname:h}=new URL(m.url);if(m.method==="POST"&&h==="/results")return a(await m.json()),new Response("ok");if(m.method==="POST"&&h==="/progress")return t(` page: ${await m.text()}`),new Response("ok");if(m.method==="POST"&&h==="/failed")return i(Error(`The probe page failed: ${await m.text()}`)),new Response("ok");if(h==="/")return new Response(o,{headers:{"content-type":"text/html; charset=utf-8","cache-control":"no-store"}});return new Response("not found",{status:404})}}),d=c.url.href,p=null,g=()=>{p?.kill(),l.exit(130)};l.once("SIGINT",g),l.once("SIGTERM",g);let v=e.timeoutMs??n.idleMs+n.loadMs+n.interactionWindowMs+60000,$e=setTimeout(()=>i(Error(`No results within ${Math.round(v/1000)} s. Was the window visible?`)),v);try{if(e.mode==="craft")t(`Opening ${d} in Craft ${s} (${r})`),p=Bun.spawn([r,d,"--title","Stacks interactive probe","--width","1280","--height","800"],{stdout:"ignore",stderr:"ignore"}),p.exited.then((M)=>i(Error(`Craft exited (code ${M}) before the probe finished`)));else{t(`Probe page: ${d}`);let M=At(d,e.open??!0);if(M)Bun.spawn(M,{stdout:"ignore",stderr:"ignore"});else t("Open that URL in the browser to measure, and keep its window visible.")}let m=await u,h=St(e.mode,s,m.userAgent);return A(te(m,h),"the probe result")}finally{clearTimeout($e),l.off("SIGINT",g),l.off("SIGTERM",g),p?.kill(),c.stop(!0)}}function Dt(e){let t=Math.max(...b.map(([,r])=>r.length)),n=[`${e.host.kind==="craft"?`Craft ${e.host.version}`:`${e.host.name} ${e.host.version}`} on ${e.os.name} ${e.os.version} (${e.machine.model}, ${e.machine.cpu}), display ${e.machine.display.refreshHz??"unknown"} Hz`];for(let[r,s]of b){let o=e.probes[r],a=U(r,o);n.push(` ${s.padEnd(t)} ${o.status}${a?` - ${a}`:""}`)}return n.join(`
62
+ `)}function w(e){let t=`--${e}=`;for(let n=0;n<l.argv.length;n++){let r=l.argv[n];if(r===`--${e}`){let s=l.argv[n+1];return s&&!s.startsWith("--")?s:!0}if(r.startsWith(t))return r.slice(t.length)}return}function C(e){let t=w(e);if(t===void 0||t===!0)return;let n=Number(t);if(!Number.isFinite(n)||n<0)throw Error(`--${e} must be a non-negative number of seconds`);return n*1000}var Pe=xt(S,"../../../../../../..");async function Ft(){let e=w("browser"),t=w("record")!==void 0,n=w("out"),r=w("cpu-load"),s=w("instances");if(t){if(xe(Pe,l.cwd()).startsWith("..")||!Re(L(Pe,"storage/framework/core/buddy")))throw Error("--record writes into the Stacks framework repository; run it from that checkout, or use --out <file>.")}let o={},a=C("idle"),i=C("load"),u=C("interaction-window");if(a!==void 0)o.idleMs=a;if(i!==void 0)o.loadMs=i;if(u!==void 0)o.interactionWindowMs=u;if(typeof r==="string")o.cpuLoadMsPerFrame=Number(r);if(typeof s==="string")o.instances=Number(s);let c=typeof e==="string"?e:!0;if(w("no-open")!==void 0)c=!1;let d=w("craft-bin"),p=await It({mode:e===void 0?"craft":"browser",open:c,craftBin:typeof d==="string"?d:void 0,timeoutMs:C("timeout"),page:o});console.log(`
63
+ ${Dt(p)}
64
+ `);let g=`${JSON.stringify(p,null,2)}
65
+ `;if(t){kt(S,{recursive:!0});let v=L(S,ne(p));ke(v,g),console.log(`Recorded ${xe(l.cwd(),v)}. Run \`buddy docs:capabilities\` to publish it.`)}if(typeof n==="string")ke(n,g),console.log(`Wrote ${n}`);if(!t&&typeof n!=="string")console.log(g)}if(import.meta.main)try{await Ft()}catch(e){l.stderr.write(`${e instanceof Error?e.message:String(e)}
66
+ `),l.exit(1)}export{St as collectHostFacts,$t as mainDisplayFromProfiler,Rt as parseCraftVersion,Ft as run,It as runProbe,Dt as summarize};
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Every way `value` fails to be an `InteractiveProbeRecord`, as readable
3
+ * messages. An empty array means it is valid.
4
+ *
5
+ * Hand-written rather than a schema library because the desktop package has no
6
+ * validation dependency and this is the one shape it checks.
7
+ */
8
+ export declare function validateProbeRecord(value: unknown): string[];
9
+ /** Throw with every validation error at once, or return the value typed. */
10
+ export declare function assertProbeRecord(value: unknown, source?: string): InteractiveProbeRecord;
11
+ /**
12
+ * The shape of one interactive-content measurement (stacksjs/stacks#877).
13
+ *
14
+ * A record is data, not prose: it says when it was taken, against which host
15
+ * (a Craft window or a browser) at which version, on which OS and machine. That
16
+ * is what lets it go stale visibly - a reader sees "Craft 0.0.105, macOS 27.0,
17
+ * 2026-10-02" beside every number and can tell how far that is from what they
18
+ * run.
19
+ *
20
+ * Every probe carries a status and a note. A probe that could not run - input
21
+ * latency with nobody at the keyboard, pointer lock without a user gesture, a
22
+ * gamepad with none attached - says `requires-interaction` and what was still
23
+ * observable. It never carries a number it did not measure.
24
+ */
25
+ export declare const PROBE_SCHEMA_VERSION: 1;
26
+ export declare const PROBE_STATUSES: readonly ProbeStatus[];
27
+ /** Probe order, and the label each is published under. */
28
+ export declare const PROBE_NAMES: ReadonlyArray<[ProbeName, string]>;
29
+ export declare interface ProbeResult {
30
+ status: ProbeStatus
31
+ note: string
32
+ data?: Record<string, unknown>
33
+ }
34
+ export declare interface InteractiveProbeRecord {
35
+ schema: typeof PROBE_SCHEMA_VERSION
36
+ measuredAt: string
37
+ host: {
38
+ kind: 'craft' | 'browser'
39
+ name: string
40
+ version: string
41
+ userAgent: string
42
+ }
43
+ os: {
44
+ platform: string
45
+ arch: string
46
+ name: string
47
+ version: string
48
+ build?: string
49
+ }
50
+ machine: {
51
+ model: string
52
+ cpu: string
53
+ display: {
54
+ name: string
55
+ resolution: string
56
+ refreshHz: number | null
57
+ devicePixelRatio: number
58
+ }
59
+ }
60
+ run: {
61
+ idleMs: number
62
+ loadMs: number
63
+ cpuLoadMsPerFrame: number
64
+ triangles: number
65
+ interactionWindowMs: number
66
+ sawRealInput: boolean
67
+ }
68
+ probes: Record<ProbeName, ProbeResult>
69
+ }
70
+ export type ProbeStatus = | 'available'
71
+ /** The API is absent, or creating it failed. */
72
+ | 'unavailable'
73
+ /** Numbers were taken; see `data`. */
74
+ | 'measured'
75
+ /** Needs real input, a user gesture or hardware that this run did not have. */
76
+ | 'requires-interaction'
77
+ /** Requested with a real user gesture and refused. */
78
+ | 'denied'
79
+ /** The probe threw; `note` carries the message. */
80
+ | 'error';
81
+ export type ProbeName = | 'webgl2'
82
+ | 'webgpu'
83
+ | 'framePacing'
84
+ | 'inputLatency'
85
+ | 'audioLatency'
86
+ | 'gamepad'
87
+ | 'fullscreen'
88
+ | 'pointerLock';
@@ -0,0 +1,75 @@
1
+ /**
2
+ * The `p`th percentile (0..100) of `values`, by linear interpolation between
3
+ * closest ranks - the same definition as NumPy's default and a spreadsheet's
4
+ * PERCENTILE. Returns null for an empty input rather than inventing a zero.
5
+ */
6
+ export declare function percentile(values: readonly number[], p: number): number | null;
7
+ /** Consecutive differences of a monotonic series of timestamps. */
8
+ export declare function intervals(timestamps: readonly number[]): number[];
9
+ /** Fixed-width histogram of `values`, with an explicit overflow bucket. */
10
+ export declare function histogram(values: readonly number[], bucketMs?: number, buckets?: number): Histogram;
11
+ /**
12
+ * Snap a measured frame period to the nearest common refresh rate when it is
13
+ * within `tolerance` of one, so 8.33 ms reads as 120 Hz rather than 120.05 Hz.
14
+ * Returns the unsnapped rate when nothing is close.
15
+ */
16
+ export declare function snapRefreshRate(periodMs: number, tolerance?: number): number;
17
+ /**
18
+ * Frames that should have been presented and were not.
19
+ *
20
+ * An interval of 2.0 periods means one frame was skipped; 1.4 periods is late
21
+ * but not a skipped frame. Rounding is the usual convention (it matches what a
22
+ * frame-time graph shows as a missing bar).
23
+ */
24
+ export declare function droppedFrames(frameIntervals: readonly number[], periodMs: number): number;
25
+ /**
26
+ * Summarise a run of `requestAnimationFrame` timestamps against a target rate.
27
+ *
28
+ * `targetHz` should be the display's refresh rate when it is known, because
29
+ * that is the rate a game wants. Pass the rate rAF itself runs at to count
30
+ * drops against what the web view actually delivers - the two differ when the
31
+ * web view caps rAF below the display (WebKit's 60 Hz cap on ProMotion panels).
32
+ */
33
+ export declare function framePacing(timestamps: readonly number[], targetHz: number): FramePacingStats | null;
34
+ /** Percentile summary of latency samples, or null when there were none. */
35
+ export declare function latency(samples: readonly number[]): LatencyStats | null;
36
+ /**
37
+ * The refresh rate in a `system_profiler SPDisplaysDataType` resolution string
38
+ * such as `1512 x 982 @ 120.00Hz`, or null when it names none.
39
+ */
40
+ export declare function parseRefreshHz(resolution: string | undefined): number | null;
41
+ /**
42
+ * The arithmetic behind the interactive-content probe (stacksjs/stacks#877).
43
+ *
44
+ * Kept pure and DOM-free so the numbers the probe page reports can be checked
45
+ * without a window: the page collects raw `requestAnimationFrame` timestamps and
46
+ * event-to-frame deltas, and everything that turns those into a histogram, a
47
+ * percentile or a dropped-frame count lives here, where a unit test can reach it.
48
+ */
49
+ /** Refresh rates a display can plausibly run at, used to snap a measured period. */
50
+ export declare const COMMON_REFRESH_RATES: readonly number[];
51
+ export declare interface Histogram {
52
+ bucketMs: number
53
+ counts: number[]
54
+ overflow: number
55
+ }
56
+ export declare interface FramePacingStats {
57
+ frames: number
58
+ durationMs: number
59
+ fps: number
60
+ p50Ms: number
61
+ p95Ms: number
62
+ p99Ms: number
63
+ maxMs: number
64
+ targetPeriodMs: number
65
+ targetHz: number
66
+ droppedFrames: number
67
+ longFrames: number
68
+ histogram: Histogram
69
+ }
70
+ export declare interface LatencyStats {
71
+ samples: number
72
+ p50Ms: number
73
+ p95Ms: number
74
+ maxMs: number
75
+ }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/desktop-build",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.75.38",
5
+ "version": "0.75.41",
6
6
  "description": "Build and launch Craft desktop applications for Stacks",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -67,11 +67,11 @@
67
67
  "prepublishOnly": "bun run build"
68
68
  },
69
69
  "dependencies": {
70
- "craft-native": ">=0.0.55"
70
+ "craft-native": ">=0.0.107"
71
71
  },
72
72
  "devDependencies": {
73
- "@stacksjs/build": "0.75.38",
73
+ "@stacksjs/build": "0.75.41",
74
74
  "better-dx": "^0.2.24",
75
- "@stacksjs/server": "0.75.38"
75
+ "@stacksjs/server": "0.75.41"
76
76
  }
77
77
  }