whose-turn 1.0.0-alpha.8 → 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 +31 -0
- package/README.md +25 -13
- 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 +45 -4
- package/lib/backoff.mjs +13 -0
- package/lib/config.mjs +68 -14
- package/lib/default-table.mjs +1 -0
- package/lib/errors.mjs +1 -1
- package/lib/facts.mjs +1 -1
- package/lib/feedback.mjs +82 -21
- package/lib/notify.mjs +3 -8
- package/lib/registry.mjs +27 -5
- package/lib/rules.mjs +16 -9
- package/lib/table.mjs +1 -0
- package/lib/trace.mjs +6 -6
- package/lib/workspace.mjs +63 -15
- package/package.json +2 -1
- package/server.mjs +14 -7
- 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-CST_YFrc.js +0 -1
- package/dist/assets/index-CEB3_bQv.css +0 -1
- package/dist/assets/index-DsQ_5Y-x.js +0 -7
- package/dist/assets/report-Bar3ULi6.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,8 +4,10 @@ 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 { desktopOf, 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 { withHidden } from './registry.mjs';
|
|
10
|
+
import { HIDDEN_MODES } from './backoff.mjs';
|
|
9
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';
|
|
@@ -117,7 +119,7 @@ export const startApi = async ({ port, appDir, registry, requestPoll, saveConfig
|
|
|
117
119
|
throw new ApiError('NO_WORKSPACE');
|
|
118
120
|
}
|
|
119
121
|
if (!id) {
|
|
120
|
-
return workspaces[0];
|
|
122
|
+
return workspaces.find((workspace) => !workspace.hidden) || workspaces[0];
|
|
121
123
|
}
|
|
122
124
|
const found = workspaces.find((workspace) => workspace.id === id);
|
|
123
125
|
if (!found) {
|
|
@@ -140,13 +142,18 @@ export const startApi = async ({ port, appDir, registry, requestPoll, saveConfig
|
|
|
140
142
|
write({ ws: workspace.id, version: workspace.store.state.version, polled_at: workspace.store.state.polled_at });
|
|
141
143
|
}
|
|
142
144
|
write({ update: updater.status() });
|
|
145
|
+
if (reports.hasNews?.()) {
|
|
146
|
+
write({ report_news: true });
|
|
147
|
+
}
|
|
143
148
|
const unsubscribe = registry.subscribe(write);
|
|
144
149
|
const unwatch = updater.subscribe(write);
|
|
150
|
+
const unwatchReports = reports.subscribe?.(write);
|
|
145
151
|
const keepAlive = setInterval(() => response.write(': keep-alive\n\n'), 25000);
|
|
146
152
|
response.on('close', () => {
|
|
147
153
|
clearInterval(keepAlive);
|
|
148
154
|
unsubscribe();
|
|
149
155
|
unwatch();
|
|
156
|
+
unwatchReports?.();
|
|
150
157
|
});
|
|
151
158
|
};
|
|
152
159
|
|
|
@@ -170,7 +177,7 @@ export const startApi = async ({ port, appDir, registry, requestPoll, saveConfig
|
|
|
170
177
|
}
|
|
171
178
|
if (url.pathname === '/api/config') {
|
|
172
179
|
const tables = Object.fromEntries(registry.workspaces.map((workspace) => [workspace.id, workspace.rulesTable]));
|
|
173
|
-
return send(response, 200, { workspaces: registry.rawWorkspaces, notifications: registry.notifications, 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 });
|
|
174
181
|
}
|
|
175
182
|
if (url.pathname === '/api/changelog') {
|
|
176
183
|
if (!changelog) {
|
|
@@ -187,6 +194,7 @@ export const startApi = async ({ port, appDir, registry, requestPoll, saveConfig
|
|
|
187
194
|
return send(response, 200, { report: await reports.detail(id) });
|
|
188
195
|
}
|
|
189
196
|
const list = await reports.states();
|
|
197
|
+
await reports.markSeen?.();
|
|
190
198
|
return send(response, 200, { reports: list });
|
|
191
199
|
}
|
|
192
200
|
if (url.pathname === '/api/handoff') {
|
|
@@ -248,7 +256,19 @@ export const startApi = async ({ port, appDir, registry, requestPoll, saveConfig
|
|
|
248
256
|
return { notifications: registry.notifications };
|
|
249
257
|
};
|
|
250
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
|
+
|
|
251
268
|
const handleConfig = async (body) => {
|
|
269
|
+
if (body.settings !== undefined && body.workspaces === undefined) {
|
|
270
|
+
return handleSettings(body.settings);
|
|
271
|
+
}
|
|
252
272
|
if (!Array.isArray(body.workspaces)) {
|
|
253
273
|
throw new ApiError('INVALID_ARGUMENT', { detail: 'workspaces must be a list' });
|
|
254
274
|
}
|
|
@@ -286,6 +306,22 @@ export const startApi = async ({ port, appDir, registry, requestPoll, saveConfig
|
|
|
286
306
|
return { workspaces: registry.workspaces.map((workspace) => workspace.describe()), notifications: registry.notifications, problems: registry.problems, secrets_stored: stored.map(({ service, account }) => ({ service, account })) };
|
|
287
307
|
};
|
|
288
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()) };
|
|
323
|
+
};
|
|
324
|
+
|
|
289
325
|
const handleSecret = async (body) => {
|
|
290
326
|
const service = String(body.service || '').trim();
|
|
291
327
|
const account = String(body.account || '').trim();
|
|
@@ -349,7 +385,9 @@ export const startApi = async ({ port, appDir, registry, requestPoll, saveConfig
|
|
|
349
385
|
const composer = registry.feedback && registry.feedback.composer && registry.feedback.composer.command ? registry.feedback.composer : DEFAULT_COMPOSER;
|
|
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' };
|
|
351
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 });
|
|
352
|
-
|
|
388
|
+
const list = await reports.states();
|
|
389
|
+
await reports.markSeen?.();
|
|
390
|
+
return { issue, composed_by: shaped.composed_by, reports: list };
|
|
353
391
|
} catch (error) {
|
|
354
392
|
lastRemark = { text: '', at: 0 };
|
|
355
393
|
throw error;
|
|
@@ -369,6 +407,9 @@ export const startApi = async ({ port, appDir, registry, requestPoll, saveConfig
|
|
|
369
407
|
if (url.pathname === '/api/config') {
|
|
370
408
|
return send(response, 200, await handleConfig(body));
|
|
371
409
|
}
|
|
410
|
+
if (url.pathname === '/api/workspaces/hidden') {
|
|
411
|
+
return send(response, 200, await handleHidden(body));
|
|
412
|
+
}
|
|
372
413
|
if (url.pathname === '/api/notifications') {
|
|
373
414
|
return send(response, 200, await handleNotifications(body));
|
|
374
415
|
}
|
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/config.mjs
CHANGED
|
@@ -8,6 +8,7 @@ import { statusGroups } from './facts.mjs';
|
|
|
8
8
|
import { tableProblems, tableWarnings } from './table.mjs';
|
|
9
9
|
import { DEFAULT_LIMITS } from './health.mjs';
|
|
10
10
|
import { CONNECTORS } from '../connectors/index.mjs';
|
|
11
|
+
import { HIDDEN_MODES } from './backoff.mjs';
|
|
11
12
|
|
|
12
13
|
export const DEFAULT_RULES = {
|
|
13
14
|
release_branch_prefix: 'release/',
|
|
@@ -19,6 +20,7 @@ export const DEFAULT_RULES = {
|
|
|
19
20
|
passed: ['passed'],
|
|
20
21
|
code_review: ['code review', 'in review'],
|
|
21
22
|
qa: ['ready for qa', 'in qa', 'testing'],
|
|
23
|
+
release: ['ready for release', 'ready to release', 'awaiting release'],
|
|
22
24
|
on_hold: ['on hold', 'blocked'],
|
|
23
25
|
backlog: ['backlog'],
|
|
24
26
|
},
|
|
@@ -40,6 +42,7 @@ export const WORKSPACE_FIELDS = {
|
|
|
40
42
|
theme: [
|
|
41
43
|
{ key: 'seed', label: 'Seed colour', hint: 'the scheme is derived from it; empty derives one from the workspace id', colour: true },
|
|
42
44
|
{ key: 'accent', label: 'Accent colour', hint: 'optional second hue', colour: true },
|
|
45
|
+
{ key: 'backdrop', label: 'Backdrop animal', hint: 'draw the dotted animal behind the window', boolean: true, default: true },
|
|
43
46
|
],
|
|
44
47
|
rules: [
|
|
45
48
|
{ key: 'approved_next', label: 'Next step when the PR is approved', hint: 'shown when a PR under review is approved' },
|
|
@@ -54,11 +57,37 @@ export const WORKSPACE_FIELDS = {
|
|
|
54
57
|
{ key: 'code_review', label: 'Review statuses', list: true },
|
|
55
58
|
{ key: 'qa', label: 'Testing statuses', list: true },
|
|
56
59
|
{ key: 'passed', label: 'Passed statuses', list: true },
|
|
60
|
+
{ key: 'release', label: 'Release statuses', list: true },
|
|
57
61
|
{ key: 'on_hold', label: 'On hold statuses', list: true },
|
|
58
62
|
{ key: 'backlog', label: 'Backlog statuses', list: true },
|
|
59
63
|
],
|
|
60
64
|
};
|
|
61
65
|
|
|
66
|
+
/** The monitor-wide numbers the settings sheet and Claude may edit, with their defaults: how often to poll, how far back the feed reaches, and when a health alert fires. */
|
|
67
|
+
export const GLOBAL_SETTINGS = {
|
|
68
|
+
poll_seconds: { fallback: 60, label: 'Poll every (seconds)', hint: 'how often every workspace is polled' },
|
|
69
|
+
history_days: { fallback: 14, label: 'History (days)', hint: 'how far back the feed and the mentions reach', whole: true },
|
|
70
|
+
stale_minutes: { fallback: DEFAULT_LIMITS.stale_minutes, label: 'Stale after (minutes)', hint: 'alert when no source has answered for this long' },
|
|
71
|
+
failing_polls: { fallback: DEFAULT_LIMITS.failing_polls, label: 'Failing polls in a row', hint: 'alert when one source fails this many polls in a row', whole: true },
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/** Field violations for a `settings` object: unknown keys, and values that are not positive numbers (whole numbers where the key says so). */
|
|
75
|
+
export const settingsViolations = (settings) => {
|
|
76
|
+
if (!isObject(settings)) {
|
|
77
|
+
return [{ field: 'settings', description: 'settings must be an object.' }];
|
|
78
|
+
}
|
|
79
|
+
return Object.entries(settings).flatMap(([key, value]) => {
|
|
80
|
+
const spec = GLOBAL_SETTINGS[key];
|
|
81
|
+
if (!spec) {
|
|
82
|
+
return [{ field: `settings.${key}`, description: `${key} is not a setting; the settings are ${Object.keys(GLOBAL_SETTINGS).join(', ')}.` }];
|
|
83
|
+
}
|
|
84
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0 || (spec.whole && !Number.isInteger(value))) {
|
|
85
|
+
return [{ field: `settings.${key}`, description: `${key} must be a positive ${spec.whole ? 'whole ' : ''}number.` }];
|
|
86
|
+
}
|
|
87
|
+
return [];
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
|
|
62
91
|
/** The per-platform directory that holds config.json and the state, unless WHOSE_TURN_DATA overrides it. */
|
|
63
92
|
export const defaultDataDir = () => {
|
|
64
93
|
if (process.platform === 'darwin') {
|
|
@@ -125,14 +154,21 @@ const normaliseTheme = (theme, problems, workspaceId) => {
|
|
|
125
154
|
if (!theme) {
|
|
126
155
|
return null;
|
|
127
156
|
}
|
|
157
|
+
const backdrop = theme.backdrop === false ? { backdrop: false } : {};
|
|
158
|
+
if (theme.backdrop !== undefined && typeof theme.backdrop !== 'boolean') {
|
|
159
|
+
problems.push(`Workspace "${workspaceId}": theme.backdrop must be true or false; the backdrop stays on.`);
|
|
160
|
+
}
|
|
161
|
+
if (theme.seed === undefined && theme.accent === undefined) {
|
|
162
|
+
return Object.keys(backdrop).length ? backdrop : null;
|
|
163
|
+
}
|
|
128
164
|
if (!isHex(theme.seed)) {
|
|
129
165
|
problems.push(`Workspace "${workspaceId}": theme.seed must be a hex colour like #3d7a5c; the theme is ignored.`);
|
|
130
|
-
return null;
|
|
166
|
+
return Object.keys(backdrop).length ? backdrop : null;
|
|
131
167
|
}
|
|
132
168
|
if (theme.accent !== undefined && !isHex(theme.accent)) {
|
|
133
169
|
problems.push(`Workspace "${workspaceId}": theme.accent must be a hex colour like #c27a2c; the accent is ignored.`);
|
|
134
170
|
}
|
|
135
|
-
return { seed: theme.seed.toLowerCase(), ...(isHex(theme.accent) ? { accent: theme.accent.toLowerCase() } : {}) };
|
|
171
|
+
return { seed: theme.seed.toLowerCase(), ...(isHex(theme.accent) ? { accent: theme.accent.toLowerCase() } : {}), ...backdrop };
|
|
136
172
|
};
|
|
137
173
|
|
|
138
174
|
const normaliseLinks = (links) => (Array.isArray(links) ? links : []).map((entry) => {
|
|
@@ -158,6 +194,9 @@ const fieldProblems = (workspace, id, index) => {
|
|
|
158
194
|
problems.push(violation(`${at}.key_pattern`, `${where}: key_pattern is not a valid regular expression: ${error.message}`));
|
|
159
195
|
}
|
|
160
196
|
}
|
|
197
|
+
if (workspace.hidden !== undefined && !HIDDEN_MODES.includes(workspace.hidden)) {
|
|
198
|
+
problems.push(violation(`${at}.hidden`, `${where}: hidden must be one of ${HIDDEN_MODES.join(', ')}, or left out.`));
|
|
199
|
+
}
|
|
161
200
|
const rules = workspace.rules || {};
|
|
162
201
|
const backlogChoices = WORKSPACE_FIELDS.rules.find((field) => field.key === 'backlog_rows').choices;
|
|
163
202
|
if (rules.backlog_rows !== undefined && !backlogChoices.includes(rules.backlog_rows)) {
|
|
@@ -209,7 +248,7 @@ const normaliseWorkspace = (workspace, index, problems, warnings) => {
|
|
|
209
248
|
if (tableNotes.length) {
|
|
210
249
|
warnings[id] = tableNotes;
|
|
211
250
|
}
|
|
212
|
-
return {
|
|
251
|
+
return /** @type {any} */ ({
|
|
213
252
|
id,
|
|
214
253
|
links: normaliseLinks(workspace.links),
|
|
215
254
|
name: String(workspace.name || workspace.id || `Workspace ${index + 1}`),
|
|
@@ -217,7 +256,8 @@ const normaliseWorkspace = (workspace, index, problems, warnings) => {
|
|
|
217
256
|
rules,
|
|
218
257
|
key_pattern: workspace.key_pattern || DEFAULT_KEY_PATTERN,
|
|
219
258
|
theme: normaliseTheme(workspace.theme, problems, id),
|
|
220
|
-
|
|
259
|
+
...(HIDDEN_MODES.includes(workspace.hidden) ? { hidden: workspace.hidden } : {}),
|
|
260
|
+
});
|
|
221
261
|
};
|
|
222
262
|
|
|
223
263
|
export const loadConfig = (env = process.env) => {
|
|
@@ -226,7 +266,7 @@ export const loadConfig = (env = process.env) => {
|
|
|
226
266
|
const raw = readJson(configFile);
|
|
227
267
|
const problems = [];
|
|
228
268
|
if (raw === null) {
|
|
229
|
-
problems.push(`No workspace config yet:
|
|
269
|
+
problems.push(`No workspace config yet: add a source in the workspace settings (top right of the workspace). It will be written to ${configFile}.`);
|
|
230
270
|
} else if (raw.parse_error) {
|
|
231
271
|
problems.push(`Cannot parse ${configFile}: ${raw.parse_error}`);
|
|
232
272
|
}
|
|
@@ -235,13 +275,17 @@ export const loadConfig = (env = process.env) => {
|
|
|
235
275
|
const { workspaces: normalised, problems: shapeProblems, warnings } = normaliseWorkspaces(rawWorkspaces);
|
|
236
276
|
const warningLines = Object.entries(warnings).flatMap(([id, lines]) => lines.map((line) => `Workspace "${id}": ${line}.`));
|
|
237
277
|
const workspaces = normalised.filter((workspace) => WORKSPACE_ID.test(workspace.id));
|
|
278
|
+
const health = normaliseHealth(source, problems);
|
|
279
|
+
const pollSeconds = Number(source.poll_seconds || GLOBAL_SETTINGS.poll_seconds.fallback);
|
|
280
|
+
const historyDays = Number(source.history_days || GLOBAL_SETTINGS.history_days.fallback);
|
|
238
281
|
return {
|
|
239
282
|
dataDir,
|
|
240
283
|
configFile,
|
|
241
284
|
port: resolvePort(dataDir, env),
|
|
242
|
-
pollSeconds
|
|
243
|
-
historyDays
|
|
244
|
-
health
|
|
285
|
+
pollSeconds,
|
|
286
|
+
historyDays,
|
|
287
|
+
health,
|
|
288
|
+
settings: { poll_seconds: pollSeconds, history_days: historyDays, ...health },
|
|
245
289
|
feedback: withoutRetiredFeedbackKeys(source.feedback),
|
|
246
290
|
notifications: normaliseNotifications(source.notifications, problems),
|
|
247
291
|
workspaces,
|
|
@@ -307,6 +351,9 @@ export const normaliseWorkspaces = (rawWorkspaces) => {
|
|
|
307
351
|
problems.push(`Workspace "${workspace.id}" has two connectors with the same id; give each an "id".`);
|
|
308
352
|
}
|
|
309
353
|
}
|
|
354
|
+
if (workspaces.length && workspaces.every((workspace) => workspace.hidden)) {
|
|
355
|
+
problems.push('Keep at least one workspace in the rail: parked and paused workspaces are out of it.');
|
|
356
|
+
}
|
|
310
357
|
const violations = problems.map(toViolation);
|
|
311
358
|
return { workspaces, problems: violations.map((entry) => entry.description), violations, warnings };
|
|
312
359
|
};
|
|
@@ -315,10 +362,17 @@ const RETIRED_FEEDBACK_KEYS = ['workspace', 'team', 'project', 'token_service',
|
|
|
315
362
|
|
|
316
363
|
const withoutRetiredFeedbackKeys = (feedback) => (isObject(feedback) ? Object.fromEntries(Object.entries(feedback).filter(([key]) => !RETIRED_FEEDBACK_KEYS.includes(key))) : {});
|
|
317
364
|
|
|
365
|
+
const storedConfig = (configFile) => {
|
|
366
|
+
const current = readJson(configFile);
|
|
367
|
+
return current && !current.parse_error ? current : {};
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
/** Writes `patch` over the config file's top-level keys and leaves every other key as it is; an unparsable file is replaced, the previous one stays as config.json.bak. */
|
|
371
|
+
const mergeIntoConfig = (configFile, patch) => writeConfig(configFile, { ...storedConfig(configFile), ...patch });
|
|
372
|
+
|
|
318
373
|
/** Writes the workspace list into the config file; the previous file stays as config.json.bak. */
|
|
319
374
|
export const saveWorkspaces = (configFile, rawWorkspaces) => {
|
|
320
|
-
const
|
|
321
|
-
const source = current && !current.parse_error ? current : {};
|
|
375
|
+
const source = storedConfig(configFile);
|
|
322
376
|
delete source.github;
|
|
323
377
|
delete source.jira;
|
|
324
378
|
delete source.rules;
|
|
@@ -332,10 +386,10 @@ export const saveWorkspaces = (configFile, rawWorkspaces) => {
|
|
|
332
386
|
};
|
|
333
387
|
|
|
334
388
|
/** Writes the validated notifications block into the config file. */
|
|
335
|
-
export const saveNotifications = (configFile, notifications) => {
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
389
|
+
export const saveNotifications = (configFile, notifications) => mergeIntoConfig(configFile, { notifications });
|
|
390
|
+
|
|
391
|
+
/** Writes the validated monitor-wide numbers (poll_seconds, history_days, stale_minutes, failing_polls) into the config file, leaving every other key as it is. */
|
|
392
|
+
export const saveSettings = (configFile, settings) => mergeIntoConfig(configFile, settings);
|
|
339
393
|
|
|
340
394
|
const writeConfig = (configFile, next) => {
|
|
341
395
|
mkdirSync(dirname(configFile), { recursive: true });
|
package/lib/default-table.mjs
CHANGED
|
@@ -44,6 +44,7 @@ export const defaultTable = (rules) => [
|
|
|
44
44
|
},
|
|
45
45
|
{ when: { 'ticket.status': 'code_review' }, then: { whose: 'reviewer', next: 'Code review' } },
|
|
46
46
|
{ when: { 'ticket.status': 'qa' }, then: { whose: 'them', next: 'Testing' } },
|
|
47
|
+
{ when: { 'ticket.status': 'release' }, then: { whose: 'release', next: 'the next release' } },
|
|
47
48
|
{ when: { 'ticket.status': 'on_hold', 'ticket.hold_reason': { present: true } }, then: { whose: 'them', next: 'On hold: {ticket.hold_reason}', hold: true } },
|
|
48
49
|
{ when: { 'ticket.status': 'on_hold' }, then: { whose: 'them', next: 'On hold' } },
|
|
49
50
|
{
|
package/lib/errors.mjs
CHANGED
|
@@ -9,7 +9,7 @@ const REASONS = {
|
|
|
9
9
|
INVALID_BODY: { code: 400, status: 'INVALID_ARGUMENT', message: 'The request body is not valid JSON.' },
|
|
10
10
|
INVALID_ARGUMENT: { code: 400, status: 'INVALID_ARGUMENT', message: 'A required value is missing or has the wrong shape.' },
|
|
11
11
|
INVALID_CONFIG: { code: 400, status: 'INVALID_ARGUMENT', message: 'The workspace list has problems; nothing was saved.' },
|
|
12
|
-
NO_WORKSPACE: { code: 412, status: 'FAILED_PRECONDITION', message: 'No workspace is configured;
|
|
12
|
+
NO_WORKSPACE: { code: 412, status: 'FAILED_PRECONDITION', message: 'No workspace is configured; add one with + in the rail.' },
|
|
13
13
|
UPSTREAM_FAILED: { code: 502, status: 'UNAVAILABLE', message: 'The tracker did not answer; try again in a minute.' },
|
|
14
14
|
RATE_LIMITED: { code: 429, status: 'RESOURCE_EXHAUSTED', message: 'The source is rate-limited; try again later.' },
|
|
15
15
|
OPEN_TOO_SOON: { code: 429, status: 'RESOURCE_EXHAUSTED', message: 'The window was just opened; wait a few seconds before opening it again.' },
|
package/lib/facts.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import { DAY, oneline, newestFirst } from './text.mjs';
|
|
|
4
4
|
export const shortDate = (iso) => new Date(iso).toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
|
5
5
|
|
|
6
6
|
const CATEGORY_GROUPS = { new: 'todo', indeterminate: 'in_progress', done: 'done' };
|
|
7
|
-
const CHAIN_GROUPS = ['backlog', 'passed', 'code_review', 'qa', 'on_hold'];
|
|
7
|
+
const CHAIN_GROUPS = ['backlog', 'passed', 'code_review', 'qa', 'release', 'on_hold'];
|
|
8
8
|
|
|
9
9
|
/** The closed fact dictionary a table row may test or quote, each with its plain words.
|
|
10
10
|
* Rules: docs/mechanisms/rules-table.md, Facts. */
|