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.
- package/CHANGELOG.md +44 -0
- package/README.md +28 -16
- package/config.example.json +0 -4
- package/connectors/github.mjs +24 -0
- package/dist/assets/_plugin-vue_export-helper-BbgK5wvS.js +18 -0
- package/dist/assets/_plugin-vue_export-helper-BneMU3KP.css +1 -0
- package/dist/assets/bem-OtK9ToP0.js +1 -0
- package/dist/assets/index-B6grE8kv.css +1 -0
- package/dist/assets/index-C3jZLqLJ.js +15 -0
- package/dist/assets/report-DUlemvzD.js +1 -0
- package/dist/assets/scheme-DCqBrLhv.js +1 -0
- package/dist/assets/sheet-Ctd_6q4a.js +1 -0
- package/dist/index.html +6 -6
- package/dist/report.html +4 -4
- package/dist/sheet.html +4 -4
- package/lib/api.mjs +70 -24
- package/lib/backoff.mjs +13 -0
- package/lib/compose.mjs +6 -8
- package/lib/config.mjs +102 -15
- package/lib/default-table.mjs +1 -0
- package/lib/errors.mjs +1 -2
- package/lib/facts.mjs +1 -1
- package/lib/feedback.mjs +140 -111
- package/lib/linear-api.mjs +1 -1
- package/lib/notify.mjs +7 -8
- package/lib/registry.mjs +32 -5
- package/lib/rules.mjs +16 -9
- package/lib/table.mjs +1 -0
- package/lib/text.mjs +3 -0
- package/lib/trace.mjs +6 -6
- package/lib/workspace.mjs +63 -15
- package/package.json +2 -1
- package/server.mjs +17 -8
- package/dist/assets/_plugin-vue_export-helper-DeQjDyQ9.js +0 -18
- package/dist/assets/_plugin-vue_export-helper-aQHODzfZ.css +0 -1
- package/dist/assets/bem-zV6iDqYN.js +0 -1
- package/dist/assets/index-BLFgmJcy.js +0 -7
- package/dist/assets/index-CTrTp-xu.css +0 -1
- package/dist/assets/report-BJ_aC1A7.js +0 -1
- package/dist/assets/scheme-BdlolNvz.js +0 -1
- 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-
|
|
10
|
-
<link rel="modulepreload" crossorigin href="/assets/_plugin-vue_export-helper-
|
|
11
|
-
<link rel="modulepreload" crossorigin href="/assets/scheme-
|
|
12
|
-
<link rel="modulepreload" crossorigin href="/assets/bem-
|
|
13
|
-
<link rel="stylesheet" crossorigin href="/assets/_plugin-vue_export-helper-
|
|
14
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
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-
|
|
8
|
-
<link rel="modulepreload" crossorigin href="/assets/_plugin-vue_export-helper-
|
|
9
|
-
<link rel="modulepreload" crossorigin href="/assets/bem-
|
|
10
|
-
<link rel="stylesheet" crossorigin href="/assets/_plugin-vue_export-helper-
|
|
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-
|
|
8
|
-
<link rel="modulepreload" crossorigin href="/assets/_plugin-vue_export-helper-
|
|
9
|
-
<link rel="modulepreload" crossorigin href="/assets/scheme-
|
|
10
|
-
<link rel="stylesheet" crossorigin href="/assets/_plugin-vue_export-helper-
|
|
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 {
|
|
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(
|
|
194
|
+
return send(response, 200, { report: await reports.detail(id) });
|
|
189
195
|
}
|
|
190
|
-
const list = await reports.states(
|
|
191
|
-
|
|
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 (
|
|
329
|
-
throw new ApiError('
|
|
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
|
-
|
|
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, {
|
|
349
|
-
const issue = await createFeedback({
|
|
350
|
-
|
|
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
|
|
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
|
|
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 {{
|
|
54
|
-
export const composeIssue = async (text, {
|
|
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
|
|
61
|
+
return parse(await (run || runCommand(composer))(PROMPT(clean), timeoutMs), label);
|
|
64
62
|
} catch {
|
|
65
63
|
return fallback(clean, bugWords);
|
|
66
64
|
}
|