whose-turn 1.0.0-alpha.7 → 1.0.0-alpha.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +28 -16
  3. package/config.example.json +0 -4
  4. package/connectors/github.mjs +24 -0
  5. package/dist/assets/_plugin-vue_export-helper-BbgK5wvS.js +18 -0
  6. package/dist/assets/_plugin-vue_export-helper-BneMU3KP.css +1 -0
  7. package/dist/assets/bem-OtK9ToP0.js +1 -0
  8. package/dist/assets/index-B6grE8kv.css +1 -0
  9. package/dist/assets/index-C3jZLqLJ.js +15 -0
  10. package/dist/assets/report-DUlemvzD.js +1 -0
  11. package/dist/assets/scheme-DCqBrLhv.js +1 -0
  12. package/dist/assets/sheet-Ctd_6q4a.js +1 -0
  13. package/dist/index.html +6 -6
  14. package/dist/report.html +4 -4
  15. package/dist/sheet.html +4 -4
  16. package/lib/api.mjs +70 -24
  17. package/lib/backoff.mjs +13 -0
  18. package/lib/compose.mjs +6 -8
  19. package/lib/config.mjs +102 -15
  20. package/lib/default-table.mjs +1 -0
  21. package/lib/errors.mjs +1 -2
  22. package/lib/facts.mjs +1 -1
  23. package/lib/feedback.mjs +140 -111
  24. package/lib/linear-api.mjs +1 -1
  25. package/lib/notify.mjs +7 -8
  26. package/lib/registry.mjs +32 -5
  27. package/lib/rules.mjs +16 -9
  28. package/lib/table.mjs +1 -0
  29. package/lib/text.mjs +3 -0
  30. package/lib/trace.mjs +6 -6
  31. package/lib/workspace.mjs +63 -15
  32. package/package.json +2 -1
  33. package/server.mjs +17 -8
  34. package/dist/assets/_plugin-vue_export-helper-DeQjDyQ9.js +0 -18
  35. package/dist/assets/_plugin-vue_export-helper-aQHODzfZ.css +0 -1
  36. package/dist/assets/bem-zV6iDqYN.js +0 -1
  37. package/dist/assets/index-BLFgmJcy.js +0 -7
  38. package/dist/assets/index-CTrTp-xu.css +0 -1
  39. package/dist/assets/report-BJ_aC1A7.js +0 -1
  40. package/dist/assets/scheme-BdlolNvz.js +0 -1
  41. package/dist/assets/sheet-q2RQEgOf.js +0 -1
@@ -0,0 +1 @@
1
+ import{d as v,e as w,v as s,F as o,L as a,H as c,K as t,N as f,z as p,O as u,M as h,I as k,b as g,_ as y,$ as N}from"./_plugin-vue_export-helper-BbgK5wvS.js";import{g as x,S as b,m as R,t as C,j as d}from"./bem-OtK9ToP0.js";const B={class:"app report-page"},L={key:0,class:"report-page__note"},S={class:"report-page__head"},T={class:"report-page__id"},V={class:"report-page__title"},E={class:"report-page__meta"},I={class:"report-page__section"},P={class:"report-page__text"},W={class:"report-page__section"},z={key:0,class:"report-page__unanswered"},A={key:1,class:"empty"},D={class:"report-page__answer-meta"},F={class:"report-page__text"},M=v({__name:"ReportPage",setup(U){const n=new URLSearchParams(location.search).get("id")||"",e=g(null),i=g(n?"Loading…":"No report id in the link.");return w(()=>{n&&(document.title=n+" · Your report · Whose Turn",x("/api/feedback?id="+encodeURIComponent(n)).then(_=>{e.value=_.report||null,i.value=_.report?"":"No report "+n+" was filed from this machine."}).catch(()=>{i.value=b+"; reload this page once it is back."}))}),(_,r)=>(s(),o("main",B,[e.value?(s(),o(c,{key:1},[t("header",S,[t("span",T,a(e.value.id),1),t("h1",V,a(e.value.title),1),t("span",{class:f(["pill",p(R)("pill",p(C)(e.value.outcome))])},a(e.value.state||"filed, not yet seen"),3)]),t("p",E,[u(a(e.value.kind||"report")+" · filed "+a(p(d)(e.value.filed_at)),1),e.value.changed_at?(s(),o(c,{key:0},[u(" · last change "+a(p(d)(e.value.changed_at)),1)],64)):h("",!0)]),t("section",I,[r[0]||(r[0]=t("h2",{class:"report-page__heading"},"What you reported",-1)),t("p",P,a(e.value.body||"(no text kept)"),1)]),t("section",W,[r[1]||(r[1]=t("h2",{class:"report-page__heading"},"Answers",-1)),e.value.unanswered?(s(),o("p",z," Closed without an answer. The maintainer owes you a reason; this page will show it once it is written. ")):e.value.answers.length?h("",!0):(s(),o("p",A,"No answer yet.")),(s(!0),o(c,null,k(e.value.answers,(l,m)=>(s(),o("article",{key:m,class:"report-page__answer"},[t("p",D,a(l.by||"maintainer")+" · "+a(p(d)(l.at)),1),t("p",F,a(l.text),1)]))),128))])],64)):(s(),o("p",L,a(i.value),1)),r[2]||(r[2]=t("a",{class:"report-page__back",href:"/"},"Back to the window",-1))]))}}),O=y(M,[["__scopeId","data-v-16ea9dee"]]);N(O).mount("#app");
@@ -0,0 +1 @@
1
+ import{w as N,a1 as J,E as Y,a2 as _,a3 as we,a4 as Pe,a5 as Ne,b as _e,D as ke,g as ne,a6 as Se,i as Fe,a7 as Le,e as We,a8 as Ie,a9 as ze,U as $e,Y as Be,c as O,z as qe,r as pe}from"./_plugin-vue_export-helper-BbgK5wvS.js";function P(e,t){return Le()?(Ie(e,t),!0):!1}const Z=new WeakMap,Ve=(...e)=>{var t;const a=e[0],n=(t=ne())===null||t===void 0?void 0:t.proxy,i=n??Le();if(i==null&&!Se())throw new Error("injectLocal must be called in setup");return i&&Z.has(i)&&a in Z.get(i)?Z.get(i)[a]:Fe(...e)},z=typeof window<"u"&&typeof document<"u";typeof WorkerGlobalScope<"u"&&globalThis instanceof WorkerGlobalScope;const ta=e=>typeof e<"u",Je=e=>e!=null,Ke=Object.prototype.toString,Ue=e=>Ke.call(e)==="[object Object]",M=()=>{};function ie(...e){if(e.length!==1)return Pe(...e);const t=e[0];return typeof t=="function"?Ne(we(()=>({get:t,set:M}))):_e(t)}function oe(e,t){function a(...n){return new Promise((i,o)=>{Promise.resolve(e(()=>t.apply(this,n),{fn:t,thisArg:this,args:n})).then(i).catch(o)})}return"cancel"in e&&Object.assign(a,{cancel:e.cancel,flush:e.flush,isPending:e.isPending}),a}const Ee=e=>e();function je(e,t={}){let a,n,i=M,o=M;const s=_(!1),r=c=>{clearTimeout(c),i(),i=M};let l;return Object.assign(c=>{const u=Y(e),d=Y(t.maxWait);return a&&r(a),u<=0||d!==void 0&&d<=0?(n&&(r(n),n=void 0),s.value=!1,Promise.resolve(c())):(s.value=!0,new Promise((g,p)=>{i=t.rejectOnCancel?p:g,o=g,l=c,d&&!n&&(n=setTimeout(()=>{a&&r(a),n=void 0,s.value=!1,g(l())},d)),a=setTimeout(()=>{n&&r(n),n=void 0,s.value=!1,g(c())},u)}))},{cancel:()=>{a&&(r(a),a=void 0),n&&(r(n),n=void 0),s.value=!1,o=M},flush:()=>{if(s.value){a&&(clearTimeout(a),a=void 0),n&&(clearTimeout(n),n=void 0),s.value=!1;const c=o;i=M,o=M,c(l())}},isPending:J(s)})}function Ge(...e){let t=0,a,n=!0,i=M,o,s,r,l,h;!$e(e[0])&&typeof e[0]=="object"?{delay:s,trailing:r=!0,leading:l=!0,rejectOnCancel:h=!1}=e[0]:[s,r=!0,l=!0,h=!1]=e;const c=()=>{a&&(clearTimeout(a),a=void 0,i(),i=M)};return d=>{const g=Y(s),p=Date.now()-t,b=()=>o=d();return c(),g<=0?(t=Date.now(),b()):(p>g?(t=Date.now(),(l||!n)&&b()):r&&(o=new Promise((v,R)=>{i=h?R:v,a=setTimeout(()=>{t=Date.now(),n=!0,v(b()),c()},Math.max(0,g-p))})),!l&&!a&&(a=setTimeout(()=>n=!0,g)),n=!1,o)}}function Qe(e=Ee,t={}){const{initialState:a="active"}=t,n=ie(a==="active");function i(){n.value=!1}function o(){n.value=!0}const s=(...r)=>{n.value&&e(...r)};return{isActive:J(n),pause:i,resume:o,eventFilter:s}}function fe(e){return e.endsWith("rem")?Number.parseFloat(e)*16:Number.parseFloat(e)}function V(e){return Array.isArray(e)?e:[e]}function Ze(e){return ne()}function aa(e){if(!z)return e;let t=0,a,n;const i=()=>{t-=1,n&&t<=0&&(n.stop(),a=void 0,n=void 0)};return((...o)=>(t+=1,n||(n=ze(!0),a=n.run(()=>e(...o))),P(i),a))}function na(e,t=1e4){return we((a,n)=>{let i=Y(e),o;const s=()=>setTimeout(()=>{i=Y(e),n()},Y(t));return P(()=>{clearTimeout(o)}),{get(){return a(),i},set(r){i=r,n(),clearTimeout(o),o=s()}}})}function et(e,t=200,a={}){return oe(je(t,a),e)}function tt(e,t=200,a=!0,n=!0,i=!1){return oe(Ge(t,a,n,i),e)}function De(e,t,a={}){const{eventFilter:n=Ee,...i}=a;return N(e,oe(n,t),i)}function at(e,t,a={}){const{eventFilter:n,initialState:i="active",...o}=a,{eventFilter:s,pause:r,resume:l,isActive:h}=Qe(n,{initialState:i});return{stop:De(e,t,{...o,eventFilter:s}),pause:r,resume:l,isActive:h}}function se(e,t=!0,a){Ze()?We(e,a):t?e():ke(e)}function ia(e,t=1e3,a={}){const{immediate:n=!0,immediateCallback:i=!1}=a;let o=null;const s=_(!1);function r(){o&&(clearInterval(o),o=null)}function l(){s.value=!1,r()}function h(){const c=Y(t);c<=0||(s.value=!0,i&&e(),r(),s.value&&(o=setInterval(e,c)))}return n&&z&&h(),($e(t)||typeof t=="function")&&P(N(t,()=>{s.value&&z&&h()})),P(l),{isActive:J(s),pause:l,resume:h}}function oa(e,t,a={}){const{immediate:n=!0,immediateCallback:i=!1}=a,o=_(!1);let s;function r(){s&&(clearTimeout(s),s=void 0)}function l(){o.value=!1,r()}function h(...c){i&&e(),r(),o.value=!0,s=setTimeout(()=>{o.value=!1,s=void 0,e(...c)},Y(t))}return n&&(o.value=!0,z&&h()),P(l),{isPending:J(o),start:h,stop:l}}function sa(e,t,a={}){const{debounce:n=0,maxWait:i=void 0,...o}=a;return De(e,t,{...o,eventFilter:je(n,{maxWait:i})})}function nt(e,t,a){return N(e,t,{...a,immediate:!0})}const $=z?window:void 0,it=z?window.document:void 0;function C(e){var t;const a=Y(e);return(t=a==null?void 0:a.$el)!==null&&t!==void 0?t:a}function q(...e){const t=(n,i,o,s)=>(n.addEventListener(i,o,s),()=>n.removeEventListener(i,o,s)),a=O(()=>{const n=V(Y(e[0])).filter(i=>i!=null);return n.every(i=>typeof i!="string")?n:void 0});return nt(()=>{var n,i;return[(n=(i=a.value)===null||i===void 0?void 0:i.map(o=>C(o)))!==null&&n!==void 0?n:[$].filter(o=>o!=null),V(Y(a.value?e[1]:e[0])),V(qe(a.value?e[2]:e[1])),Y(a.value?e[3]:e[2])]},([n,i,o,s],r,l)=>{if(!(n!=null&&n.length)||!(i!=null&&i.length)||!(o!=null&&o.length))return;const h=Ue(s)?{...s}:s,c=n.flatMap(u=>i.flatMap(d=>o.map(g=>t(u,d,g,h))));l(()=>{c.forEach(u=>u())})},{flush:"post"})}function ot(){const e=_(!1),t=ne();return t&&We(()=>{e.value=!0},t),e}function re(e){const t=ot();return O(()=>(t.value,!!e()))}function st(e,t,a={}){const{window:n=$,...i}=a;let o;const s=re(()=>n&&"MutationObserver"in n),r=()=>{o&&(o.disconnect(),o=void 0)},l=O(()=>{const d=Y(e),g=V(d).map(C).filter(Je);return new Set(g)}),h=N(l,d=>{r(),s.value&&d.size&&(o=new MutationObserver(t),d.forEach(g=>o.observe(g,i)))},{immediate:!0,flush:"post"}),c=()=>o==null?void 0:o.takeRecords(),u=()=>{h(),r()};return P(u),{isSupported:s,stop:u,takeRecords:c}}function rt(e){return typeof e=="function"?e:typeof e=="string"?t=>t.key===e:Array.isArray(e)?t=>e.includes(t.key):()=>!0}function ra(...e){let t,a,n={};e.length===3?(t=e[0],a=e[1],n=e[2]):e.length===2?typeof e[1]=="object"?(t=!0,a=e[0],n=e[1]):(t=e[0],a=e[1]):(t=!0,a=e[0]);const{target:i=$,eventName:o="keydown",passive:s=!1,dedupe:r=!1}=n,l=rt(t);return q(i,o,c=>{c.repeat&&Y(r)||l(c)&&a(c)},s)}function lt(e,t={}){const{immediate:a=!0,fpsLimit:n=null,window:i=$,once:o=!1}=t,s=_(!1),r=O(()=>{const g=Y(n);return g?1e3/g:null});let l=0,h=null;function c(g){if(!s.value||!i)return;l||(l=g);const p=g-l;if(r.value&&p<r.value){h=i.requestAnimationFrame(c);return}if(l=g,e({delta:p,timestamp:g}),o){s.value=!1,h=null;return}h=i.requestAnimationFrame(c)}function u(){!s.value&&i&&(s.value=!0,l=0,h=i.requestAnimationFrame(c))}function d(){s.value=!1,h!=null&&i&&(i.cancelAnimationFrame(h),h=null)}return a&&u(),P(d),{isActive:J(s),pause:d,resume:u}}const dt=Symbol("vueuse-ssr-width");function ut(){const e=Se()?Ve(dt,null):null;return typeof e=="number"?e:void 0}function Oe(e,t={}){const{window:a=$,ssrWidth:n=ut()}=t,i=re(()=>a&&"matchMedia"in a&&typeof a.matchMedia=="function"),o=_(typeof n=="number"),s=_(),r=_(!1),l=h=>{r.value=h.matches};return Be(()=>{if(o.value){o.value=!i.value;const h=Y(e).split(",");r.value=h.some(c=>{const u=c.includes("not all"),d=c.match(/\(\s*min-width:\s*(-?\d+(?:\.\d*)?[a-z]+\s*)\)/),g=c.match(/\(\s*max-width:\s*(-?\d+(?:\.\d*)?[a-z]+\s*)\)/);let p=!!(d||g);return d&&p&&(p=n>=fe(d[1])),g&&p&&(p=n<=fe(g[1])),u?!p:p});return}i.value&&(s.value=a.matchMedia(Y(e)),r.value=s.value.matches)}),q(s,"change",l,{passive:!0}),O(()=>r.value)}const K=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{},U="__vueuse_ssr_handlers__",ct=ht();function ht(){return U in K||(K[U]=K[U]||{}),K[U]}function gt(e,t){return ct[e]||t}function pt(e){return Oe("(prefers-color-scheme: dark)",e)}function ft(e){return e==null?"any":e instanceof Set?"set":e instanceof Map?"map":e instanceof Date?"date":typeof e=="boolean"?"boolean":typeof e=="string"?"string":typeof e=="object"?"object":Number.isNaN(e)?"any":"number"}const bt={boolean:{read:e=>e==="true",write:e=>String(e)},object:{read:e=>JSON.parse(e),write:e=>JSON.stringify(e)},number:{read:e=>Number.parseFloat(e),write:e=>String(e)},any:{read:e=>e,write:e=>String(e)},string:{read:e=>e,write:e=>String(e)},map:{read:e=>new Map(JSON.parse(e)),write:e=>JSON.stringify(Array.from(e.entries()))},set:{read:e=>new Set(JSON.parse(e)),write:e=>JSON.stringify(Array.from(e))},date:{read:e=>new Date(e),write:e=>e.toISOString()}},be="vueuse-storage";function Te(e,t,a,n={}){var i;const{flush:o="pre",deep:s=!0,listenToStorageChanges:r=!0,writeDefaults:l=!0,mergeDefaults:h=!1,shallow:c,window:u=$,eventFilter:d,onError:g=f=>{console.error(f)},initOnMounted:p}=n,b=(c?_:_e)(typeof t=="function"?t():t),v=O(()=>Y(e));if(!a)try{a=gt("getDefaultStorage",()=>$==null?void 0:$.localStorage)()}catch(f){g(f)}if(!a)return b;const R=Y(t),w=ft(R),X=(i=n.serializer)!==null&&i!==void 0?i:bt[w],{pause:k,resume:H}=at(b,f=>T(f),{flush:o,deep:s,eventFilter:d});N(v,()=>D(),{flush:o});let W=!1;const E=f=>{p&&!W||D(f)},j=f=>{p&&!W||F(f)};u&&r&&(a instanceof Storage?q(u,"storage",E,{passive:!0}):q(u,be,j)),p?se(()=>{W=!0,D()}):D();function y(f,m){if(u){const S={key:v.value,oldValue:f,newValue:m,storageArea:a};u.dispatchEvent(a instanceof Storage?new StorageEvent("storage",S):new CustomEvent(be,{detail:S}))}}function T(f){try{const m=a.getItem(v.value);if(f==null)y(m,null),a.removeItem(v.value);else{const S=X.write(f);m!==S&&(a.setItem(v.value,S),y(m,S))}}catch(m){g(m)}}function L(f){const m=f?f.newValue:a.getItem(v.value);if(m==null)return l&&R!=null&&a.setItem(v.value,X.write(R)),R;if(!f&&h){const S=X.read(m);return typeof h=="function"?h(S,R):w==="object"&&!Array.isArray(S)?{...R,...S}:S}else return typeof m!="string"?m:X.read(m)}function D(f){if(!(f&&f.storageArea!==a)){if(f&&f.key==null){b.value=R;return}if(!(f&&f.key!==v.value)){k();try{const m=X.write(b.value);(f===void 0||(f==null?void 0:f.newValue)!==m)&&(b.value=L(f))}catch(m){g(m)}finally{f?ke(H):H()}}}}function F(f){D(f.detail)}return b}function yt(e,t,a={}){const{window:n=$,...i}=a;let o;const s=re(()=>n&&"ResizeObserver"in n),r=()=>{o&&(o.disconnect(),o=void 0)},l=O(()=>{const u=Y(e);return Array.isArray(u)?u.map(d=>C(d)):[C(u)]}),h=N(l,u=>{if(r(),s.value&&n){o=new ResizeObserver(t);for(const d of u)d instanceof Element&&o.observe(d,i)}},{immediate:!0,flush:"post"}),c=()=>{r(),h()};return P(c),{isSupported:s,stop:c}}function la(e,t={width:0,height:0},a={}){const{window:n=$,box:i="content-box"}=a,o=O(()=>{var u;return(u=C(e))===null||u===void 0||(u=u.namespaceURI)===null||u===void 0?void 0:u.includes("svg")}),s=_(t.width),r=_(t.height),{stop:l}=yt(e,([u])=>{const d=i==="border-box"?u.borderBoxSize:i==="content-box"?u.contentBoxSize:u.devicePixelContentBoxSize;if(n&&o.value){const g=C(e);if(g){const p=g.getBoundingClientRect();s.value=p.width,r.value=p.height}}else if(d){const g=V(d);s.value=g.reduce((p,{inlineSize:b})=>p+b,0),r.value=g.reduce((p,{blockSize:b})=>p+b,0)}else s.value=u.contentRect.width,r.value=u.contentRect.height},a);se(()=>{const u=C(e);if(u&&"offsetWidth"in u)if(i==="content-box"&&n){const d=n.getComputedStyle(u),g=Number.parseFloat(d.paddingLeft)+Number.parseFloat(d.paddingRight),p=Number.parseFloat(d.paddingTop)+Number.parseFloat(d.paddingBottom),b=Number.parseFloat(d.borderLeftWidth)+Number.parseFloat(d.borderRightWidth),v=Number.parseFloat(d.borderTopWidth)+Number.parseFloat(d.borderBottomWidth);s.value=u.offsetWidth-g-b,r.value=u.offsetHeight-p-v}else s.value=u.offsetWidth,r.value=u.offsetHeight;else u&&(s.value=t.width,r.value=t.height)});const h=N(()=>C(e),u=>{s.value=u?t.width:0,r.value=u?t.height:0});function c(){l(),h()}return{width:s,height:r,stop:c}}function mt(e){return e===!0?{}:e}const vt="message";function da(e,t=[],a={}){const n=_(null),i=_(null),o=_("CONNECTING"),s=_(null),r=_(null),l=ie(e),h=_(null);let c=!1,u=0;const{withCredentials:d=!1,immediate:g=!0,autoConnect:p=!0,autoReconnect:b,serializer:v={read:k=>k}}=a,R=()=>{z&&s.value&&(s.value.close(),s.value=null,o.value="CLOSED",c=!0)},w=()=>{if(c||typeof l.value>"u")return;const k=new EventSource(l.value,{withCredentials:d});o.value="CONNECTING",s.value=k,k.onopen=()=>{o.value="OPEN",r.value=null},k.onerror=H=>{if(o.value="CLOSED",r.value=H,k.readyState===2&&!c&&b){k.close();const{retries:W=-1,delay:E=1e3,onFailed:j}=mt(b);u+=1,typeof W=="number"&&(W<0||u<W)||typeof W=="function"&&W()?setTimeout(w,E):j==null||j()}},t=t.length>0?t:[vt];for(const H of t)q(k,H,W=>{var E,j;n.value=H,i.value=(E=v.read(W.data))!==null&&E!==void 0?E:null,h.value=(j=W.lastEventId)!==null&&j!==void 0?j:null},{passive:!0})},X=()=>{z&&(R(),c=!1,u=0,w())};return g&&X(),p&&N(l,X),P(R),{eventSource:s,event:n,data:i,status:o,error:r,open:X,close:R,lastEventId:h}}function ua(e=null,t={}){const{baseUrl:a="",rel:n="icon",document:i=it}=t,o=ie(e),s=r=>{const l=i==null?void 0:i.head.querySelectorAll(`link[rel*="${n}"]`);if(!l||l.length===0){const h=i==null?void 0:i.createElement("link");h&&(h.rel=n,h.href=`${a}${r}`,h.type=`image/${r.split(".").pop()}`,i==null||i.head.append(h));return}l==null||l.forEach(h=>h.href=`${a}${r}`)};return N(o,(r,l)=>{typeof r=="string"&&r!==l&&s(r)},{immediate:!0}),o}const ye=1;function ca(e,t={}){const{throttle:a=0,idle:n=200,onStop:i=M,onScroll:o=M,offset:s={left:0,right:0,top:0,bottom:0},observe:r={mutation:!1},eventListenerOptions:l={capture:!1,passive:!0},behavior:h="auto",window:c=$,onError:u=y=>{console.error(y)}}=t,d=typeof r=="boolean"?{mutation:r}:r,g=_(0),p=_(0),b=O({get(){return g.value},set(y){R(y,void 0)}}),v=O({get(){return p.value},set(y){R(void 0,y)}});function R(y,T){var L,D,F,f;if(!c)return;const m=Y(e);if(!m)return;(L=m instanceof Document?c.document.body:m)===null||L===void 0||L.scrollTo({top:(D=Y(T))!==null&&D!==void 0?D:v.value,left:(F=Y(y))!==null&&F!==void 0?F:b.value,behavior:Y(h)});const S=(m==null||(f=m.document)===null||f===void 0?void 0:f.documentElement)||(m==null?void 0:m.documentElement)||m;b!=null&&(g.value=S.scrollLeft),v!=null&&(p.value=S.scrollTop)}const w=_(!1),X=pe({left:!0,right:!1,top:!0,bottom:!1}),k=pe({left:!1,right:!1,top:!1,bottom:!1}),H=y=>{w.value&&(w.value=!1,k.left=!1,k.right=!1,k.top=!1,k.bottom=!1,i(y))},W=et(H,a+n),E=y=>{var T;if(!c)return;const L=(y==null||(T=y.document)===null||T===void 0?void 0:T.documentElement)||(y==null?void 0:y.documentElement)||C(y),{display:D,flexDirection:F,direction:f}=c.getComputedStyle(L),m=f==="rtl"?-1:1,S=L.scrollLeft;k.left=S<g.value,k.right=S>g.value;const ue=Math.abs(S*m)<=(s.left||0),ce=Math.abs(S*m)+L.clientWidth>=L.scrollWidth-(s.right||0)-ye;D==="flex"&&F==="row-reverse"?(X.left=ce,X.right=ue):(X.left=ue,X.right=ce),g.value=S;let B=L.scrollTop;y===c.document&&!B&&(B=c.document.body.scrollTop),k.top=B<p.value,k.bottom=B>p.value;const he=Math.abs(B)<=(s.top||0),ge=Math.abs(B)+L.clientHeight>=L.scrollHeight-(s.bottom||0)-ye;D==="flex"&&F==="column-reverse"?(X.top=ge,X.bottom=he):(X.top=he,X.bottom=ge),p.value=B},j=y=>{var T;if(!c)return;const L=(T=y.target.documentElement)!==null&&T!==void 0?T:y.target;E(L),w.value=!0,W(y),o(y)};return q(e,"scroll",a?tt(j,a,!0,!1):j,l),se(()=>{try{const y=Y(e);if(!y)return;E(y)}catch(y){u(y)}}),d!=null&&d.mutation&&e!=null&&e!==c&&e!==document&&st(e,()=>{const y=Y(e);y&&E(y)},{attributes:!0,childList:!0,subtree:!0}),q(e,"scrollend",H,l),{x:b,y:v,isScrolling:w,arrivedState:X,directions:k,measure(){const y=Y(e);c&&y&&E(y)}}}function ha(e,t,a={}){const{window:n=$}=a;return Te(e,t,n==null?void 0:n.localStorage,a)}function ga(e={}){const{controls:t=!1,scheduler:a=lt}=e,n=_(new Date),o=a(()=>n.value=new Date);return t?{now:n,...o}:n}function pa(e){const t=Oe("(prefers-reduced-motion: reduce)",e);return O(()=>t.value?"reduce":"no-preference")}function fa(e,t,a={}){const{window:n=$}=a;return Te(e,t,n==null?void 0:n.sessionStorage,a)}const Yt=e=>{let t=e>>>0;return()=>{t=t+1831565813>>>0;let a=t;return a=Math.imul(a^a>>>15,a|1),a^=a+Math.imul(a^a>>>7,a|61),((a^a>>>14)>>>0)/4294967296}},ba=e=>{const t=Yt(e),a=(n,i)=>n+(i-n)*t();return{next:t,range:a,int:(n,i)=>Math.floor(a(n,i+1)),chance:n=>t()<n,vary:(n,i=.12)=>n*a(1-i,1+i)}},Ae=(e,t=0,a=1)=>Math.min(a,Math.max(t,e)),Me=(e,t,a)=>e+(t-e)*a,G=/\s*(?:(\d+\.?\d*(?:e[+-]?\d+)?|\.\d+)|([A-Za-z_]\w*)|(<=|>=|==|!=|[-+*/%<>(),[\]]))/iy,Rt=e=>{const t=e.trim(),a=[];for(G.lastIndex=0;G.lastIndex<t.length;){const n=G.exec(t);if(!n)throw new Error(`cannot read "${t.slice(G.lastIndex).trim()}" in expression "${t}"`);a.push(n[1]!==void 0?{number:Number(n[1])}:n[2]!==void 0?{name:n[2]}:{op:n[3]})}return a},ee={"<":[1,(e,t)=>e<t],">":[1,(e,t)=>e>t],"<=":[1,(e,t)=>e<=t],">=":[1,(e,t)=>e>=t],"==":[1,(e,t)=>e===t],"!=":[1,(e,t)=>e!==t],"+":[2,(e,t)=>e+t],"-":[2,(e,t)=>e-t],"*":[3,(e,t)=>e*t],"/":[3,(e,t)=>e/t],"%":[3,(e,t)=>e%t]},Xt={range:(e,t,a)=>e.random.range(t,a),int:(e,t,a)=>e.random.int(t,a),chance:(e,t)=>e.random.chance(t),vary:(e,t,a)=>e.random.vary(t,a),cos:(e,t)=>Math.cos(t),sin:(e,t)=>Math.sin(t),sqrt:(e,t)=>Math.sqrt(t),abs:(e,t)=>Math.abs(t),hypot:(e,...t)=>Math.hypot(...t),min:(e,...t)=>Math.min(...t),max:(e,...t)=>Math.max(...t),lerp:(e,t,a,n)=>Me(t,a,n),clamp:(e,t,a,n)=>Ae(t,a,n),len:(e,t)=>t.length},me=new Map,wt=e=>{const t=me.get(e);if(t)return t;const a=Rt(e);let n=0;const i=()=>a[n],o=()=>a[n++],s=d=>{const g=o();if(!g||g.op!==d)throw new Error(`expected "${d}" in expression "${e}"`)},r=()=>{const d=[];if(i()&&i().op===")")return o(),d;do d.push(c(0));while(i()&&i().op===","&&o());return s(")"),d},l=()=>{const d=o();if(!d)throw new Error(`expression "${e}" ends early`);const g=d.number;if(g!==void 0)return()=>g;if(d.op==="-"){const p=h();return(b,v)=>-p(b,v)}if(d.op==="("){const p=c(0);return s(")"),p}if(d.name!==void 0){const p=d.name;if(i()&&i().op==="("){o();const b=r();if(p==="if")return(R,w)=>b[0](R,w)?b[1](R,w):b[2](R,w);const v=Xt[p];if(!v)throw new Error(`unknown function "${p}" in expression "${e}"`);return(R,w)=>v(w,...b.map(X=>X(R,w)))}return p==="PI"?()=>Math.PI:b=>{if(!(p in b))throw new Error(`unknown name "${p}" in expression "${e}"`);return b[p]}}throw new Error(`unexpected "${d.op}" in expression "${e}"`)},h=()=>{let d=l();for(;i()&&i().op==="[";){o();const g=c(0);s("]");const p=d;d=(b,v)=>p(b,v)[g(b,v)]}return d},c=d=>{let g=h();for(;i()&&ee[i().op??""]&&ee[i().op??""][0]>=d;){const[p,b]=ee[o().op??""],v=c(p+1),R=g;g=(w,X)=>b(R(w,X),v(w,X))}return g},u=c(0);if(n<a.length)throw new Error(`unexpected "${JSON.stringify(a[n])}" in expression "${e}"`);return me.set(e,u),u},te=e=>Object.create(e),I=(e,t,a)=>{const n=Object.keys(e).filter(i=>!t.includes(i));if(n.length)throw new Error(`${a} has unknown key "${n[0]}"`)},x=(e,t,a)=>{if(typeof e=="number")return e;if(typeof e=="string")return wt(e)(t,a);if(Array.isArray(e))return e.map(n=>x(n,t,a));if(e&&typeof e=="object"&&"if"in e)return I(e,["if","then","else"],"value {if}"),x(x(e.if,t,a)?e.then:e.else,t,a);if(e&&typeof e=="object"&&e.map){const n=e.map;return I(n,["count","values","as","let","value"],"value {map}"),xe(n,t,a).map(i=>(le(n.let,i,a),x(n.value,i,a)))}throw new Error(`cannot evaluate ${JSON.stringify(e)}`)},le=(e,t,a)=>{Object.entries(e||{}).forEach(([n,i])=>{t[n]=x(i,t,a)})},xe=(e,t,a)=>(e.values!==void 0?x(e.values,t,a):Array.from({length:x(e.count,t,a)},(i,o)=>o)).map((i,o)=>{const s=te(t);return s.index=o,e.as&&(s[e.as]=i),s}),ve={ellipse:e=>{const[t,a,n,i,o=0]=e;return{kind:"ellipse",x:t,y:a,radiusX:n,radiusY:i,angle:o}},circle:e=>{const[t,a,n]=e;return{kind:"ellipse",x:t,y:a,radiusX:n,radiusY:n,angle:0}},polygon:e=>({kind:"polygon",points:e}),blob:e=>({kind:"blob",points:e}),tube:(e,t,a,n)=>({kind:"tube",points:e,radii:[].concat(x(t.radii,a,n))})},Q=(e,t,a,n)=>{e.forEach(i=>{const o=Object.keys(ve).find(s=>s in i);if(o){I(i,[o,"radii","cut","part"],`shape {${o}}`);const s=ve[o](x(i[o],t,a),i,t,a);n.push(i.cut?Object.assign({},s,{cut:!0}):s)}else if("let"in i)I(i,["let","part"],"statement {let}"),le(i.let,t,a);else if("repeat"in i){I(i,["repeat","part"],"statement {repeat}");const s=i.repeat;I(s,["count","values","as","body"],"repeat"),xe(s,t,a).forEach(r=>Q(s.body||[],r,a,n))}else if("if"in i)I(i,["if","then","else","part"],"statement {if}"),Q((x(i.if,t,a)?i.then:i.else)||[],te(t),a,n);else if("use"in i){I(i,["use","with","part"],"statement {use}");const s=a.defs[i.use];if(!s)throw new Error(`unknown def "${i.use}"`);const r=te(t);Object.entries(i.with||{}).forEach(([l,h])=>{r[l]=x(h,t,a)}),Q(s,r,a,n)}else throw new Error(`unknown statement ${JSON.stringify(i)}`)})},_t=(e,t)=>{const a={...e};return Object.entries(t||{}).forEach(([n,i])=>{const o=a[n]||{};a[n]={...o,...i,params:{...o.params,...i.params},drop:[...o.drop||[],...i.drop||[]],body:[...o.body||[],...i.body||[]]}}),a},Ye=new WeakMap,de=(e,t,a=[])=>{const n=Ye.get(e)||new Map;Ye.set(e,n);const i=n.get(t);if(i)return i;const o=e[t];if(!o)throw new Error(`unknown species "${t}"`);if(a.includes(t))throw new Error(`species "${t}" extends itself through ${a.join(" > ")}`);const s=o.extends?de(e,o.extends,[...a,t]):{params:{},defs:{},body:[],poses:{}},r={name:t,params:{...s.params,...o.params},defs:{...s.defs,...o.defs},body:[...s.body.filter(l=>!(o.drop||[]).includes(l.part??"")),...o.body||[]],poses:_t(s.poses,o.poses)};return n.set(t,r),r},He=e=>Object.entries(e.poses).map(([t,a])=>[t,a.weight===void 0?1:a.weight]).filter(([,t])=>t>0),kt=(e,t)=>{if(e.length<2)return e.length?e[0][0]:"";let a=t.next()*e.reduce((i,[,o])=>i+o,0);return(e.find(([,i])=>(a-=i,a<0))||e[e.length-1])[0]},ya=(e,t,a,n={})=>{const i=de(e,t),o=He(i),s=n.pose&&o.some(([d])=>d===n.pose)?n.pose:kt(o,a),r=i.poses[s]||{},l=new Set(r.drop||[]),h={random:a,defs:i.defs},c={},u=[];try{le({...i.params,...r.params},c,h),Q([...i.body.filter(d=>!l.has(d.part??"")),...r.body||[]],c,h,u)}catch(d){throw new Error(`species "${t}"${s?` (pose ${s})`:""}: ${d.message}`,{cause:d})}return{shapes:u,pose:s}},ma=e=>Object.keys(e).filter(t=>!e[t].abstract),va=(e,t)=>{const a=He(de(e,t)).map(([n])=>n);return a.length?a:[""]},A=256,ae=2.6,Re=A/ae,St=(e,t,a,n,i)=>{const o=i*i,s=o*i,r=l=>.5*(2*t[l]+(-e[l]+a[l])*i+(2*e[l]-5*t[l]+4*a[l]-n[l])*o+(-e[l]+3*t[l]-3*a[l]+n[l])*s);return[r(0),r(1)]},Xe=(e,t,a)=>{const n=e.length,i=r=>t?e[(r+n)%n]:e[Ae(r,0,n-1)],o=t?n:n-1,s=[];for(let r=0;r<o;r+=1)for(let l=0;l<a;l+=1)s.push(St(i(r-1),i(r),i(r+1),i(r+2),l/a));return t||s.push(e[n-1]),s},Lt=(e,t)=>{if(e.length===1)return e[0];const a=t*(e.length-1),n=Math.min(e.length-2,Math.floor(a));return Me(e[n],e[n+1],a-n)},Ce=(e,t)=>{if(e.globalCompositeOperation=t.cut?"destination-out":"source-over",e.beginPath(),t.kind==="ellipse")e.ellipse(t.x,t.y,t.radiusX,t.radiusY,t.angle,0,Math.PI*2),e.fill();else if(t.kind==="polygon"||t.kind==="blob")(t.kind==="blob"?Xe(t.points,!0,12):t.points).forEach(([n,i],o)=>o===0?e.moveTo(n,i):e.lineTo(n,i)),e.closePath(),e.fill();else if(t.kind==="tube"){const a=Xe(t.points,!1,24);a.forEach(([n,i],o)=>{const s=Lt(t.radii,o/Math.max(1,a.length-1));e.moveTo(n+s,i),e.arc(n,i,s,0,Math.PI*2)}),e.fill()}},Ya=(e,t)=>{const a=document.createElement("canvas");a.width=A,a.height=A;const n=a.getContext("2d",{willReadFrequently:!0});if(!n)return new Uint8Array(A*A);n.translate(A/2,A/2),n.scale(Re*t.facing*t.stretchX,Re*t.stretchY),n.rotate(t.tilt),n.fillStyle="#000",e.forEach(s=>Ce(n,s));const i=n.getImageData(0,0,A,A).data,o=new Uint8Array(A*A);for(let s=0;s<o.length;s+=1)o[s]=i[s*4+3]>127?1:0;return o},Ra=(e,t,a)=>{const n=e.getContext("2d");n&&(n.clearRect(0,0,e.width,e.height),n.save(),n.translate(e.width/2,e.height/2),n.scale(e.width/ae,e.width/ae),n.fillStyle=a,t.forEach(i=>Ce(n,i)),n.restore())},Wt=!0,$t={groundY:.8,bodyRadiusX:"range(0.4, 0.46)",bodyRadiusY:"range(0.27, 0.32)",bodyAngle:-.45,legLength:"range(0.28, 0.34)",bodyY:"groundY - legLength - bodyRadiusY",headRadius:"range(0.15, 0.18)",headX:"bodyRadiusX * 0.55",headY:"bodyY - bodyRadiusY * 0.95 - headRadius * 0.4",neckWidth:"headRadius * 0.85",beakLength:"range(0.17, 0.23)",beakHeight:.09,tailLength:"range(0.3, 0.4)",tailSpread:.14,tailDrop:.18,legWidth:.022,wingWidth:.12},Et=[{ellipse:[0,"bodyY","bodyRadiusX","bodyRadiusY","bodyAngle"],part:"body"},{tube:[["bodyRadiusX * 0.25","bodyY - bodyRadiusY * 0.4"],["headX","headY + headRadius * 0.4"]],radii:["bodyRadiusY * 0.7","neckWidth"],part:"neck"},{circle:["headX","headY","headRadius"],part:"head"},{polygon:[["headX + headRadius * 0.7","headY - beakHeight * 0.6"],["headX + headRadius + beakLength","headY + beakHeight * 0.3"],["headX + headRadius * 0.7","headY + beakHeight * 0.7"]],part:"head"},{circle:["headX + headRadius * 0.3","headY - headRadius * 0.15",.025],cut:!0,part:"eye"},{polygon:[["-bodyRadiusX * 0.6","bodyY - tailSpread * 0.4"],["-bodyRadiusX * 0.6 - tailLength","bodyY + tailDrop - tailSpread"],["-bodyRadiusX * 0.6 - tailLength * 0.95","bodyY + tailDrop + tailSpread * 0.4"],["-bodyRadiusX * 0.3","bodyY + tailSpread * 0.6"]],part:"tail"},{tube:[["-bodyRadiusX * 0.55","bodyY - bodyRadiusY * 0.15"],["-bodyRadiusX * 0.1","bodyY + bodyRadiusY * 0.3"],["bodyRadiusX * 0.35","bodyY - bodyRadiusY * 0.05"]],radii:.012,cut:!0,part:"wing"},{repeat:{values:[-.08,.1],as:"legShift",body:[{tube:[["legShift","bodyY + bodyRadiusY * 0.6"],["legShift","groundY"]],radii:"legWidth"},{ellipse:["legShift + 0.05","groundY",.07,.02]}]},part:"legs"}],jt={perched:{weight:3},sleeping:{weight:1,params:{headX:"bodyRadiusX * 0.35",headY:"bodyY - bodyRadiusY * 0.55",beakLength:.1,bodyAngle:-.2},drop:["neck","eye","wing"],body:[{tube:[["headX + headRadius * 0.4","headY - headRadius * 0.1"],["headX + headRadius * 0.65","headY - headRadius * 0.05"]],radii:.012,cut:!0}]},front:{weight:1,params:{headX:0,headY:"bodyY - bodyRadiusX * 0.8 - headRadius * 0.2"},drop:["body","neck","head","eye","tail","wing","legs"],body:[{ellipse:[0,"bodyY - 0.02","bodyRadiusY * 1.25","bodyRadiusX * 0.95"]},{circle:["headX","headY","headRadius * 1.1"]},{polygon:[["headX - beakHeight","headY + headRadius * 0.15"],["headX + beakHeight","headY + headRadius * 0.15"],["headX","headY + headRadius * 0.15 + beakLength * 0.6"]]},{repeat:{values:[-1,1],as:"side",body:[{circle:["headX + side * headRadius * 0.45","headY - headRadius * 0.2",.027],cut:!0},{ellipse:["side * bodyRadiusY * 1.15","bodyY + 0.02","wingWidth * 0.9","bodyRadiusX * 0.6","side * -0.15"]},{tube:[["side * 0.1","groundY - legLength"],["side * 0.1","groundY"]],radii:"legWidth"},{ellipse:["side * 0.12","groundY",.07,.02]}]}}]}},Dt={abstract:Wt,params:$t,body:Et,poses:jt},Ot=!0,Tt={groundY:.7,bodyRadiusX:"range(0.5, 0.56)",bodyRadiusY:"range(0.26, 0.31)",legLength:"range(0.4, 0.5)",legWidth:.07,stride:0,bodyY:"groundY - bodyRadiusY - legLength",headRadiusX:"range(0.19, 0.22)",headRadiusY:"range(0.16, 0.18)",headX:"bodyRadiusX * 0.95",headY:"bodyY - bodyRadiusY * 0.8",neckWidth:"headRadiusY * 0.9",snoutLength:.16,snoutRadius:.08,earHeight:"range(0.16, 0.22)",earWidth:.09,earSpread:"range(0.1, 0.3)",eyeSize:.028,tailLength:"range(0.35, 0.45)",tailLift:"range(0.05, 0.2)",tailWidth:.06},At={ears:[{repeat:{values:[-.5,.3],as:"shift",body:[{polygon:[["headX + headRadiusX * shift - earWidth","headY - headRadiusY * 0.5"],["headX + headRadiusX * shift - earWidth * 0.2","headY - headRadiusY * 0.75 - earHeight"],["headX + headRadiusX * shift + earWidth","headY - headRadiusY * 0.7"]]}]}}]},Mt=[{ellipse:[0,"bodyY","bodyRadiusX","bodyRadiusY"],part:"body"},{tube:[["bodyRadiusX * 0.5","bodyY - bodyRadiusY * 0.2"],["headX - headRadiusX * 0.3","headY + headRadiusY * 0.2"]],radii:["bodyRadiusY * 0.8","neckWidth"],part:"neck"},{ellipse:["headX","headY","headRadiusX","headRadiusY"],part:"head"},{ellipse:["headX + headRadiusX * 0.75","headY + headRadiusY * 0.35","snoutLength","snoutRadius"],part:"head"},{use:"ears",part:"head"},{circle:["headX + headRadiusX * 0.4","headY - headRadiusY * 0.15","eyeSize"],cut:!0,part:"eye"},{tube:[["-bodyRadiusX * 0.85","bodyY - bodyRadiusY * 0.2"],["-bodyRadiusX * 0.85 - tailLength * 0.5","bodyY - bodyRadiusY * 0.2 - tailLift * 0.5"],["-bodyRadiusX * 0.85 - tailLength","bodyY - bodyRadiusY * 0.2 - tailLift"]],radii:["tailWidth","tailWidth * 0.9","tailWidth * 0.6"],part:"tail"},{repeat:{values:[-.72,-.5,.4,.68],as:"along",body:[{let:{swing:"if(index % 2 == 0, stride, -stride)",legX:"along * bodyRadiusX",hipY:"bodyY + bodyRadiusY * 0.5"}},{tube:[["legX","hipY"],["legX + swing","(hipY + groundY) / 2"],["legX + swing * 1.2","groundY"]],radii:["legWidth * 1.25","legWidth * 0.9","legWidth * 0.8"]}]},part:"legs"}],xt={standing:{weight:3},walking:{weight:2,params:{stride:"range(0.06, 0.14)"}},sleeping:{weight:1,params:{legLength:.02,headX:"bodyRadiusX * 0.8",headY:"groundY - headRadiusY",tailLift:.02},drop:["legs","eye","neck"],body:[{ellipse:["bodyRadiusX * 0.55","groundY - 0.05","bodyRadiusX * 0.28",.06]},{tube:[["headX + headRadiusX * 0.2","headY - headRadiusY * 0.1"],["headX + headRadiusX * 0.4","headY"],["headX + headRadiusX * 0.6","headY - headRadiusY * 0.1"]],radii:.012,cut:!0}]},sitting:{weight:1,params:{headX:"bodyRadiusY * 0.5",headY:"groundY - bodyRadiusX * 1.55 - headRadiusY * 0.4"},drop:["body","neck","legs","tail"],body:[{ellipse:["bodyRadiusY * 0.2","groundY - bodyRadiusX * 0.85","bodyRadiusY * 1.05","bodyRadiusX * 0.9",.12]},{ellipse:["-bodyRadiusY * 0.55","groundY - bodyRadiusY * 0.55","bodyRadiusY * 1.2","bodyRadiusY * 0.62"]},{tube:[["bodyRadiusY * 0.75","groundY - bodyRadiusX * 1.0"],["bodyRadiusY * 0.8","groundY"]],radii:"legWidth * 1.1"},{tube:[["-bodyRadiusY * 1.2","groundY - 0.05"],["-bodyRadiusY * 1.2 - tailLength","groundY - 0.03"]],radii:["tailWidth","tailWidth * 0.6"]}]},front:{weight:1,params:{headX:0,headY:"groundY - bodyRadiusX * 1.1 - headRadiusY * 0.9"},drop:["body","neck","head","eye","legs","tail"],body:[{ellipse:[0,"groundY - bodyRadiusX * 0.7","bodyRadiusY * 1.25","bodyRadiusX * 0.75"]},{ellipse:["headX","headY","headRadiusX * 1.15","headRadiusY * 1.1"]},{ellipse:["headX","headY + headRadiusY * 0.45","snoutRadius * 1.5","snoutRadius"]},{repeat:{values:[-1,1],as:"side",body:[{polygon:[["headX + side * headRadiusX * 1.05","headY - headRadiusY * 0.3"],["headX + side * headRadiusX * (0.75 + earSpread)","headY - headRadiusY * 0.8 - earHeight"],["headX + side * headRadiusX * 0.3","headY - headRadiusY * 1.0"]]},{circle:["headX + side * headRadiusX * 0.5","headY - headRadiusY * 0.15","eyeSize * 1.2"],cut:!0},{tube:[["side * bodyRadiusY * 0.55","groundY - bodyRadiusX * 0.4"],["side * bodyRadiusY * 0.55","groundY"]],radii:"legWidth * 1.3"}]}},{circle:["headX","headY + headRadiusY * 0.3","snoutRadius * 0.35"],cut:!0}]}},Ht={abstract:Ot,params:Tt,defs:At,body:Mt,poses:xt},Ct=!0,Pt={length:"range(0.85, 0.95)",height:"range(0.36, 0.42)",arch:0,dorsalHeight:"range(0.2, 0.28)",finLength:.22,flukeSpan:.32,eyeX:.58,eyeSize:.035},Nt=[{blob:[["length","arch * 0.6"],["length * 0.55","-height * 0.8 - arch * 0.2"],["-length * 0.2","-height * 0.7 - arch * 0.2"],["-length * 0.75","-height * 0.12 + arch * 0.6"],["-length * 0.75","height * 0.12 + arch * 0.6"],["-length * 0.2","height * 0.8 - arch * 0.4"],["length * 0.5","height * 0.75 - arch * 0.2"]]},{polygon:[["-length * 0.7","arch * 0.6"],["-length - flukeSpan * 0.55","arch * 0.6 - flukeSpan"],["-length - flukeSpan * 0.3","arch * 0.6"],["-length - flukeSpan * 0.55","arch * 0.6 + flukeSpan"]],part:"fluke"},{polygon:[["length * 0.3","-height * 0.75 - arch * 0.2"],["-length * 0.2","-height * 0.7 - arch * 0.2 - dorsalHeight"],["-length * 0.35","-height * 0.65 - arch * 0.2"]]},{ellipse:["length * 0.2","height * 0.6 - arch * 0.3","finLength",.06,.6]},{circle:["length * eyeX","-height * 0.1 + arch * 0.4","eyeSize"],cut:!0}],Ft={cruising:{weight:3},leaping:{weight:0,params:{arch:.38}}},It={abstract:Ct,params:Pt,body:Nt,poses:Ft},zt=[{let:{humpHeight:"range(0.18, 0.3)",headDrop:"range(-0.08, 0.12)",legLength:"range(0.68, 0.8)"}},{ellipse:[-.1,0,"vary(0.62, 0.06)","vary(0.38, 0.06)"]},{ellipse:[.2,-.12,.32,"humpHeight + 0.1"]},{ellipse:[.62,"-0.02 + headDrop",.27,.23,.15]},{ellipse:[.82,"0.08 + headDrop",.12,.09,.5]},{circle:[.5,"-0.24 + headDrop",.1]},{circle:[.68,"-0.26 + headDrop",.09]},{circle:[-.72,-.08,.07]},{circle:[.7,"-0.04 + headDrop",.03],cut:!0},{repeat:{values:[-.55,-.3,.15,.36],as:"legX",body:[{tube:[["legX",.15],["legX + range(-0.04, 0.04)","legLength"]],radii:[.14,.12]},{ellipse:["legX + 0.06","legLength + 0.03",.14,.06]}]}}],Bt={body:zt},qt={sitting:{weight:.55,body:[{let:{headX:"range(0.12, 0.28)",headY:"range(-0.58, -0.45)",earHeight:"range(0.26, 0.4)",earTilt:"range(-0.08, 0.08)",curlsForward:"chance(0.5)",tailPoints:{if:"curlsForward",then:[[-.5,.78],[-.2,.92],[.25,.9],["range(0.45, 0.6)","range(0.7, 0.85)"]],else:[[-.55,.7],[-.88,.62],["-range(0.9, 1.05)",.3],["-range(0.75, 0.95)","range(-0.05, 0.15)"]]}}},{ellipse:[-.15,.45,"vary(0.48)","vary(0.4)"]},{ellipse:[.1,.05,"vary(0.3)",.5,-.15]},{ellipse:["headX","headY","vary(0.27, 0.08)","vary(0.23, 0.08)"]},{polygon:[["headX - 0.25","headY - 0.04"],["headX - 0.2 + earTilt","headY - 0.12 - earHeight"],["headX - 0.02","headY - 0.18"]]},{polygon:[["headX + 0.04","headY - 0.18"],["headX + 0.22 + earTilt","headY - 0.12 - earHeight"],["headX + 0.27","headY - 0.03"]]},{tube:[[.14,.3],[.16,.86]],radii:.07},{tube:[[.3,.3],[.33,.86]],radii:.065},{tube:"tailPoints",radii:[.075,.07,.065,.055]},{ellipse:["headX - 0.1","headY - 0.01",.045,.03],cut:!0},{ellipse:["headX + 0.1","headY - 0.01",.045,.03],cut:!0}]},walking:{weight:.45,body:[{let:{tailHeight:"range(0.6, 0.95)",tailHook:"range(-0.25, 0.15)",stride:"range(0.05, 0.16)",headY:"range(-0.32, -0.18)"}},{ellipse:[-.05,.05,"vary(0.58, 0.08)","vary(0.22)"]},{ellipse:[.62,"headY",.21,.19]},{polygon:[[.45,"headY - 0.08"],[.47,"headY - 0.38"],[.6,"headY - 0.17"]]},{polygon:[[.64,"headY - 0.17"],[.76,"headY - 0.38"],[.8,"headY - 0.06"]]},{tube:[[-.45,.1],["-0.45 - stride",.42],["-0.42 - stride",.68]],radii:[.08,.055,.05]},{tube:[[-.25,.12],["-0.22 + stride",.45],["-0.2 + stride",.68]],radii:[.07,.05,.05]},{tube:[[.3,.12],["0.3 + stride",.45],["0.32 + stride",.68]],radii:[.07,.05,.05]},{tube:[[.42,.12],["0.42 - stride",.45],["0.43 - stride",.68]],radii:[.07,.05,.05]},{tube:[[-.58,-.02],[-.85,-.25],[-.85,"-tailHeight + 0.2"],["-0.7 + tailHook","-tailHeight"]],radii:[.06,.055,.05,.045]},{ellipse:[.68,"headY - 0.02",.035,.022],cut:!0}]}},Vt={poses:qt},Jt={antler:[{let:{height:"range(0.42, 0.6)"}},{let:{beam:[["rootX","rootY"],["rootX - 0.1 * spread","rootY - height * 0.4"],["rootX - 0.06 * spread","rootY - height * 0.8"],["rootX + 0.08 * spread","rootY - height"]]}},{tube:"beam",radii:[.04,.036,.032,.026]},{repeat:{count:"int(2, 4)",body:[{let:{anchor:"beam[min(3, index + 1)]",length:"range(0.12, 0.2)"}},{tube:["anchor",["anchor[0] + length * 0.7 * spread","anchor[1] - length"]],radii:[.03,.024]}]}}]},Kt=[{let:{neckLean:"range(0.4, 0.55)",headY:"range(-0.55, -0.42)",legLength:"range(0.85, 0.95)",headX:"neckLean + 0.08"}},{ellipse:[-.1,.05,"vary(0.48, 0.06)","vary(0.21, 0.08)"]},{tube:[[.25,0],["neckLean - 0.05",-.25],["neckLean","headY + 0.05"]],radii:[.13,.09,.075]},{ellipse:["headX","headY",.14,.09,.4]},{ellipse:["headX + 0.13","headY + 0.07",.09,.06,.4]},{ellipse:["headX - 0.13","headY - 0.06",.1,.035,-.5]},{ellipse:[-.58,-.04,.07,.04,-.5]},{tube:[[-.45,.12],[-.5,.5],[-.42,"legLength"]],radii:[.055,.035,.03]},{tube:[[-.32,.14],[-.34,.5],[-.3,"legLength"]],radii:[.05,.035,.03]},{tube:[[.2,.15],[.22,.55],[.2,"legLength"]],radii:[.045,.035,.03]},{tube:[[.32,.12],[.36,.52],[.33,"legLength"]],radii:[.045,.035,.03]},{circle:["headX + 0.02","headY - 0.02",.025],cut:!0},{use:"antler",with:{rootX:"headX - 0.06",rootY:"headY - 0.06",spread:-.8}},{use:"antler",with:{rootX:"headX + 0.04",rootY:"headY - 0.06",spread:1.3}}],Ut={defs:Jt,body:Kt},Gt={length:"range(0.8, 0.88)",height:"range(0.2, 0.25)",dorsalHeight:.2,flukeSpan:.36,finLength:.2,eyeX:.62,eyeSize:.025},Qt=["fluke"],Zt=[{ellipse:["-length * 0.85","arch * 0.6 - flukeSpan * 0.45","flukeSpan * 0.55",.07,-.95],part:"fluke"},{ellipse:["-length * 0.85","arch * 0.6 + flukeSpan * 0.45","flukeSpan * 0.55",.07,.95],part:"fluke"},{ellipse:["length + 0.03","height * 0.35 + arch * 0.6",.15,.06,.12]}],e0={leaping:{weight:1}},t0={extends:"swimmer",params:Gt,drop:Qt,body:Zt,poses:e0},a0={bodyRadiusX:"range(0.5, 0.56)",bodyRadiusY:"range(0.27, 0.3)",bodyAngle:-.1,legLength:.16,headRadius:.15,headX:"bodyRadiusX * 0.7",headY:"bodyY - bodyRadiusY - 0.12",neckWidth:.1,beakLength:.17,beakHeight:.07,tailLength:.2,tailSpread:.1,tailDrop:-.12,wingWidth:.15},n0={extends:"bird",params:a0},i0=[{let:{earSize:"range(0.9, 1.15)",trunkCurl:"range(-0.1, 0.25)",headY:"range(-0.32, -0.2)",legLength:"range(0.76, 0.86)"}},{ellipse:[-.15,0,"vary(0.6, 0.06)","vary(0.4, 0.06)"]},{ellipse:[.45,"headY",.3,.3]},{tube:[[-.73,-.1],[-.8,.15],[-.8,.38]],radii:[.025,.02,.035]},{repeat:{values:[-.6,-.36,.05,.26],as:"legX",body:[{tube:[["legX",.2],["legX","legLength"]],radii:.12}]}},{tube:[[.6,"headY + 0.28"],[.78,"headY + 0.38"],[.92,"headY + 0.3"]],radii:.035,cut:!0},{tube:[[.68,"headY - 0.02"],[.84,"headY + 0.25"],[.86,"headY + 0.6"],["0.9 + trunkCurl","headY + 0.92"],["1.02 + trunkCurl","headY + 0.85"]],radii:[.12,.1,.075,.055,.045]},{let:{earPoints:{map:{values:[[.12,-.52],[.38,-.6],[.48,-.22],[.38,.16],[.14,.1],[.02,-.2]],as:"point",value:["0.27 + (point[0] - 0.27) * earSize","headY + 0.05 + (point[1] + 0.2) * earSize"]}}}},{blob:"earPoints"},{tube:["earPoints[1]","earPoints[2]","earPoints[3]"],radii:.022,cut:!0},{circle:[.58,"headY - 0.04",.035],cut:!0}],o0={body:i0},s0={height:"range(0.4, 0.48)",dorsalHeight:.22,eyeX:.62,eyeSize:.05},r0={extends:"swimmer",params:s0},l0=["neck"],d0={bodyRadiusX:"range(0.3, 0.34)",bodyRadiusY:.2,bodyAngle:-.12,legLength:"range(0.75, 0.85)",headRadius:.07,headX:"range(0.38, 0.48)",headY:"bodyY - bodyRadiusY - range(0.7, 0.8)",beakLength:.12,beakHeight:.05,tailLength:.12,tailSpread:.06,tailDrop:.02,legWidth:.026,wingWidth:.08},u0=[{tube:[["bodyRadiusX * 0.55","bodyY - bodyRadiusY * 0.4"],["bodyRadiusX * 0.9","bodyY - bodyRadiusY - 0.28"],["headX - 0.14","headY + 0.3"],["headX - 0.02","headY + 0.08"]],radii:[.075,.04,.035,.03],part:"neck"}],c0={sleeping:{weight:0},front:{weight:0}},h0={extends:"bird",drop:l0,params:d0,body:u0,poses:c0},g0=[{let:{earHeight:"range(0.28, 0.4)",snout:"range(0.95, 1.1)",tailDroop:"range(-0.15, 0.25)",tailBush:"range(0.13, 0.19)",legLength:"range(0.6, 0.72)"}},{ellipse:[-.05,.05,"vary(0.48, 0.08)","vary(0.19)"]},{ellipse:[.35,0,.18,.22]},{blob:[[.28,-.12],[.52,-.32],[.72,-.28],["snout",-.12],[.75,-.04],[.48,.14]]},{polygon:[[.48,-.26],[.5,"-0.28 - earHeight"],[.63,-.31]]},{polygon:[[.6,-.3],[.7,"-0.28 - earHeight * 0.95"],[.75,-.25]]},{tube:[[-.45,.02],[-.75,"0.12 + tailDroop * 0.5"],[-1,"0.08 + tailDroop"],[-1.15,"-0.06 + tailDroop"]],radii:[.06,"tailBush","tailBush * 0.85",.025]},{ellipse:[.7,-.17,.035,.022,-.3],cut:!0},{repeat:{values:[-.4,-.25,.28,.4],as:"legX",body:[{let:{step:"if(index % 2 == 0, range(-0.08, 0.08), 0)"}},{tube:[["legX",.12],["legX + step","legLength"]],radii:[.05,.035]}]}}],p0={body:g0},f0=[{let:{bodyY:"range(0.05, 0.15)",bodyRadiusX:"range(0.6, 0.72)",bodyRadiusY:"range(0.3, 0.38)",spikeCount:"int(11, 17)",spikeLength:"range(0.3, 0.45)",sweep:"range(0.2, 0.55)",frontAngle:"PI * range(1.62, 1.75)"}},{ellipse:[0,"bodyY","bodyRadiusX","bodyRadiusY"]},{repeat:{count:"spikeCount",body:[{let:{angle:"lerp(PI * 0.88, frontAngle, index / (spikeCount - 1)) + range(-0.04, 0.04)",baseX:"cos(angle) * bodyRadiusX * 0.9",baseY:"bodyY + sin(angle) * bodyRadiusY * 0.9",directionX:"cos(angle) - sweep",directionY:"sin(angle) * 1.2",directionLength:"hypot(directionX, directionY)",length:"spikeLength * range(0.7, 1.25)",halfWidth:"range(0.07, 0.1)",tangentX:"-directionY / directionLength",tangentY:"directionX / directionLength"}},{polygon:[["baseX - tangentX * halfWidth","baseY - tangentY * halfWidth"],["baseX + directionX / directionLength * (length + 0.08)","baseY + directionY / directionLength * (length + 0.08)"],["baseX + tangentX * halfWidth","baseY + tangentY * halfWidth"]]}]}},{let:{snoutX:"bodyRadiusX * 0.85",snoutReach:"range(0.28, 0.42)"}},{ellipse:["snoutX - 0.05","bodyY + 0.12",.3,.2,.3]},{polygon:[["snoutX - 0.05","bodyY - 0.02"],["snoutX + snoutReach","bodyY + 0.25"],["snoutX - 0.1","bodyY + 0.3"]]},{circle:["snoutX + snoutReach - 0.02","bodyY + 0.25",.055]},{repeat:{values:[-.42,-.15,.22,.45],as:"legX",body:[{tube:[["legX","bodyY + bodyRadiusY * 0.7"],["legX + 0.04","bodyY + bodyRadiusY + range(0.08, 0.14)"]],radii:.06}]}},{circle:["snoutX + 0.04","bodyY + 0.08",.04],cut:!0}],b0={body:f0},y0=["neck"],m0={bodyRadiusX:"range(0.6, 0.68)",bodyRadiusY:"range(0.3, 0.34)",legLength:"range(0.6, 0.7)",legWidth:.055,headRadiusX:.17,headRadiusY:.14,headX:"bodyRadiusX * 0.95 + 0.22",headY:"bodyY - bodyRadiusY - 0.32",neckWidth:.1,snoutLength:.24,snoutRadius:.09,earHeight:.1,earWidth:.05,tailLength:.3,tailLift:"range(-0.6, -0.4)",tailWidth:.07},v0=[{tube:[["bodyRadiusX * 0.55","bodyY - bodyRadiusY * 0.9"],["bodyRadiusX * 0.7","bodyY - bodyRadiusY - 0.2"],["headX - 0.06","headY - headRadiusY * 0.8"]],radii:[.1,.075,.045],part:"neck"}],Y0={sitting:{weight:0},front:{weight:0}},R0={extends:"quadruped",drop:y0,params:m0,body:v0,poses:Y0},X0=[{let:{mantleRadius:"range(0.38, 0.46)",mantleLift:"range(0.35, 0.5)",armCount:"int(7, 8)",sway:"range(0.2, 0.5)"}},{ellipse:[0,"-mantleLift","mantleRadius","mantleRadius * 1.25"]},{ellipse:[0,.05,"mantleRadius * 1.1",.3]},{repeat:{count:"armCount",body:[{let:{spread:"lerp(-0.65, 0.65, index / (armCount - 1))",wave:"if(index % 2 == 0, sway, -sway) * range(0.6, 1.2)",reach:"range(0.5, 0.7)"}},{tube:[["spread * 0.45",.1],["spread * 0.9 + wave * 0.3","reach * 0.5"],["spread * 1.2 + wave","reach"],["spread * 1.1 + wave * 1.4","reach + 0.18"]],radii:[.1,.075,.05,.02]}]}},{circle:["-mantleRadius * 0.4","-mantleLift + 0.15",.07],cut:!0},{circle:["mantleRadius * 0.4","-mantleLift + 0.15",.07],cut:!0}],w0={body:X0},_0={bodyRadiusX:"range(0.56, 0.62)",bodyRadiusY:"range(0.2, 0.24)",legLength:"range(0.16, 0.22)",legWidth:.075,headRadiusX:.17,headRadiusY:.15,headX:"bodyRadiusX + 0.02",headY:"bodyY - bodyRadiusY * 0.7",neckWidth:"bodyRadiusY * 0.8",snoutLength:.11,snoutRadius:.07,earHeight:.04,earWidth:.05,tailLength:"range(0.5, 0.62)",tailLift:"range(-0.1, 0.05)",tailWidth:.11},k0={extends:"quadruped",params:_0},S0=[{let:{tuftHeight:"range(0.3, 0.5)",eyeSize:"range(0.13, 0.17)",lookX:"range(-0.04, 0.04)"}},{ellipse:[0,.15,"vary(0.46, 0.08)","vary(0.6, 0.06)"]},{ellipse:[0,-.4,.44,.33]},{polygon:[[-.42,-.5],[-.5,"-0.5 - tuftHeight"],[-.16,-.66]]},{polygon:[[.42,-.5],[.5,"-0.5 - tuftHeight"],[.16,-.66]]},{ellipse:[-.42,.2,.15,.42,.18]},{ellipse:[.42,.2,.15,.42,-.18]},{tube:[[-1.15,.82],[0,.8],[1.15,"range(0.78, 0.88)"]],radii:.05},{tube:[[-.14,.7],[-.16,.82]],radii:.045},{tube:[[.14,.7],[.16,.82]],radii:.045},{circle:[-.2,-.4,"eyeSize"],cut:!0},{circle:[.2,-.4,"eyeSize"],cut:!0},{circle:["-0.2 + lookX",-.4,"eyeSize * 0.42"]},{circle:["0.2 + lookX",-.4,"eyeSize * 0.42"]},{polygon:[[-.05,-.27],[.05,-.27],[0,-.14]],cut:!0},{repeat:{count:3,as:"row",body:[{repeat:{values:[-1,0,1],as:"column",body:[{let:{featherX:"column * 0.16 + (row % 2) * 0.08 - 0.04",featherY:"0.05 + row * 0.15"}},{tube:[["featherX - 0.05","featherY"],["featherX","featherY + 0.04"],["featherX + 0.05","featherY"]],radii:.014,cut:!0}]}}]}}],L0={body:S0},W0=["wing"],$0={bodyRadiusX:"range(0.55, 0.62)",bodyRadiusY:"range(0.3, 0.34)",bodyAngle:"-PI / 2",legLength:.06,headRadius:.17,headX:.04,headY:"bodyY - bodyRadiusX * 0.85",neckWidth:.15,beakLength:.15,beakHeight:.06,tailLength:.12,tailSpread:.08,tailDrop:.1,wingWidth:.07},E0=[{ellipse:["-bodyRadiusY * 0.95","bodyY + 0.02",.08,"bodyRadiusX * 0.6",.15],part:"flipper"},{ellipse:["bodyRadiusY * 0.95","bodyY + 0.02",.08,"bodyRadiusX * 0.6",-.15],part:"flipper"}],j0={sleeping:{weight:0}},D0={extends:"bird",drop:W0,params:$0,body:E0,poses:j0},O0={bodyRadiusX:"range(0.56, 0.62)",bodyRadiusY:"range(0.36, 0.4)",legLength:"range(0.2, 0.26)",legWidth:.08,headRadiusX:.25,headRadiusY:.23,headX:"bodyRadiusX * 0.95",headY:"bodyY + 0.02",neckWidth:.22,snoutLength:.12,snoutRadius:.12,earHeight:.1,earWidth:.1,earSpread:.1,tailLength:.14,tailLift:"range(0.15, 0.25)",tailWidth:.035},T0={sitting:{weight:0}},A0={extends:"quadruped",params:O0,poses:T0},M0={ear:[{ellipse:["baseX + sin(angle) * length","baseY - cos(angle) * length",.085,"length","angle"]}]},x0=[{let:{earLength:"range(0.38, 0.52)",earLean:"range(-0.5, 0.05)",floppy:"chance(0.3)",headX:"range(0.36, 0.46)",headY:"range(-0.18, -0.05)"}},{ellipse:[-.2,.35,"vary(0.46, 0.08)","vary(0.38, 0.08)"]},{ellipse:[.08,.22,.38,.33]},{ellipse:["headX","headY",.24,.2,.2]},{use:"ear",with:{baseX:"headX - 0.06",baseY:"headY - 0.12",angle:"earLean",length:"earLength"}},{use:"ear",with:{baseX:"headX + 0.04",baseY:"headY - 0.1",angle:"earLean + if(floppy, 1.2, 0.25)",length:"earLength * if(floppy, 0.85, 0.95)"}},{circle:[-.68,.2,"range(0.11, 0.15)"]},{ellipse:[-.08,.72,.3,.07]},{tube:[[.3,.35],[.36,.72]],radii:.06},{circle:["headX + 0.06","headY - 0.04",.035],cut:!0}],H0={defs:M0,body:x0},C0=[{let:{hornLength:"range(0.62, 0.85)",hornLean:"range(-0.12, 0.02)",headDrop:"range(0, 0.12)",legLength:"range(0.68, 0.82)"}},{ellipse:[-.15,.05,"vary(0.62, 0.06)","vary(0.36, 0.08)"]},{ellipse:[.15,-.1,.34,.3]},{blob:[[.3,-.2],[.68,"-0.02 + headDrop"],[1.02,"0.24 + headDrop"],[.98,"0.42 + headDrop"],[.62,"0.42 + headDrop"],[.36,.3]]},{polygon:[[.8,"0.24 + headDrop"],[.84,"0.1 + headDrop"],["0.96 + hornLean","0.24 + headDrop - hornLength"],[1.05,"0.3 + headDrop"]]},{polygon:[[.66,"0.1 + headDrop"],["0.74 + hornLean","0.08 + headDrop - hornLength * 0.45"],[.82,"0.16 + headDrop"]]},{polygon:[[.34,-.14],["range(0.28, 0.34)",-.46],[.48,-.12]]},{tube:[[-.74,-.02],[-.84,.12],[-.83,.28]],radii:.025},{circle:[.6,"0.18 + headDrop",.03],cut:!0},{repeat:{values:[-.56,-.32,.08,.3],as:"legX",body:[{tube:[["legX",.2],["legX + range(-0.03, 0.03)","legLength"]],radii:.12}]}}],P0={body:C0},N0={length:"range(0.85, 0.92)",height:"range(0.2, 0.24)",dorsalHeight:"range(0.35, 0.42)",finLength:.3,flukeSpan:.4,eyeX:.68,eyeSize:.022},F0={extends:"swimmer",params:N0},I0=[{let:{shellRadius:"range(0.48, 0.6)",shellX:"range(-0.25, -0.1)",shellY:"0.55 - shellRadius - range(0.02, 0.08)",turns:"range(2.6, 3.4)",stalk:"range(0.3, 0.42)"}},{let:{spiral:{map:{count:49,as:"step",let:{angle:"step / 48 * turns * PI * 2",radius:"shellRadius * 0.86 * (step / 48)"},value:["shellX + cos(angle) * radius","shellY + sin(angle) * radius"]}}}},{blob:[[-.95,.66],[-.5,.56],[.4,.56],[.68,.38],[.82,.08],[.95,.02],[1.02,.18],[.96,.5],[.8,.7],[-.88,.76]]},{circle:["shellX","shellY","shellRadius"]},{tube:"spiral",radii:.026,cut:!0},{tube:[[.88,.08],["0.95 + range(0, 0.08)","0.08 - stalk"]],radii:.024},{circle:["0.95 + 0.04","0.08 - stalk - 0.02",.05]},{tube:[[.82,.1],[.78,"0.1 - stalk * 0.8"]],radii:.022},{circle:[.78,"0.1 - stalk * 0.8 - 0.02",.045]}],z0={body:I0},B0=[{let:{shellHeight:"range(0.45, 0.6)",shellWidth:"range(0.66, 0.76)",neckReach:"range(0.85, 1.0)",dome:{map:{count:25,as:"step",let:{angle:"PI + (step / 24) * PI"},value:["cos(angle) * shellWidth","0.25 + sin(angle) * shellHeight"]}},scuteY:"0.25 - shellHeight * 0.45"}},{polygon:"dome"},{ellipse:[0,.27,"shellWidth + 0.07",.08]},{tube:[["shellWidth - 0.1",.24],["neckReach - 0.15",.1]],radii:.09},{ellipse:["neckReach",.08,.17,.12,-.1]},{ellipse:["-shellWidth * 0.62",.42,.12,.16,.3]},{ellipse:["shellWidth * 0.6",.42,.12,.16,-.3]},{polygon:[["-shellWidth - 0.02",.22],["-shellWidth - 0.2",.3],["-shellWidth - 0.02",.3]]},{tube:[["-shellWidth * 0.95",.2],["-shellWidth * 0.5","scuteY"],["shellWidth * 0.5","scuteY"],["shellWidth * 0.95",.2]],radii:.015,cut:!0},{tube:[["-shellWidth * 0.25","scuteY"],["-shellWidth * 0.3",.2]],radii:.015,cut:!0},{tube:[["shellWidth * 0.25","scuteY"],["shellWidth * 0.3",.2]],radii:.015,cut:!0},{tube:[["-shellWidth * 0.2","scuteY"],[0,"0.25 - shellHeight * 0.95"]],radii:.014,cut:!0},{tube:[["shellWidth * 0.2","scuteY"],[0,"0.25 - shellHeight * 0.95"]],radii:.014,cut:!0},{circle:["neckReach + 0.06",.04,.028],cut:!0}],q0={body:B0},V0=[{let:{belly:"range(0.24, 0.34)",back:"range(-0.42, -0.32)",tailLift:"range(-0.35, 0.05)",spout:"chance(0.75)"}},{blob:[[.9,-.02],[.72,"back + 0.1"],[.3,"back"],[-.2,"back + 0.12"],[-.6,"-0.08 + tailLift * 0.4"],[-.88,"tailLift * 0.8"],[-.62,"0.12 + tailLift * 0.3"],[-.1,"belly"],[.5,"belly + 0.02"],[.84,.18]]},{ellipse:[-1,"tailLift - 0.16","vary(0.28)",.075,-.85]},{ellipse:[-1,"tailLift + 0.13","vary(0.28)",.075,.85]},{ellipse:[.22,"belly + 0.08","vary(0.24)",.065,.55]},{tube:[[.88,.1],[.6,.16],[.32,.12]],radii:.018,cut:!0},{circle:[.42,.04,.035],cut:!0},{if:"spout",then:[{tube:[[.55,"back - 0.06"],[.56,"back - 0.3"],[.44,"back - 0.5"]],radii:[.035,.03,.02]},{tube:[[.56,"back - 0.3"],[.7,"back - 0.48"]],radii:[.03,.02]},{circle:[.38,"back - 0.48",.03]},{circle:[.76,"back - 0.42",.025]},{circle:[.6,"back - 0.6",.025]}]}],J0={body:V0},K0={bodyRadiusX:"range(0.58, 0.66)",bodyRadiusY:"range(0.26, 0.3)",legLength:"range(0.52, 0.62)",legWidth:.06,headRadiusX:.2,headRadiusY:.16,snoutLength:.24,snoutRadius:.07,earHeight:"range(0.2, 0.26)",tailLength:"range(0.4, 0.5)",tailLift:"range(-0.25, -0.05)",tailWidth:.09},U0={extends:"quadruped",params:K0},G0=Object.assign({"./base/bird.json":Dt,"./base/quadruped.json":Ht,"./base/swimmer.json":It,"./bear.json":Bt,"./cat.json":Vt,"./deer.json":Ut,"./dolphin.json":t0,"./duck.json":n0,"./elephant.json":o0,"./fish.json":r0,"./flamingo.json":h0,"./fox.json":p0,"./hedgehog.json":b0,"./horse.json":R0,"./octopus.json":w0,"./otter.json":k0,"./owl.json":L0,"./penguin.json":D0,"./pig.json":A0,"./rabbit.json":H0,"./rhino.json":P0,"./shark.json":F0,"./snail.json":z0,"./turtle.json":q0,"./whale.json":J0,"./wolf.json":U0}),Xa=Object.fromEntries(Object.entries(G0).map(([e,t])=>[e.replace(/^.*\//,"").replace(/\.json$/,""),t])),Q0=pt(),Z0=()=>Q0.value,wa=O(Z0);export{yt as A,ca as B,la as C,sa as D,ua as E,Ra as F,va as G,A as M,et as a,ya as b,ba as c,Re as d,Ae as e,ae as f,wa as g,ha as h,Z0 as i,fa as j,na as k,Me as l,Yt as m,da as n,ga as o,Ya as p,ia as q,Xa as r,ma as s,ta as t,q as u,pa as v,ra as w,aa as x,oa as y,st as z};
@@ -0,0 +1 @@
1
+ import{d as V,Y as F,v as t,F as l,L as i,K as a,b as c,c as k,_ as C,e as I,w as T,O as d,V as h,a0 as U,z as $,H as g,I as f,W as M,M as w,x as L,Q as P,$ as z}from"./_plugin-vue_export-helper-BbgK5wvS.js";import{F as A,b as D,r as y,c as E,s as O,G as q,g as G}from"./scheme-DCqBrLhv.js";const H={class:"species-cell"},K={key:0,class:"species-cell__error"},Q={class:"species-cell__seed"},R=V({__name:"SpeciesCell",props:{name:{},pose:{},seed:{},ink:{}},setup(m){const n=m,o=c(null),r=k(()=>{try{return{shapes:D(y,n.name,E(n.seed),{pose:n.pose}).shapes,error:""}}catch(u){return{shapes:[],error:u.message}}});return F(()=>{o.value&&!r.value.error&&n.ink&&A(o.value,r.value.shapes,n.ink)}),(u,_)=>(t(),l("figure",H,[r.value.error?(t(),l("p",K,i(r.value.error),1)):(t(),l("canvas",{key:1,ref_key:"canvas",ref:o,class:"species-cell__canvas",width:"260",height:"260"},null,512)),a("figcaption",Q,i(m.seed),1)]))}}),W=C(R,[["__scopeId","data-v-220e2a5a"]]),Y={class:"sheet-page__head"},j={class:"sheet-page__field"},J={value:""},X=["value"],Z={class:"sheet-page__field"},ee={class:"sheet-page__field"},se={class:"sheet-page__label"},ae={key:0,class:"sheet-page__pose"},te=V({__name:"SheetPage",setup(m){const n=new URLSearchParams(location.search),o=c(Math.min(12,Math.max(1,Number(n.get("seeds"))||6))),r=c(Number(n.get("from"))||1),u=c(n.get("species")||""),_=O(y),N=k(()=>Array.from({length:o.value},(p,e)=>r.value+e)),B=k(()=>_.filter(p=>!u.value||p===u.value).flatMap(p=>q(y,p).map(e=>({name:p,pose:e,key:`${p}:${e}`})))),v=c(null),b=c(""),S=()=>{v.value&&(b.value=getComputedStyle(v.value).color)};return I(S),T(G,S),(p,e)=>(t(),l("main",{ref_key:"page",ref:v,class:"app sheet-page"},[a("header",Y,[e[6]||(e[6]=a("h1",{class:"sheet-page__title"},"Species sheet",-1)),a("label",j,[e[3]||(e[3]=d("Species ",-1)),h(a("select",{"onUpdate:modelValue":e[0]||(e[0]=s=>u.value=s)},[a("option",J,"all ("+i($(_).length)+")",1),(t(!0),l(g,null,f($(_),s=>(t(),l("option",{key:s,value:s},i(s),9,X))),128))],512),[[U,u.value]])]),a("label",Z,[e[4]||(e[4]=d("Seeds ",-1)),h(a("input",{"onUpdate:modelValue":e[1]||(e[1]=s=>o.value=s),class:"sheet-page__number",type:"number",min:"1",max:"12"},null,512),[[M,o.value,void 0,{number:!0}]])]),a("label",ee,[e[5]||(e[5]=d("From seed ",-1)),h(a("input",{"onUpdate:modelValue":e[2]||(e[2]=s=>r.value=s),class:"sheet-page__number",type:"number",min:"0"},null,512),[[M,r.value,void 0,{number:!0}]])])]),(t(!0),l(g,null,f(B.value,s=>(t(),l("section",{key:s.key,class:"sheet-page__row"},[a("h2",se,[d(i(s.name),1),s.pose?(t(),l("small",ae,i(s.pose),1)):w("",!0)]),a("div",{class:"sheet-page__cells",style:P({gridTemplateColumns:`repeat(${o.value}, minmax(0, 1fr))`})},[(t(!0),l(g,null,f(N.value,x=>(t(),L(W,{key:x,name:s.name,pose:s.pose,seed:x,ink:b.value},null,8,["name","pose","seed","ink"]))),128))],4)]))),128))],512))}}),le=C(te,[["__scopeId","data-v-b4daa75a"]]);z(le).mount("#app");
package/dist/index.html CHANGED
@@ -6,12 +6,12 @@
6
6
  <title>Whose Turn</title>
7
7
  <script src="/vendor/popper.min.js"></script>
8
8
  <script src="/vendor/tippy-bundle.umd.min.js"></script>
9
- <script type="module" crossorigin src="/assets/index-BLFgmJcy.js"></script>
10
- <link rel="modulepreload" crossorigin href="/assets/_plugin-vue_export-helper-DeQjDyQ9.js">
11
- <link rel="modulepreload" crossorigin href="/assets/scheme-BdlolNvz.js">
12
- <link rel="modulepreload" crossorigin href="/assets/bem-zV6iDqYN.js">
13
- <link rel="stylesheet" crossorigin href="/assets/_plugin-vue_export-helper-aQHODzfZ.css">
14
- <link rel="stylesheet" crossorigin href="/assets/index-CTrTp-xu.css">
9
+ <script type="module" crossorigin src="/assets/index-C3jZLqLJ.js"></script>
10
+ <link rel="modulepreload" crossorigin href="/assets/_plugin-vue_export-helper-BbgK5wvS.js">
11
+ <link rel="modulepreload" crossorigin href="/assets/scheme-DCqBrLhv.js">
12
+ <link rel="modulepreload" crossorigin href="/assets/bem-OtK9ToP0.js">
13
+ <link rel="stylesheet" crossorigin href="/assets/_plugin-vue_export-helper-BneMU3KP.css">
14
+ <link rel="stylesheet" crossorigin href="/assets/index-B6grE8kv.css">
15
15
  </head>
16
16
  <body class="page">
17
17
  <div id="app" class="page__mount"></div>
package/dist/report.html CHANGED
@@ -4,10 +4,10 @@
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
6
  <title>Your report · Whose Turn</title>
7
- <script type="module" crossorigin src="/assets/report-BJ_aC1A7.js"></script>
8
- <link rel="modulepreload" crossorigin href="/assets/_plugin-vue_export-helper-DeQjDyQ9.js">
9
- <link rel="modulepreload" crossorigin href="/assets/bem-zV6iDqYN.js">
10
- <link rel="stylesheet" crossorigin href="/assets/_plugin-vue_export-helper-aQHODzfZ.css">
7
+ <script type="module" crossorigin src="/assets/report-DUlemvzD.js"></script>
8
+ <link rel="modulepreload" crossorigin href="/assets/_plugin-vue_export-helper-BbgK5wvS.js">
9
+ <link rel="modulepreload" crossorigin href="/assets/bem-OtK9ToP0.js">
10
+ <link rel="stylesheet" crossorigin href="/assets/_plugin-vue_export-helper-BneMU3KP.css">
11
11
  <link rel="stylesheet" crossorigin href="/assets/report-D7II0AfP.css">
12
12
  </head>
13
13
  <body class="page page--scroll">
package/dist/sheet.html CHANGED
@@ -4,10 +4,10 @@
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
6
  <title>Species sheet · Whose Turn</title>
7
- <script type="module" crossorigin src="/assets/sheet-q2RQEgOf.js"></script>
8
- <link rel="modulepreload" crossorigin href="/assets/_plugin-vue_export-helper-DeQjDyQ9.js">
9
- <link rel="modulepreload" crossorigin href="/assets/scheme-BdlolNvz.js">
10
- <link rel="stylesheet" crossorigin href="/assets/_plugin-vue_export-helper-aQHODzfZ.css">
7
+ <script type="module" crossorigin src="/assets/sheet-Ctd_6q4a.js"></script>
8
+ <link rel="modulepreload" crossorigin href="/assets/_plugin-vue_export-helper-BbgK5wvS.js">
9
+ <link rel="modulepreload" crossorigin href="/assets/scheme-DCqBrLhv.js">
10
+ <link rel="stylesheet" crossorigin href="/assets/_plugin-vue_export-helper-BneMU3KP.css">
11
11
  <link rel="stylesheet" crossorigin href="/assets/sheet-DYpaJaXO.css">
12
12
  </head>
13
13
  <body class="page page--scroll">
package/lib/api.mjs CHANGED
@@ -4,9 +4,11 @@ import { join } from 'node:path';
4
4
  import { createStatic } from './static.mjs';
5
5
  import { ApiError, toApiError } from './errors.mjs';
6
6
  import { connectorCatalogue, secretTarget, secretsRequired } from '../connectors/index.mjs';
7
- import { entryShapeViolations, normaliseWorkspaces, WORKSPACE_FIELDS } from './config.mjs';
7
+ import { desktopOf, entryShapeViolations, normaliseWorkspaces, settingsViolations, GLOBAL_SETTINGS, WORKSPACE_FIELDS } from './config.mjs';
8
8
  import { FACTS } from './facts.mjs';
9
- import { createFeedback, isConfigured } from './feedback.mjs';
9
+ import { withHidden } from './registry.mjs';
10
+ import { HIDDEN_MODES } from './backoff.mjs';
11
+ import { createFeedback, installOf } from './feedback.mjs';
10
12
  import { composeIssue, DEFAULT_COMPOSER } from './compose.mjs';
11
13
  import { readSize, saveSize, checkMode, spawnOpener } from './window.mjs';
12
14
  import { frameHandOff } from './handoff.mjs';
@@ -109,16 +111,15 @@ const aggregateSummary = (workspaces) => {
109
111
 
110
112
  const OPEN_COOLDOWN_MS = 5000;
111
113
 
112
- export const startApi = async ({ port, appDir, registry, requestPoll, saveConfig, secrets, inbox, http, reports, dataDir, opener = spawnOpener(appDir), updater = noUpdates(), changelog = null }) => {
114
+ export const startApi = async ({ port, appDir, registry, requestPoll, saveConfig, secrets, inbox, http, reports, dataDir, opener = spawnOpener(appDir), updater = noUpdates(), changelog = null, version = '' }) => {
113
115
  const windowFile = createStatic(join(appDir, 'dist'));
114
- const feedbackCache = {};
115
116
  const pick = (id) => {
116
117
  const workspaces = registry.workspaces;
117
118
  if (!workspaces.length) {
118
119
  throw new ApiError('NO_WORKSPACE');
119
120
  }
120
121
  if (!id) {
121
- return workspaces[0];
122
+ return workspaces.find((workspace) => !workspace.hidden) || workspaces[0];
122
123
  }
123
124
  const found = workspaces.find((workspace) => workspace.id === id);
124
125
  if (!found) {
@@ -141,13 +142,18 @@ export const startApi = async ({ port, appDir, registry, requestPoll, saveConfig
141
142
  write({ ws: workspace.id, version: workspace.store.state.version, polled_at: workspace.store.state.polled_at });
142
143
  }
143
144
  write({ update: updater.status() });
145
+ if (reports.hasNews?.()) {
146
+ write({ report_news: true });
147
+ }
144
148
  const unsubscribe = registry.subscribe(write);
145
149
  const unwatch = updater.subscribe(write);
150
+ const unwatchReports = reports.subscribe?.(write);
146
151
  const keepAlive = setInterval(() => response.write(': keep-alive\n\n'), 25000);
147
152
  response.on('close', () => {
148
153
  clearInterval(keepAlive);
149
154
  unsubscribe();
150
155
  unwatch();
156
+ unwatchReports?.();
151
157
  });
152
158
  };
153
159
 
@@ -164,14 +170,14 @@ export const startApi = async ({ port, appDir, registry, requestPoll, saveConfig
164
170
  return send(response, 200, { workspaces: [], problems: registry.problems, update: updater.status() });
165
171
  }
166
172
  const workspace = pick(url.searchParams.get('ws'));
167
- return send(response, 200, { ...workspace.snapshot(url.searchParams.get('full') === '1'), workspaces: registry.workspaces.map((item) => item.describe()), problems: registry.problems, update: updater.status() });
173
+ return send(response, 200, { ...workspace.snapshot(url.searchParams.get('full') === '1'), workspaces: registry.workspaces.map((item) => item.describe()), problems: registry.problems, notifications: registry.notifications, update: updater.status() });
168
174
  }
169
175
  if (url.pathname === '/api/summary') {
170
- return send(response, 200, { ...aggregateSummary(registry.workspaces), problems: registry.problems, handed: inbox.pending().length, update: updater.status() });
176
+ return send(response, 200, { ...aggregateSummary(registry.workspaces), problems: registry.problems, handed: inbox.pending().length, notifications: registry.notifications, update: updater.status() });
171
177
  }
172
178
  if (url.pathname === '/api/config') {
173
179
  const tables = Object.fromEntries(registry.workspaces.map((workspace) => [workspace.id, workspace.rulesTable]));
174
- return send(response, 200, { workspaces: registry.rawWorkspaces, problems: registry.problems, connectors: connectorCatalogue(), fields: WORKSPACE_FIELDS, facts: FACTS, tables, table_warnings: normaliseWorkspaces(registry.rawWorkspaces).warnings, config_file: registry.configFile });
180
+ return send(response, 200, { workspaces: registry.rawWorkspaces, notifications: registry.notifications, settings: registry.settings, settings_fields: GLOBAL_SETTINGS, problems: registry.problems, connectors: connectorCatalogue(), fields: WORKSPACE_FIELDS, facts: FACTS, tables, table_warnings: normaliseWorkspaces(registry.rawWorkspaces).warnings, config_file: registry.configFile });
175
181
  }
176
182
  if (url.pathname === '/api/changelog') {
177
183
  if (!changelog) {
@@ -185,10 +191,11 @@ export const startApi = async ({ port, appDir, registry, requestPoll, saveConfig
185
191
  if (url.pathname === '/api/feedback') {
186
192
  const id = url.searchParams.get('id');
187
193
  if (id) {
188
- return send(response, 200, { report: await reports.detail(reportDeps(), id) });
194
+ return send(response, 200, { report: await reports.detail(id) });
189
195
  }
190
- const list = await reports.states(reportDeps());
191
- return send(response, 200, { configured: isConfigured(registry.feedback), project: registry.feedback ? registry.feedback.project : '', reports: list });
196
+ const list = await reports.states();
197
+ await reports.markSeen?.();
198
+ return send(response, 200, { reports: list });
192
199
  }
193
200
  if (url.pathname === '/api/handoff') {
194
201
  if (url.searchParams.get('all') === '1') {
@@ -240,7 +247,28 @@ export const startApi = async ({ port, appDir, registry, requestPoll, saveConfig
240
247
  return { ...target, value: String(item.value).trim() };
241
248
  });
242
249
 
250
+ const handleNotifications = async (body) => {
251
+ const desktop = desktopOf(body);
252
+ if (desktop === undefined) {
253
+ throw new ApiError('INVALID_ARGUMENT', { detail: 'desktop must be true or false' }, [{ field: 'desktop', description: 'Send {desktop: true} or {desktop: false}.' }]);
254
+ }
255
+ await registry.saveNotifications({ desktop });
256
+ return { notifications: registry.notifications };
257
+ };
258
+
259
+ const handleSettings = async (settings) => {
260
+ const violations = settingsViolations(settings);
261
+ if (violations.length) {
262
+ throw new ApiError('INVALID_CONFIG', { count: String(violations.length) }, violations);
263
+ }
264
+ await registry.saveSettings(settings);
265
+ return { settings: registry.settings, problems: registry.problems };
266
+ };
267
+
243
268
  const handleConfig = async (body) => {
269
+ if (body.settings !== undefined && body.workspaces === undefined) {
270
+ return handleSettings(body.settings);
271
+ }
244
272
  if (!Array.isArray(body.workspaces)) {
245
273
  throw new ApiError('INVALID_ARGUMENT', { detail: 'workspaces must be a list' });
246
274
  }
@@ -275,7 +303,23 @@ export const startApi = async ({ port, appDir, registry, requestPoll, saveConfig
275
303
  await secrets.set(secret.service, secret.account, secret.value);
276
304
  }
277
305
  await saveConfig(body.workspaces);
278
- return { workspaces: registry.workspaces.map((workspace) => workspace.describe()), problems: registry.problems, secrets_stored: stored.map(({ service, account }) => ({ service, account })) };
306
+ return { workspaces: registry.workspaces.map((workspace) => workspace.describe()), notifications: registry.notifications, problems: registry.problems, secrets_stored: stored.map(({ service, account }) => ({ service, account })) };
307
+ };
308
+
309
+ const handleHidden = async ({ id, hidden }) => {
310
+ if (!registry.workspaces.some((workspace) => workspace.id === id)) {
311
+ throw new ApiError('NO_SUCH_WORKSPACE', { ws: String(id || '') });
312
+ }
313
+ if (hidden !== null && !HIDDEN_MODES.includes(hidden)) {
314
+ throw new ApiError('INVALID_ARGUMENT', { detail: 'hidden must be "parked", "paused" or null' }, [{ field: 'hidden', description: 'Send "parked", "paused" or null to show the workspace again.' }]);
315
+ }
316
+ const { violations } = normaliseWorkspaces(registry.rawWorkspaces.map((entry) => (entry.id === id ? withHidden(entry, hidden) : entry)));
317
+ if (violations.length) {
318
+ throw new ApiError('INVALID_CONFIG', { count: String(violations.length) }, violations);
319
+ }
320
+ const created = await registry.setHidden(id, hidden || '');
321
+ await Promise.all(created.map((workspace) => workspace.pollIfDue()));
322
+ return { workspaces: registry.workspaces.map((workspace) => workspace.describe()) };
279
323
  };
280
324
 
281
325
  const handleSecret = async (body) => {
@@ -321,12 +365,10 @@ export const startApi = async ({ port, appDir, registry, requestPoll, saveConfig
321
365
  return { item: inbox.add({ ws: workspace.id, ws_name: workspace.name, kind, ref, snapshot }) };
322
366
  };
323
367
 
324
- const reportDeps = () => ({ settings: registry.feedback, secrets, http });
325
-
326
368
  let lastRemark = { text: '', at: 0 };
327
369
  const handleFeedback = async (body) => {
328
- if (!isConfigured(registry.feedback)) {
329
- throw new ApiError('NOT_CONFIGURED');
370
+ if ('context' in body) {
371
+ throw new ApiError('INVALID_ARGUMENT', { detail: 'context is no longer accepted: a report carries only its text and the monitor version' }, [{ field: 'context', description: 'Remove it; nothing but what the user wrote leaves this machine.' }]);
330
372
  }
331
373
  const remark = typeof body.text === 'string' ? body.text.trim() : '';
332
374
  if (!remark && !String(body.title || '').trim()) {
@@ -338,16 +380,14 @@ export const startApi = async ({ port, appDir, registry, requestPoll, saveConfig
338
380
  if (remark) {
339
381
  lastRemark = { text: remark, at: Date.now() };
340
382
  }
341
- let context = null;
342
- if (body.ws && body.kind_ref) {
343
- const workspace = pick(body.ws);
344
- context = workspace.handOffText(body.kind_ref.kind, body.kind_ref.ref);
345
- }
383
+ const sources = (body.ws ? [pick(body.ws)] : registry.workspaces).flatMap((workspace) => workspace.sourceTypes);
346
384
  try {
347
385
  const composer = registry.feedback && registry.feedback.composer && registry.feedback.composer.command ? registry.feedback.composer : DEFAULT_COMPOSER;
348
- const shaped = remark ? await composeIssue(remark, { about: context || '', composer, bugWords: registry.feedback && registry.feedback.bug_words }) : { kind: body.kind, title: body.title, body: body.body, composed_by: body.from || 'caller' };
349
- const issue = await createFeedback({ settings: registry.feedback, secrets, http, cache: feedbackCache, reports }, { kind: shaped.kind, title: shaped.title, body: shaped.body, from: body.from, context: context || body.context || null });
350
- return { issue, composed_by: shaped.composed_by, reports: await reports.states(reportDeps()) };
386
+ const shaped = remark ? await composeIssue(remark, { composer, bugWords: registry.feedback && registry.feedback.bug_words }) : { kind: body.kind, title: body.title, body: body.body, composed_by: body.from || 'caller' };
387
+ const issue = await createFeedback({ http, reports, relayUrl: reports.relayUrl, version, sources, install: installOf(appDir), client: body.client }, { kind: shaped.kind, title: shaped.title, body: shaped.body });
388
+ const list = await reports.states();
389
+ await reports.markSeen?.();
390
+ return { issue, composed_by: shaped.composed_by, reports: list };
351
391
  } catch (error) {
352
392
  lastRemark = { text: '', at: 0 };
353
393
  throw error;
@@ -367,6 +407,12 @@ export const startApi = async ({ port, appDir, registry, requestPoll, saveConfig
367
407
  if (url.pathname === '/api/config') {
368
408
  return send(response, 200, await handleConfig(body));
369
409
  }
410
+ if (url.pathname === '/api/workspaces/hidden') {
411
+ return send(response, 200, await handleHidden(body));
412
+ }
413
+ if (url.pathname === '/api/notifications') {
414
+ return send(response, 200, await handleNotifications(body));
415
+ }
370
416
  if (url.pathname === '/api/secrets') {
371
417
  return send(response, 200, await handleSecret(body));
372
418
  }
package/lib/backoff.mjs CHANGED
@@ -1,2 +1,15 @@
1
1
  /** The next poll delay in seconds: doubled up to 15 minutes while any source is rate limited, else the normal interval. */
2
2
  export const nextDelay = (outcomes, delay, pollSeconds) => (outcomes.some((outcome) => outcome.rateLimited) ? Math.min(delay * 2, 15 * 60) : pollSeconds);
3
+
4
+ export const PARKED_EVERY_MS = 15 * 60 * 1000;
5
+
6
+ /** The workspace's hide modes: parked is checked rarely and returns by itself, paused is never checked. */
7
+ export const HIDDEN_MODES = ['parked', 'paused'];
8
+
9
+ /** Whether a workspace is polled in this round: always when shown, every 15 minutes when parked (its first check after a start is due 15 minutes after the stored last check, at once when there is none or it is older), never when paused. */
10
+ export const isDue = (hidden, lastPollAt, now) => {
11
+ if (hidden === 'paused') {
12
+ return false;
13
+ }
14
+ return hidden !== 'parked' || now - lastPollAt >= PARKED_EVERY_MS;
15
+ };
package/lib/compose.mjs CHANGED
@@ -1,17 +1,15 @@
1
1
  /** Turns a remark into an issue (title, kind, body) with the user's own agent CLI, `claude -p` unless the config names another; falls back to the first line as the title. RMA-109. */
2
2
  import { execFile } from 'node:child_process';
3
3
  import { escapeRegex } from './text.mjs';
4
- import { untrustedBlock } from './handoff.mjs';
5
4
 
6
5
  const KINDS = ['bug', 'improvement', 'idea'];
7
6
  export const DEFAULT_COMPOSER = { command: 'claude', args: ['-p', '--output-format', 'json', '--model', 'claude-haiku-5-5'] };
8
7
 
9
- const PROMPT = (text, about) => `You turn a user's remark about a tool called Whose Turn (a local monitor of tickets and pull requests) into an issue for its backlog.
8
+ const PROMPT = (text) => `You turn a user's remark about a tool called Whose Turn (a local monitor of tickets and pull requests) into an issue for its backlog.
10
9
  Answer with one JSON object only, no prose, no code fence: {"title": string, "kind": "bug" | "improvement" | "idea", "body": string}.
11
- Rules: the title and the body are in English whatever language the remark is in; translate faithfully, keep every detail the user gave, add nothing the user did not say. The title is one line under 80 characters; kind is bug when something misbehaves, improvement when something that exists should be better, idea when it is new; the body restates the remark clearly and may end with one line "Context: …" taken from the context below if it helps a developer find the place, and, when the remark was not in English, with one line "Original: …" quoting it as written.
10
+ Rules: the title and the body are in English whatever language the remark is in; translate faithfully, keep every detail the user gave, add nothing the user did not say. The title is one line under 80 characters; kind is bug when something misbehaves, improvement when something that exists should be better, idea when it is new; the body restates the remark clearly and ends, when the remark was not in English, with one line "Original: …" quoting it as written.
12
11
  Remark:
13
- ${text}
14
- ${about ? `\nContext of the window at that moment:\n${untrustedBlock(about.slice(0, 3000))}` : ''}`;
12
+ ${text}`;
15
13
 
16
14
  const DEFAULT_BUG_WORDS = ['bug', 'broken', 'crash', 'error', 'fail', 'wrong'];
17
15
  const fallback = (text, bugWords) => {
@@ -50,8 +48,8 @@ const runCommand = (composer) => (prompt, timeoutMs) => new Promise((resolve, re
50
48
  child.stdin.end(prompt);
51
49
  });
52
50
 
53
- /** @param {string} text @param {{ about?: string, composer?: object, bugWords?: string[], run?: Function, timeoutMs?: number }} [options] */
54
- export const composeIssue = async (text, { about = '', composer = DEFAULT_COMPOSER, bugWords = [], run, timeoutMs = 45000 } = {}) => {
51
+ /** @param {string} text @param {{ composer?: object, bugWords?: string[], run?: Function, timeoutMs?: number }} [options] */
52
+ export const composeIssue = async (text, { composer = DEFAULT_COMPOSER, bugWords = [], run, timeoutMs = 45000 } = {}) => {
55
53
  const clean = String(text || '').trim();
56
54
  if (!clean) {
57
55
  throw new Error('Write at least one line');
@@ -60,7 +58,7 @@ export const composeIssue = async (text, { about = '', composer = DEFAULT_COMPOS
60
58
  const model = args[args.indexOf('--model') + 1];
61
59
  const label = composer.label || (args.includes('--model') && model ? `${composer.command} ${model}` : composer.command);
62
60
  try {
63
- return parse(await (run || runCommand(composer))(PROMPT(clean, about), timeoutMs), label);
61
+ return parse(await (run || runCommand(composer))(PROMPT(clean), timeoutMs), label);
64
62
  } catch {
65
63
  return fallback(clean, bugWords);
66
64
  }