austere-axi 0.4.0 → 0.4.1

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/README.md CHANGED
@@ -216,7 +216,8 @@ Off loopback, a request without a valid invite or the owner key is refused. The
216
216
  | `AUSTERE_AXI_NO_OPEN` | | `1` = never open a browser |
217
217
  | `AUSTERE_AXI_HOST` | | extra bind address (never `0.0.0.0`) |
218
218
  | `AUSTERE_AXI_NO_TAILSCALE` | | `1` = loopback only |
219
- | `AUSTERE_AXI_LINK_HOST` | | host in the printed remote link |
219
+ | `AUSTERE_AXI_LINK_HOST` | | host in the printed remote link (`http://<host>:<port>`) |
220
+ | `AUSTERE_AXI_LINK_URL` | | full origin of the printed remote link, e.g. `https://austere.example.com` behind a TLS proxy; the host is accepted without `ALLOWED_HOSTS` |
220
221
  | `AUSTERE_AXI_ALLOWED_HOSTS` | | extra `Host` headers to accept (reverse proxy) |
221
222
  | `AUSTERE_AXI_DISCONNECT_GRACE_MS` | `300000` | how long a dropped connection can stay away before `poll` returns; `0` disables |
222
223
  | `AUSTERE_AXI_IDLE_TIMEOUT_MS` | `1800000` | idle shutdown; `0` disables (the hub never idles out) |
package/dist/austere.js CHANGED
@@ -1598,11 +1598,11 @@ dataLane = Group("DATA STORES WITH A HEADING LONGER THAN THE COLUMN IS WIDE", [c
1598
1598
  <button type="button" data-act="queue" class="primary">Queue</button>
1599
1599
  </span>
1600
1600
  </div>
1601
- </div>`,document.body.appendChild(a);let r=a.querySelector(".austere-hover"),n=a.querySelector(".austere-hover-label"),o=a.querySelector(".austere-selbtn"),i=a.querySelector(".austere-pop"),l=a.querySelector(".austere-pop-path"),s=a.querySelector(".austere-pop-quote"),u=a.querySelector("textarea"),d=null,p=null,f=0,m=null,x=H=>!!H?.closest?.(`[${wX}]`),v=H=>H?.closest?.("[data-oui-id]")||null;function w(H,W,K=4){H.style.left=`${W.left-K}px`,H.style.top=`${W.top-K}px`,H.style.width=`${W.width+K*2}px`,H.style.height=`${W.height+K*2}px`}function b(H){if(!H){r.hidden=!0;return}w(r,IX(H)),n.textContent=`${H.getAttribute("data-oui-id")} \xB7 ${H.getAttribute("data-oui-c")}`,r.hidden=!1}function k(H){t!=="annotate"||!i.hidden||(m=H.target,!f&&(f=requestAnimationFrame(()=>{f=0,!x(m)&&b(v(m))})))}function I(){let H=window.getSelection();return!!H&&!H.isCollapsed&&H.toString().trim().length>0}function L(H){if(t!=="annotate"||x(H.target))return;if(!i.hidden){H.preventDefault(),H.stopPropagation();return}if(I()||!H.altKey&&H.target.closest(e$a))return;let W=v(H.target);W&&(H.preventDefault(),H.stopPropagation(),E({target:W,el:H.target,point:{x:H.clientX,y:H.clientY}}))}function M(){if(t!=="annotate"||!i.hidden)return;let H=window.getSelection();if(!H||H.isCollapsed||!H.rangeCount){o.hidden=!0,p=null;return}let W=H.toString().trim(),K=H.getRangeAt(0),J=K.commonAncestorContainer;J.nodeType!==1&&(J=J.parentElement);let F=v(J);if(!W||!F||x(J)){o.hidden=!0,p=null;return}let ne=K.getBoundingClientRect();p={target:F,el:J,selection:xv(W,600),point:{x:ne.left,y:ne.bottom}},o.hidden=!1;let ue=o.offsetWidth||140;o.style.left=`${Math.max(8,Math.min(window.innerWidth-ue-8,ne.left+ne.width/2-ue/2))}px`,o.style.top=`${Math.max(8,Math.min(window.innerHeight-40,ne.bottom+8))}px`}let _=0;function A(){clearTimeout(_),_=setTimeout(M,120)}o.addEventListener("mousedown",H=>H.preventDefault()),o.addEventListener("click",H=>{H.preventDefault(),H.stopPropagation(),p&&E(p),o.hidden=!0});function D(){l.textContent="",d.path.forEach((H,W)=>{let K=document.createElement("button");if(K.type="button",K.className="austere-chip"+(H.id===d.targetId?" active":""),K.textContent=H.id,K.title=H.component,K.addEventListener("click",()=>{d.targetId=H.id,d.component=H.component,D(),u.focus()}),l.appendChild(K),W<d.path.length-1){let J=document.createElement("span");J.className="austere-chip-sep",J.textContent="\u203A",l.appendChild(J)}})}function E({target:H,el:W,selection:K,point:J}){let F=t$a(H),ne=a$a(H,W),ue=IEt(H,W),U;K||(U=xv((ue||W).innerText||(ue||W).textContent,200)||void 0,ue&&U===ne?.part&&(U=void 0)),d={targetId:H.getAttribute("data-oui-id"),component:H.getAttribute("data-oui-c"),path:F,selection:K||void 0,excerpt:U,detail:ne},D();let Ae=K||U||ne?.part||"";s.textContent=xv(Ae,220),s.hidden=!Ae,u.value="",r.hidden=!0,o.hidden=!0,b(H),r.classList.add("locked"),i.hidden=!1,T(J),u.focus()}function T(H){if(window.innerWidth<640){i.classList.add("sheet"),i.style.left=i.style.top="";return}i.classList.remove("sheet");let W=i.offsetWidth||360,K=i.offsetHeight||200,J=H.x+12,F=H.y+12;J+W>window.innerWidth-8&&(J=Math.max(8,H.x-W-12)),F+K>window.innerHeight-8&&(F=Math.max(8,H.y-K-12)),i.style.left=`${J}px`,i.style.top=`${F}px`}function O(){i.hidden=!0,r.classList.remove("locked"),r.hidden=!0,d=null}function $(){let H=u.value.trim();if(!d)return;if(!H&&!d.selection){u.focus(),u.classList.add("shake"),setTimeout(()=>u.classList.remove("shake"),400);return}let W={kind:"annotation",target:d.targetId,component:d.component,path:d.path.map(J=>J.id),note:H};d.selection&&(W.selection=d.selection),d.excerpt&&!d.selection&&(W.excerpt=d.excerpt),d.detail&&d.targetId===d.path[d.path.length-1].id&&(W.detail=d.detail),e({type:"annotate",item:W});let K=d.targetId;O(),window.getSelection()?.removeAllRanges(),q([K],"queued")}i.addEventListener("click",H=>{let W=H.target.closest("[data-act]")?.getAttribute("data-act");W==="cancel"&&O(),W==="queue"&&$()}),u.addEventListener("keydown",H=>{H.key==="Enter"&&!H.shiftKey&&!H.isComposing?(H.preventDefault(),$()):H.key==="Escape"&&(H.preventDefault(),u.value.trim()||O())}),document.addEventListener("mousemove",k,{passive:!0}),document.addEventListener("click",L,!0),document.addEventListener("mouseup",A),document.addEventListener("keyup",H=>{(H.shiftKey||H.key.startsWith("Arrow"))&&A()}),document.addEventListener("selectionchange",()=>{"ontouchstart"in window&&A()}),document.addEventListener("keydown",H=>{(H.metaKey||H.ctrlKey)&&(H.key==="i"||H.key==="I")&&(H.preventDefault(),e({type:"toggle-mode"}))}),window.addEventListener("scroll",()=>{i.hidden&&(r.hidden=!0,o.hidden=!0)},{passive:!0}),document.addEventListener("mouseleave",()=>{i.hidden&&(r.hidden=!0)});function P(H,W,K,J=new Set){if(J.has(H))return[];J.add(H);let F=document.querySelectorAll(`[data-oui-id="${CSS.escape(H)}"]`);return F.length?Array.from(F):!W||!K?[]:K(W,H).flatMap(ne=>P(ne,W,K,J))}function q(H,W="changed",K,J){let F=new Set;for(let ue of H)for(let U of P(ue,K,J))F.add(U);let ne=Array.from(F).filter(ue=>!Array.from(F).some(U=>U!==ue&&U.contains(ue)));for(let ue of ne){let U=IX(ue),Ae=document.createElement("div");Ae.setAttribute(wX,""),Ae.className=`austere-flash ${W}`,Ae.style.left=`${U.left+window.scrollX-4}px`,Ae.style.top=`${U.top+window.scrollY-4}px`,Ae.style.width=`${U.width+8}px`,Ae.style.height=`${U.height+8}px`,document.body.appendChild(Ae),setTimeout(()=>Ae.remove(),2400)}return ne}function V(H,W,K){let J=P(H,W,K);if(!J.length)return!1;let F=IX(J[0]),ne=F.top+window.scrollY-Math.max(24,(window.innerHeight-F.height)/3);return window.scrollTo({top:Math.max(0,ne),behavior:"smooth"}),setTimeout(()=>q([H],"changed",W,K),350),!0}return{setMode(H){t=H==="annotate"?"annotate":"explore",document.documentElement.classList.toggle("austere-annotating",t==="annotate"),t!=="annotate"&&(O(),o.hidden=!0,r.hidden=!0)},flash:q,reveal:V}}var LEt={mermaid:{name:"@austere-axi/pack-mermaid",version:"0.4.0",components:["Mermaid"]},shadcn:{name:"@austere-axi/pack-shadcn",version:"0.4.0",components:["Progress","Avatar","AvatarGroup","Tooltip","HoverCard","Breadcrumb","BreadcrumbItem","Kbd","Skeleton","Calendar","Timeline","TimelineItem"]}};function r$a(e){let t=e.lastIndexOf("@");return t<=0?{name:e,version:""}:{name:e.slice(0,t),version:e.slice(t+1)}}var n$a=/^(@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/,o$a=/^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/;function _Et(e){let t=[];for(let a of String(e||"").split(",")){let[r,n]=a.trim().split(/\s+/);if(!r)continue;let{name:o,version:i}=r$a(r);!n$a.test(o)||!o$a.test(i)||t.some(l=>l.name===o)||t.push(n&&/^sha(256|384|512)-[A-Za-z0-9+/=]+$/.test(n)?{name:o,version:i,integrity:n}:{name:o,version:i})}return t}var MEt="https://cdn.jsdelivr.net/npm";function CEt(e=document){let t=e.querySelector('meta[name="austere-packs"]');return t?_Et(t.getAttribute("content")||""):[]}function AEt(e,t){let a=[...t];for(let[r,n]of Object.entries(LEt))a.some(o=>o.name===n.name)||n.components.some(o=>new RegExp(`\\b${o}\\s*\\(`).test(e))&&a.push({name:n.name,version:n.version,bundled:r});return a}function SX(){return/\/__austere\/$/.test(w2)}function i$a(e){if(e.bundled)return{src:B4(`packs/${e.bundled}/pack.js`)||`${MEt}/austere-axi@${e.version}/dist/packs/${e.bundled}/pack.js`};if(SX()){let t=e.integrity?`?integrity=${encodeURIComponent(e.integrity)}`:"";return{src:`${w2}installed-packs/${encodeURIComponent(`${e.name}@${e.version}`)}/pack.js${t}`}}return{src:`${MEt}/${e.name}@${e.version}/dist/pack.js`,integrity:e.integrity}}function l$a(e,t){return new Promise((a,r)=>{let n=document.createElement("script");n.src=e,n.async=!0,t&&(n.integrity=t,n.crossOrigin="anonymous"),n.onload=()=>a(),n.onerror=()=>r(new Error(`could not load ${e}`)),document.head.appendChild(n)})}async function NEt(e,{onError:t}={}){let a=e.filter(r=>!ys().some(n=>n.name===r.name));return await Promise.all(a.map(r=>{let{src:n,integrity:o}=i$a(r);return l$a(n,o).then(()=>{ys().some(i=>i.name===r.name)||t?.(r,new Error(`${n} loaded but did not register pack ${r.name}`))},i=>t?.(r,i))})),ys()}function TEt(e){for(let t of e){if(!t.css)continue;let a=`austere-pack-css-${t.name.replace(/[^a-z0-9]/gi,"-")}`;if(document.getElementById(a))continue;let r=document.createElement("style");r.id=a,r.textContent=t.css,document.head.appendChild(r)}}var s$a=/[A-Za-z_$]/;function c$a(e){let t=[],a=String(e??""),r=0,n=1,o=a.length;for(;r<o;){let i=a[r];if(i===`
1601
+ </div>`,document.body.appendChild(a);let r=a.querySelector(".austere-hover"),n=a.querySelector(".austere-hover-label"),o=a.querySelector(".austere-selbtn"),i=a.querySelector(".austere-pop"),l=a.querySelector(".austere-pop-path"),s=a.querySelector(".austere-pop-quote"),u=a.querySelector("textarea"),d=null,p=null,f=0,m=null,x=H=>!!H?.closest?.(`[${wX}]`),v=H=>H?.closest?.("[data-oui-id]")||null;function w(H,W,K=4){H.style.left=`${W.left-K}px`,H.style.top=`${W.top-K}px`,H.style.width=`${W.width+K*2}px`,H.style.height=`${W.height+K*2}px`}function b(H){if(!H){r.hidden=!0;return}w(r,IX(H)),n.textContent=`${H.getAttribute("data-oui-id")} \xB7 ${H.getAttribute("data-oui-c")}`,r.hidden=!1}function k(H){t!=="annotate"||!i.hidden||(m=H.target,!f&&(f=requestAnimationFrame(()=>{f=0,!x(m)&&b(v(m))})))}function I(){let H=window.getSelection();return!!H&&!H.isCollapsed&&H.toString().trim().length>0}function L(H){if(t!=="annotate"||x(H.target))return;if(!i.hidden){H.preventDefault(),H.stopPropagation();return}if(I()||!H.altKey&&H.target.closest(e$a))return;let W=v(H.target);W&&(H.preventDefault(),H.stopPropagation(),E({target:W,el:H.target,point:{x:H.clientX,y:H.clientY}}))}function M(){if(t!=="annotate"||!i.hidden)return;let H=window.getSelection();if(!H||H.isCollapsed||!H.rangeCount){o.hidden=!0,p=null;return}let W=H.toString().trim(),K=H.getRangeAt(0),J=K.commonAncestorContainer;J.nodeType!==1&&(J=J.parentElement);let F=v(J);if(!W||!F||x(J)){o.hidden=!0,p=null;return}let ne=K.getBoundingClientRect();p={target:F,el:J,selection:xv(W,600),point:{x:ne.left,y:ne.bottom}},o.hidden=!1;let ue=o.offsetWidth||140;o.style.left=`${Math.max(8,Math.min(window.innerWidth-ue-8,ne.left+ne.width/2-ue/2))}px`,o.style.top=`${Math.max(8,Math.min(window.innerHeight-40,ne.bottom+8))}px`}let _=0;function A(){clearTimeout(_),_=setTimeout(M,120)}o.addEventListener("mousedown",H=>H.preventDefault()),o.addEventListener("click",H=>{H.preventDefault(),H.stopPropagation(),p&&E(p),o.hidden=!0});function D(){l.textContent="",d.path.forEach((H,W)=>{let K=document.createElement("button");if(K.type="button",K.className="austere-chip"+(H.id===d.targetId?" active":""),K.textContent=H.id,K.title=H.component,K.addEventListener("click",()=>{d.targetId=H.id,d.component=H.component,D(),u.focus()}),l.appendChild(K),W<d.path.length-1){let J=document.createElement("span");J.className="austere-chip-sep",J.textContent="\u203A",l.appendChild(J)}})}function E({target:H,el:W,selection:K,point:J}){let F=t$a(H),ne=a$a(H,W),ue=IEt(H,W),U;K||(U=xv((ue||W).innerText||(ue||W).textContent,200)||void 0,ue&&U===ne?.part&&(U=void 0)),d={targetId:H.getAttribute("data-oui-id"),component:H.getAttribute("data-oui-c"),path:F,selection:K||void 0,excerpt:U,detail:ne},D();let Ae=K||U||ne?.part||"";s.textContent=xv(Ae,220),s.hidden=!Ae,u.value="",r.hidden=!0,o.hidden=!0,b(H),r.classList.add("locked"),i.hidden=!1,T(J),u.focus()}function T(H){if(window.innerWidth<640){i.classList.add("sheet"),i.style.left=i.style.top="";return}i.classList.remove("sheet");let W=i.offsetWidth||360,K=i.offsetHeight||200,J=H.x+12,F=H.y+12;J+W>window.innerWidth-8&&(J=Math.max(8,H.x-W-12)),F+K>window.innerHeight-8&&(F=Math.max(8,H.y-K-12)),i.style.left=`${J}px`,i.style.top=`${F}px`}function O(){i.hidden=!0,r.classList.remove("locked"),r.hidden=!0,d=null}function $(){let H=u.value.trim();if(!d)return;if(!H&&!d.selection){u.focus(),u.classList.add("shake"),setTimeout(()=>u.classList.remove("shake"),400);return}let W={kind:"annotation",target:d.targetId,component:d.component,path:d.path.map(J=>J.id),note:H};d.selection&&(W.selection=d.selection),d.excerpt&&!d.selection&&(W.excerpt=d.excerpt),d.detail&&d.targetId===d.path[d.path.length-1].id&&(W.detail=d.detail),e({type:"annotate",item:W});let K=d.targetId;O(),window.getSelection()?.removeAllRanges(),q([K],"queued")}i.addEventListener("click",H=>{let W=H.target.closest("[data-act]")?.getAttribute("data-act");W==="cancel"&&O(),W==="queue"&&$()}),u.addEventListener("keydown",H=>{H.key==="Enter"&&!H.shiftKey&&!H.isComposing?(H.preventDefault(),$()):H.key==="Escape"&&(H.preventDefault(),u.value.trim()||O())}),document.addEventListener("mousemove",k,{passive:!0}),document.addEventListener("click",L,!0),document.addEventListener("mouseup",A),document.addEventListener("keyup",H=>{(H.shiftKey||H.key.startsWith("Arrow"))&&A()}),document.addEventListener("selectionchange",()=>{"ontouchstart"in window&&A()}),document.addEventListener("keydown",H=>{(H.metaKey||H.ctrlKey)&&(H.key==="i"||H.key==="I")&&(H.preventDefault(),e({type:"toggle-mode"}))}),window.addEventListener("scroll",()=>{i.hidden&&(r.hidden=!0,o.hidden=!0)},{passive:!0}),document.addEventListener("mouseleave",()=>{i.hidden&&(r.hidden=!0)});function P(H,W,K,J=new Set){if(J.has(H))return[];J.add(H);let F=document.querySelectorAll(`[data-oui-id="${CSS.escape(H)}"]`);return F.length?Array.from(F):!W||!K?[]:K(W,H).flatMap(ne=>P(ne,W,K,J))}function q(H,W="changed",K,J){let F=new Set;for(let ue of H)for(let U of P(ue,K,J))F.add(U);let ne=Array.from(F).filter(ue=>!Array.from(F).some(U=>U!==ue&&U.contains(ue)));for(let ue of ne){let U=IX(ue),Ae=document.createElement("div");Ae.setAttribute(wX,""),Ae.className=`austere-flash ${W}`,Ae.style.left=`${U.left+window.scrollX-4}px`,Ae.style.top=`${U.top+window.scrollY-4}px`,Ae.style.width=`${U.width+8}px`,Ae.style.height=`${U.height+8}px`,document.body.appendChild(Ae),setTimeout(()=>Ae.remove(),2400)}return ne}function V(H,W,K){let J=P(H,W,K);if(!J.length)return!1;let F=IX(J[0]),ne=F.top+window.scrollY-Math.max(24,(window.innerHeight-F.height)/3);return window.scrollTo({top:Math.max(0,ne),behavior:"smooth"}),setTimeout(()=>q([H],"changed",W,K),350),!0}return{setMode(H){t=H==="annotate"?"annotate":"explore",document.documentElement.classList.toggle("austere-annotating",t==="annotate"),t!=="annotate"&&(O(),o.hidden=!0,r.hidden=!0)},flash:q,reveal:V}}var LEt={mermaid:{name:"@austere-axi/pack-mermaid",version:"0.4.1",components:["Mermaid"]},shadcn:{name:"@austere-axi/pack-shadcn",version:"0.4.1",components:["Progress","Avatar","AvatarGroup","Tooltip","HoverCard","Breadcrumb","BreadcrumbItem","Kbd","Skeleton","Calendar","Timeline","TimelineItem"]}};function r$a(e){let t=e.lastIndexOf("@");return t<=0?{name:e,version:""}:{name:e.slice(0,t),version:e.slice(t+1)}}var n$a=/^(@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/,o$a=/^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/;function _Et(e){let t=[];for(let a of String(e||"").split(",")){let[r,n]=a.trim().split(/\s+/);if(!r)continue;let{name:o,version:i}=r$a(r);!n$a.test(o)||!o$a.test(i)||t.some(l=>l.name===o)||t.push(n&&/^sha(256|384|512)-[A-Za-z0-9+/=]+$/.test(n)?{name:o,version:i,integrity:n}:{name:o,version:i})}return t}var MEt="https://cdn.jsdelivr.net/npm";function CEt(e=document){let t=e.querySelector('meta[name="austere-packs"]');return t?_Et(t.getAttribute("content")||""):[]}function AEt(e,t){let a=[...t];for(let[r,n]of Object.entries(LEt))a.some(o=>o.name===n.name)||n.components.some(o=>new RegExp(`\\b${o}\\s*\\(`).test(e))&&a.push({name:n.name,version:n.version,bundled:r});return a}function SX(){return/\/__austere\/$/.test(w2)}function i$a(e){if(e.bundled)return{src:B4(`packs/${e.bundled}/pack.js`)||`${MEt}/austere-axi@${e.version}/dist/packs/${e.bundled}/pack.js`};if(SX()){let t=e.integrity?`?integrity=${encodeURIComponent(e.integrity)}`:"";return{src:`${w2}installed-packs/${encodeURIComponent(`${e.name}@${e.version}`)}/pack.js${t}`}}return{src:`${MEt}/${e.name}@${e.version}/dist/pack.js`,integrity:e.integrity}}function l$a(e,t){return new Promise((a,r)=>{let n=document.createElement("script");n.src=e,n.async=!0,t&&(n.integrity=t,n.crossOrigin="anonymous"),n.onload=()=>a(),n.onerror=()=>r(new Error(`could not load ${e}`)),document.head.appendChild(n)})}async function NEt(e,{onError:t}={}){let a=e.filter(r=>!ys().some(n=>n.name===r.name));return await Promise.all(a.map(r=>{let{src:n,integrity:o}=i$a(r);return l$a(n,o).then(()=>{ys().some(i=>i.name===r.name)||t?.(r,new Error(`${n} loaded but did not register pack ${r.name}`))},i=>t?.(r,i))})),ys()}function TEt(e){for(let t of e){if(!t.css)continue;let a=`austere-pack-css-${t.name.replace(/[^a-z0-9]/gi,"-")}`;if(document.getElementById(a))continue;let r=document.createElement("style");r.id=a,r.textContent=t.css,document.head.appendChild(r)}}var s$a=/[A-Za-z_$]/;function c$a(e){let t=[],a=String(e??""),r=0,n=1,o=a.length;for(;r<o;){let i=a[r];if(i===`
1602
1602
  `){n++,r++;continue}if(i===" "||i===" "||i==="\r"){r++;continue}if(i==="#"||i==="/"&&a[r+1]==="/"){for(;r<o&&a[r]!==`
1603
1603
  `;)r++;continue}let l=r,s=n,u=0,d=null,p=!1;for(;r<o;){let x=a[r];if(d){p?p=!1:x==="\\"?p=!0:x===d&&(d=null),x===`
1604
1604
  `&&n++,r++;continue}if(x==='"'||x==="'"){d=x,r++;continue}if(x==="#"||x==="/"&&a[r+1]==="/"){for(;r<o&&a[r]!==`
1605
1605
  `;)r++;continue}if(x==="("||x==="["||x==="{")u++;else if(x===")"||x==="]"||x==="}")u=Math.max(0,u-1);else if(x===`
1606
- `){if(u===0&&!u$a(a,r+1))break;n++}r++}let f=a.slice(l,r).replace(/\s+$/,""),m=/^\s*([A-Za-z_$][\w$]*)\s*=/.exec(f);m&&s$a.test(m[1][0])&&t.push({id:m[1],text:f,line:s,endLine:n})}return t}function u$a(e,t){let a=t;for(;a<e.length&&(e[a]===" "||e[a]===" ");)a++;return e[a]==="?"||e[a]===":"}function vv(e){let t=new Map;for(let a of c$a(e))t.set(a.id,a);return t}function REt(e,t){let a=vv(e),r=vv(t),n=[];for(let[o,i]of r){let l=a.get(o);(!l||zEt(l.text)!==zEt(i.text))&&n.push(o)}return n}function LX(e,t){let a=new RegExp(`(^|[^\\w$])${p$a(t)}(?![\\w$])`),r=[];for(let[n,o]of e){if(n===t)continue;let i=o.text.slice(o.text.indexOf("=")+1);a.test(d$a(i))&&r.push(n)}return r}function d$a(e){return e.replace(/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/gs,'""')}function zEt(e){return e.replace(/\s+/g," ").trim()}function p$a(e){return e.replace(/[$()*+.?[\\\]^{|}]/g,"\\$&")}var kv=ae(It(),1),CX="0.4.0",f$a='script[type="text/openui"], script[type="text/openui-lang"]';function m$a(){if(document.getElementById("austere-styles"))return;let e=document.createElement("style");if(e.id="austere-styles",e.textContent=[Coe,Loe,_oe,Moe].join(`
1606
+ `){if(u===0&&!u$a(a,r+1))break;n++}r++}let f=a.slice(l,r).replace(/\s+$/,""),m=/^\s*([A-Za-z_$][\w$]*)\s*=/.exec(f);m&&s$a.test(m[1][0])&&t.push({id:m[1],text:f,line:s,endLine:n})}return t}function u$a(e,t){let a=t;for(;a<e.length&&(e[a]===" "||e[a]===" ");)a++;return e[a]==="?"||e[a]===":"}function vv(e){let t=new Map;for(let a of c$a(e))t.set(a.id,a);return t}function REt(e,t){let a=vv(e),r=vv(t),n=[];for(let[o,i]of r){let l=a.get(o);(!l||zEt(l.text)!==zEt(i.text))&&n.push(o)}return n}function LX(e,t){let a=new RegExp(`(^|[^\\w$])${p$a(t)}(?![\\w$])`),r=[];for(let[n,o]of e){if(n===t)continue;let i=o.text.slice(o.text.indexOf("=")+1);a.test(d$a(i))&&r.push(n)}return r}function d$a(e){return e.replace(/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/gs,'""')}function zEt(e){return e.replace(/\s+/g," ").trim()}function p$a(e){return e.replace(/[$()*+.?[\\\]^{|}]/g,"\\$&")}var kv=ae(It(),1),CX="0.4.1",f$a='script[type="text/openui"], script[type="text/openui-lang"]';function m$a(){if(document.getElementById("austere-styles"))return;let e=document.createElement("style");if(e.id="austere-styles",e.textContent=[Coe,Loe,_oe,Moe].join(`
1607
1607
  `),document.head.appendChild(e),!document.querySelector('meta[name="viewport"]')){let t=document.createElement("meta");t.name="viewport",t.content="width=device-width, initial-scale=1",document.head.appendChild(t)}document.documentElement.classList.add("austere"),document.documentElement.style.colorScheme="dark"}function PEt(e){if(!e||typeof e!="object")return;let t={};for(let[a,r]of Object.entries(e))if(!a.startsWith("$"))if(r&&typeof r=="object"&&!Array.isArray(r))if("value"in r&&Object.keys(r).length<=3)t[a]=r.value;else{let n=PEt(r);n&&Object.keys(n).length&&(t[a]=n)}else t[a]=r;return Object.keys(t).length?t:void 0}function h$a(){let e=window.parent&&window.parent!==window,t=new Map,a=r=>{e&&window.parent.postMessage({austere:1,...r},"*")};return window.addEventListener("message",r=>{if(r.source!==window.parent)return;let n=r.data;!n||n.austere!==1||typeof n.type!="string"||t.get(n.type)?.forEach(o=>o(n))}),{framed:e,connected:!1,send:a,on(r,n){return t.has(r)||t.set(r,new Set),t.get(r).add(n),()=>t.get(r).delete(n)}}}function _X(e){let t=document.createElement("div");t.className="austere-toast",t.setAttribute("data-austere-ui",""),t.textContent=e,document.body.appendChild(t),setTimeout(()=>t.classList.add("out"),2600),setTimeout(()=>t.remove(),3100)}function g$a(e){let t=[e.message];return e.form&&t.push(`form: ${JSON.stringify(e.form)}`),e.context!==void 0&&t.push(`context: ${JSON.stringify(e.context)}`),t.join(`
1608
1608
  `)}var MX=null,$Et=new Set,bv=yv,EEt="";function y$a(e,t){$Et.add(e.name);let a=!e.bundled&&SX()?" (`austere-axi check <file>` says why)":"";Rc({source:"pack",component:e.name,message:`pack ${e.name}@${e.version}: ${t.message}${a}`})}function x$a(){let e=ys(),t=e.map(a=>`${a.name}@${a.version}`).join(",");if(t===EEt)return bv;EEt=t,TEt(e);try{bv=e.length?kX(e.flatMap(a=>a.components),{onSkip:(a,r)=>Rc({source:"pack",component:a.name,message:r})}):yv}catch(a){Rc({source:"pack",message:`packs could not be added (${a.message}); rendering without them`}),bv=yv}return window.__austere.library=bv,bv}async function BEt(e){MX||(MX=CEt());let t=AEt(e,MX).filter(a=>!$Et.has(a.name));return t.some(a=>!ys().some(r=>r.name===a.name))&&await NEt(t,{onError:y$a}),x$a()}function v$a({initialProgram:e,initialLibrary:t,bridge:a,annotator:r,initialState:n}){let[o,i]=(0,ti.useState)({program:e,library:t}),l=o.program,s=(0,ti.useRef)(e),u=(0,ti.useRef)(null);(0,ti.useEffect)(()=>a.on("source",async({program:m})=>{if(typeof m!="string"||m===s.current)return;let x=REt(s.current,m);s.current=m;let v=await BEt(m);s.current===m&&(u.current=x,i({program:m,library:v}))}),[a]),(0,ti.useEffect)(()=>a.on("reveal",({id:m})=>{r?.reveal(m,vv(s.current),LX)}),[a,r]),(0,ti.useEffect)(()=>{let m=u.current;if(!m)return;u.current=null;let x=setTimeout(()=>{let v=vv(s.current),w=m.filter(b=>b!=="root");r?.flash(w,"changed",v,LX),a.send({type:"changes",ids:w})},120);return()=>clearTimeout(x)},[l,r,a]);let d=(0,ti.useCallback)(m=>{if(m.type==="open_url"){let w=m.params?.url;typeof w=="string"&&/^(https?:|mailto:)/i.test(w)&&window.open(w,"_blank","noopener");return}if(m.type!=="continue_conversation")return;let x={kind:"action",message:m.humanFriendlyMessage||"(button)"},v=PEt(m.formState);if(v&&(x.form=v),m.params?.context!==void 0&&(x.context=m.params.context),a.connected)a.send({type:"action",item:x}),_X("Queued for the agent \u2014 press Send when ready");else{let w=g$a(x);navigator.clipboard?.writeText(w).then(()=>_X("Copied \u2014 paste it to your agent"),()=>_X(w))}},[a]),p=(0,ti.useCallback)(m=>{a.send({type:"errors",errors:(m||[]).map(x=>({source:x.source,code:x.code,statementId:x.statementId,message:x.message,hint:x.hint}))})},[a]),f=(0,ti.useCallback)(m=>a.send({type:"state",state:m}),[a]);return(0,kv.jsx)(Soe,{mode:"dark",children:(0,kv.jsx)("main",{className:"austere-page",children:(0,kv.jsx)(v2,{response:l,library:o.library,isStreaming:!1,onAction:d,onError:p,onStateUpdate:f,initialState:n})})})}function DEt(){m$a();let e=document.querySelector(f$a),t=e?e.textContent:"",a=document.createElement("div");a.id="austere-root",document.body.appendChild(a);let r=h$a(),n=null,o=null,i=BEt(t),l=s=>o||(o=i.then(u=>(0,OEt.createRoot)(a).render((0,kv.jsx)(v$a,{initialProgram:t,initialLibrary:u,bridge:r,annotator:n,initialState:s}))));if(!r.framed){l(void 0);return}n=SEt({post:s=>r.send(s)}),nee(s=>r.send({type:"runtime-error",error:s})),r.on("init",async s=>{if(r.connected=!0,n.setMode(s.mode),await l(s.state||void 0),s.scrollY>0){let u=0,d=()=>{window.scrollTo(0,s.scrollY),Math.abs(window.scrollY-s.scrollY)>2&&++u<20&&setTimeout(d,50)};setTimeout(d,30)}r.send({type:"ready",title:document.title,version:CX})}),r.on("mode",s=>n.setMode(s.mode)),window.addEventListener("scroll",()=>r.send({type:"scroll",y:window.scrollY}),{passive:!0}),r.send({type:"hello",title:document.title,version:CX}),setTimeout(()=>l(void 0),600)}window.__austere={version:CX,library:yv,packs:ys};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",DEt,{once:!0}):DEt();})();
package/dist/catalog.json CHANGED
@@ -1 +1 @@
1
- {"format":1,"pack":"core","version":"0.4.0","groups":[{"name":"Layout","notes":["- A page is root = Stack([...sections], \"column\", \"l\"). Each section is a Card([CardHeader(title, subtitle), ...content]).","- Side by side: Stack([a, b], \"row\", \"m\", \"stretch\", \"start\", true). Cards in a row share the width.","- Tabs for alternative views, Accordion for detail the reader may skip, Steps for ordered procedures. Several blocks in one Card: label each with InlineHeader.","- Card children must match its list; anything else (Accordion, Tag, a Callout row) can sit in a Stack inside the Card, or directly in root's Stack."]},{"name":"Content","notes":["- MarkDownRenderer for prose, bullet lists and inline code; one statement per paragraph group so each is annotatable.","- Callout for the one thing the reader must not miss: a risk, a decision needed, a caveat.","- CodeBlock for a whole snippet. Image src may be a path relative to the artifact file.","- Architecture for systems: services, stores, queues, trust boundaries. Name every Node, Edge and Group. Over ~15 nodes, use lanes or split the diagram.","- lanes: for systems split by owner, repo or tier. Groups are the columns, left to right as listed; put every node in one, in reading order (plain groups for a heading without a frame). About 4 columns fit a laptop; wider ones scroll (check warns). Phones get the lanes stacked as bands. Pin row only to put boxes side by side or line them up.","- Diff for code changes: show the hunks that matter, not whole files. Hunk counts are recomputed, so only start lines matter.","- Mermaid for sequences, state machines, ER, gantt and quick flowcharts."]},{"name":"Data","notes":["- Table is COLUMN-oriented: Table([Col(\"Option\", names), Col(\"Effort\", efforts), Col(\"Risk\", risks)]). Col data may mix strings and components, e.g. [\"n/a\", Tag(\"high\", null, \"sm\", \"danger\")]. Cells are plain text (no markdown) and wrap; Col type \"number\" for figures.","- EntityList for key/value facts; OverviewCardBlock + OverviewCardItem + MetricIndicatorInline for a KPI row.","- EditableTable when the reviewer should correct or fill in values; edits arrive with the next @ToAssistant from its Form.","- Icon names are lucide kebab-case (circle-check, triangle-alert, git-branch)."]},{"name":"Charts","notes":["- Charts take column arrays: BarChart(labels, [Series(\"p50\", [..]), Series(\"p99\", [..])], \"grouped\", \"Week\", \"ms\").","- PieChart(labels, values) with plain number arrays."]},{"name":"Forms","notes":["- Form(name, buttons, fields). Define each FormControl as its own statement. Never nest a Form in a Form.","- To ask the reviewer to decide: OptionCards or RadioGroup inside a Form whose button is Button(\"Send decision\", @ToAssistant(\"Decision: rollout strategy\"), \"primary\"). The values arrive with that message as form: {<formName>: {<fieldName>: value}}; no context needed. Reviewers can always annotate and message, so add a notes field only when it belongs with the decision.","- OptionCards(\"pick\", \"single\", [OptionCard(\"a\", \"Option A\", \"why\", Icon(\"zap\")), OptionCard(\"b\", \"Option B\")], {required: true}, \"a\") — the last argument pre-selects.","- rules: {required: true, minLength: 10, ...}; the renderer shows the messages."]},{"name":"Buttons","notes":["- @ToAssistant(message, context?) queues a message to the agent; @OpenUrl(url) opens a link. A Button with no action sends its label.","- Use buttons for follow-ups the reviewer can pick in one click: \"Go with option B\", \"Split this into two PRs\"."]}],"components":[{"name":"Card","group":"Layout","signature":"Card(children: (TextContent | MarkDownRenderer | CardHeader | Callout | CodeBlock | Image | Separator | HorizontalBarChart | PieChart | ScatterChart | AreaChart | BarChart | LineChart | Table | TagBlock | Form | Buttons | Steps | InlineHeader | EntityList | EditableTable | OverviewCardBlock | Tabs | Stack | Architecture | Diff | FileTree | Mermaid | Progress | Avatar | AvatarGroup | Tooltip | HoverCard | Breadcrumb | Kbd | Skeleton | Calendar | Timeline)[], variant?: \"card\" | \"sunk\" | \"clear\", direction?: \"row\" | \"column\", gap?: \"none\" | \"xs\" | \"s\" | \"m\" | \"l\" | \"xl\" | \"2xl\", align?: \"start\" | \"center\" | \"end\" | \"stretch\" | \"baseline\", justify?: \"start\" | \"center\" | \"end\" | \"between\" | \"around\" | \"evenly\", wrap?: boolean)","description":"Styled container. variant: \"card\" (default, elevated) | \"sunk\" (recessed) | \"clear\" (transparent). Always full width. Accepts all Stack flex params (default: direction \"column\"). Cards flex to share space in row/wrap layouts.","tier":"core","pack":"core","examples":[]},{"name":"CardHeader","group":"Layout","signature":"CardHeader(title?: string, subtitle?: string)","description":"Header with optional title and subtitle","tier":"core","pack":"core","examples":[]},{"name":"TextContent","group":"Content","signature":"TextContent(text: string, size?: \"small\" | \"default\" | \"large\" | \"small-heavy\" | \"large-heavy\")","description":"Text block. Supports markdown. Optional size: \"small\" | \"default\" | \"large\" | \"small-heavy\" | \"large-heavy\".","tier":"core","pack":"core","examples":[]},{"name":"MarkDownRenderer","group":"Content","signature":"MarkDownRenderer(textMarkdown: string, variant?: \"clear\" | \"card\" | \"sunk\")","description":"Renders markdown text with optional container variant","tier":"core","pack":"core","examples":[]},{"name":"Callout","group":"Content","signature":"Callout(variant: \"info\" | \"warning\" | \"error\" | \"success\" | \"neutral\", title: string, description: string, visible?: $binding<boolean>)","description":"Callout banner. Optional visible is a reactive $boolean — auto-dismisses after 3s by setting $visible to false.","tier":"core","pack":"core","examples":[]},{"name":"Image","group":"Content","signature":"Image(alt: string, src?: string)","description":"Image with alt text and optional URL","tier":"core","pack":"core","examples":[]},{"name":"CodeBlock","group":"Content","signature":"CodeBlock(language: string, codeString: string)","description":"Syntax-highlighted code block","tier":"core","pack":"core","examples":[]},{"name":"InlineHeader","group":"Layout","signature":"InlineHeader(heading: string, description?: string)","description":"Compact section heading with an optional one-line description, for use inside cards.","tier":"core","pack":"core","examples":[]},{"name":"Table","group":"Data","signature":"Table(columns: Col[])","description":"Data table — column-oriented. Each Col holds its own data array.","tier":"core","pack":"core","examples":[]},{"name":"Col","group":"Data","signature":"Col(label: string, data: any, type?: \"string\" | \"number\" | \"action\")","description":"Column definition — holds label + data array","tier":"core","pack":"core","examples":[]},{"name":"EditableTable","group":"Data","signature":"EditableTable(name?: string, columns?: {type: \"text\" | \"number\" | \"date-single\" | \"select\" | \"url\", key?: string, header?: string, width?: number, options?: {value: string, label: string}[]}[], data?: {id: string, values: (string | number)[]}[])","description":"Spreadsheet-like table whose cells the user can edit inline (text, number, url, date, select columns); edits are saved back as a form field","tier":"extended","pack":"core","examples":[]},{"name":"BarChart","group":"Charts","signature":"BarChart(labels: string[], series: Series[], variant?: \"grouped\" | \"stacked\", xLabel?: string, yLabel?: string, height?: number)","description":"Vertical bars; use for comparing values across categories with one or more series","tier":"core","pack":"core","examples":[]},{"name":"LineChart","group":"Charts","signature":"LineChart(labels: string[], series: Series[], variant?: \"linear\" | \"natural\" | \"step\", xLabel?: string, yLabel?: string, height?: number)","description":"Lines over categories; use for trends and continuous data over time","tier":"core","pack":"core","examples":[]},{"name":"AreaChart","group":"Charts","signature":"AreaChart(labels: string[], series: Series[], variant?: \"linear\" | \"natural\" | \"step\", xLabel?: string, yLabel?: string, height?: number)","description":"Filled area under lines; use for cumulative totals or volume trends over time","tier":"extended","pack":"core","examples":[]},{"name":"HorizontalBarChart","group":"Charts","signature":"HorizontalBarChart(labels: string[], series: Series[], variant?: \"grouped\" | \"stacked\", xLabel?: string, yLabel?: string)","description":"Horizontal bars; prefer when category labels are long or for ranked lists","tier":"extended","pack":"core","examples":[]},{"name":"Series","group":"Charts","signature":"Series(category: string, values: number[])","description":"One data series","tier":"core","pack":"core","examples":[]},{"name":"PieChart","group":"Charts","signature":"PieChart(labels: string[], values: number[], variant?: \"pie\" | \"donut\", appearance?: \"circular\" | \"semiCircular\")","description":"Circular slices; use plucked arrays: PieChart(data.categories, data.values)","tier":"core","pack":"core","examples":[]},{"name":"ScatterChart","group":"Charts","signature":"ScatterChart(datasets: ScatterSeries[], xLabel?: string, yLabel?: string)","description":"X/Y scatter plot; use for correlations, distributions, and clustering","tier":"extended","pack":"core","examples":[]},{"name":"ScatterSeries","group":"Charts","signature":"ScatterSeries(name: string, points: Point[])","description":"Named dataset","tier":"extended","pack":"core","examples":[]},{"name":"Point","group":"Charts","signature":"Point(x: number, y: number, z?: number)","description":"Data point with numeric coordinates","tier":"extended","pack":"core","examples":[]},{"name":"Form","group":"Forms","signature":"Form(name: string, buttons: Buttons, fields?: FormControl[])","description":"Form container with fields and explicit action buttons","tier":"core","pack":"core","examples":[]},{"name":"FormControl","group":"Forms","signature":"FormControl(label: string, input: Input | TextArea | Select | Slider | CheckBoxGroup | RadioGroup | Chips | OptionCards, hint?: string)","description":"Field with label, input component, and optional hint text","tier":"core","pack":"core","examples":[]},{"name":"Label","group":"Forms","signature":"Label(text: string)","description":"Text label","tier":"extended","pack":"core","examples":[]},{"name":"Input","group":"Forms","signature":"Input(name: string, placeholder?: string, type?: \"text\" | \"email\" | \"password\" | \"number\" | \"url\", rules?: {required?: boolean, email?: boolean, url?: boolean, numeric?: boolean, min?: number, max?: number, minLength?: number, maxLength?: number, pattern?: string}, value?: $binding<string>)","description":"","tier":"core","pack":"core","examples":[]},{"name":"TextArea","group":"Forms","signature":"TextArea(name: string, placeholder?: string, rows?: number, rules?: {required?: boolean, email?: boolean, url?: boolean, numeric?: boolean, min?: number, max?: number, minLength?: number, maxLength?: number, pattern?: string}, value?: $binding<string>)","description":"","tier":"core","pack":"core","examples":[]},{"name":"Select","group":"Forms","signature":"Select(name: string, items: SelectItem[], placeholder?: string, rules?: {required?: boolean, email?: boolean, url?: boolean, numeric?: boolean, min?: number, max?: number, minLength?: number, maxLength?: number, pattern?: string}, value?: $binding<string>, size?: \"small\" | \"medium\" | \"large\")","description":"","tier":"core","pack":"core","examples":[]},{"name":"SelectItem","group":"Forms","signature":"SelectItem(value: string, label: string)","description":"Option for Select","tier":"core","pack":"core","examples":[]},{"name":"Slider","group":"Forms","signature":"Slider(name: string, variant: \"continuous\" | \"discrete\", min: number, max: number, step?: number, defaultValue?: number[], label?: string, rules?: {required?: boolean, email?: boolean, url?: boolean, numeric?: boolean, min?: number, max?: number, minLength?: number, maxLength?: number, pattern?: string}, value?: $binding<number[]>)","description":"Numeric slider input; supports continuous and discrete (stepped) variants","tier":"extended","pack":"core","examples":[]},{"name":"CheckBoxGroup","group":"Forms","signature":"CheckBoxGroup(name: string, items: CheckBoxItem[], rules?: {required?: boolean, email?: boolean, url?: boolean, numeric?: boolean, min?: number, max?: number, minLength?: number, maxLength?: number, pattern?: string}, value?: $binding<Record<string, boolean>>)","description":"","tier":"extended","pack":"core","examples":[]},{"name":"CheckBoxItem","group":"Forms","signature":"CheckBoxItem(label: string, description: string, name: string, defaultChecked?: boolean)","description":"","tier":"extended","pack":"core","examples":[]},{"name":"RadioGroup","group":"Forms","signature":"RadioGroup(name: string, items: RadioItem[], defaultValue?: string, rules?: {required?: boolean, email?: boolean, url?: boolean, numeric?: boolean, min?: number, max?: number, minLength?: number, maxLength?: number, pattern?: string}, value?: $binding<string>)","description":"","tier":"core","pack":"core","examples":[]},{"name":"RadioItem","group":"Forms","signature":"RadioItem(label: string, description: string, value: string)","description":"","tier":"core","pack":"core","examples":[]},{"name":"SwitchGroup","group":"Forms","signature":"SwitchGroup(name: string, items: SwitchItem[], variant?: \"clear\" | \"card\" | \"sunk\", value?: $binding<Record<string, boolean>>)","description":"Group of switch toggles","tier":"extended","pack":"core","examples":[]},{"name":"SwitchItem","group":"Forms","signature":"SwitchItem(label?: string, description?: string, name: string, defaultChecked?: boolean)","description":"Individual switch toggle","tier":"extended","pack":"core","examples":[]},{"name":"ChipItem","group":"Forms","signature":"ChipItem(value: string, label: string, icon?: Icon, disabled?: boolean)","description":"A single selectable chip inside a Chips group, with a value, label and optional icon.","tier":"extended","pack":"core","examples":[]},{"name":"Chips","group":"Forms","signature":"Chips(name: string, type?: \"single\" | \"multiple\", items?: ChipItem[], rules?: {required?: boolean, email?: boolean, url?: boolean, numeric?: boolean, min?: number, max?: number, minLength?: number, maxLength?: number, pattern?: string}, defaultValue?: string | string[])","description":"A form field of compact selectable chips for choosing one or many short options; the selection is stored under `name`.","tier":"extended","pack":"core","examples":[]},{"name":"OptionCard","group":"Forms","signature":"OptionCard(value: string, title: string, subtitle?: string, topContent?: Icon | Image, disabled?: boolean)","description":"A single selectable card inside an OptionCards group, with a value, title, optional subtitle and an optional Icon or Image on top.","tier":"core","pack":"core","examples":[]},{"name":"OptionCards","group":"Forms","signature":"OptionCards(name: string, type?: \"single\" | \"multiple\", items?: OptionCard[], rules?: {required?: boolean, email?: boolean, url?: boolean, numeric?: boolean, min?: number, max?: number, minLength?: number, maxLength?: number, pattern?: string}, defaultValue?: string | string[])","description":"A form field of selectable cards (title, optional subtitle, optional icon or image) laid out in a responsive grid for choosing one or many options; the selection is stored under `name`.","tier":"core","pack":"core","examples":[]},{"name":"Button","group":"Buttons","signature":"Button(label: string, action?: ActionExpression, variant?: \"primary\" | \"secondary\" | \"tertiary\", type?: \"normal\" | \"destructive\", size?: \"extra-small\" | \"small\" | \"medium\" | \"large\")","description":"Clickable button","tier":"core","pack":"core","examples":[]},{"name":"Buttons","group":"Buttons","signature":"Buttons(buttons: Button[], direction?: \"row\" | \"column\")","description":"Group of Button components. direction: \"row\" (default) | \"column\".","tier":"core","pack":"core","examples":[]},{"name":"Stack","group":"Layout","signature":"Stack(children: any[], direction?: \"row\" | \"column\", gap?: \"none\" | \"xs\" | \"s\" | \"m\" | \"l\" | \"xl\" | \"2xl\", align?: \"start\" | \"center\" | \"end\" | \"stretch\" | \"baseline\", justify?: \"start\" | \"center\" | \"end\" | \"between\" | \"around\" | \"evenly\", wrap?: boolean)","description":"Flex container. direction: \"row\"|\"column\" (default \"column\"). gap: \"none\"|\"xs\"|\"s\"|\"m\"|\"l\"|\"xl\"|\"2xl\" (default \"m\"). align: \"start\"|\"center\"|\"end\"|\"stretch\"|\"baseline\". justify: \"start\"|\"center\"|\"end\"|\"between\"|\"around\"|\"evenly\".","tier":"core","pack":"core","examples":[]},{"name":"Tabs","group":"Layout","signature":"Tabs(items: TabItem[])","description":"Tabbed container","tier":"core","pack":"core","examples":[]},{"name":"TabItem","group":"Layout","signature":"TabItem(value: string, trigger: string, content: (TextContent | MarkDownRenderer | CardHeader | Callout | CodeBlock | Image | Separator | HorizontalBarChart | PieChart | ScatterChart | AreaChart | BarChart | LineChart | Table | TagBlock | Form | Buttons | Steps | InlineHeader | EntityList | EditableTable | OverviewCardBlock | Architecture | Diff | FileTree | Mermaid | Progress | Avatar | AvatarGroup | Tooltip | HoverCard | Breadcrumb | Kbd | Skeleton | Calendar | Timeline)[])","description":"value is unique id, trigger is tab label, content is array of components","tier":"core","pack":"core","examples":[]},{"name":"Accordion","group":"Layout","signature":"Accordion(items: AccordionItem[])","description":"Collapsible sections","tier":"core","pack":"core","examples":[]},{"name":"AccordionItem","group":"Layout","signature":"AccordionItem(value: string, trigger: string, content: (TextContent | MarkDownRenderer | CardHeader | Callout | CodeBlock | Image | Separator | HorizontalBarChart | PieChart | ScatterChart | AreaChart | BarChart | LineChart | Table | TagBlock | Form | Buttons | Steps | InlineHeader | EntityList | EditableTable | OverviewCardBlock | Architecture | Diff | FileTree | Mermaid | Progress | Avatar | AvatarGroup | Tooltip | HoverCard | Breadcrumb | Kbd | Skeleton | Calendar | Timeline)[])","description":"value is unique id, trigger is section title","tier":"core","pack":"core","examples":[]},{"name":"Steps","group":"Layout","signature":"Steps(items: StepsItem[])","description":"Step-by-step guide","tier":"core","pack":"core","examples":[]},{"name":"StepsItem","group":"Layout","signature":"StepsItem(title: string, details: string)","description":"title and details text for one step","tier":"core","pack":"core","examples":[]},{"name":"Separator","group":"Layout","signature":"Separator(orientation?: \"horizontal\" | \"vertical\", decorative?: boolean)","description":"Visual divider between content sections","tier":"core","pack":"core","examples":[]},{"name":"TagBlock","group":"Data","signature":"TagBlock(tags: string[], size?: \"sm\" | \"md\" | \"lg\")","description":"tags is an array of strings; optional size sm | md | lg","tier":"extended","pack":"core","examples":[]},{"name":"Tag","group":"Data","signature":"Tag(text: string, icon?: Icon, size?: \"sm\" | \"md\" | \"lg\", variant?: \"neutral\" | \"info\" | \"success\" | \"warning\" | \"danger\")","description":"Styled tag/badge with optional Icon and variant","tier":"core","pack":"core","examples":[]},{"name":"Icon","group":"Data","signature":"Icon(name: string, category?: string)","description":"A lucide icon by kebab-case name (e.g. 'circle-check'). Optional category picks a topical fallback when the name doesn't resolve.","tier":"core","pack":"core","examples":[]},{"name":"EntityList","group":"Data","signature":"EntityList(rows?: {left: string, right: string, rightVariant?: \"text\" | \"number\"}[], size?: \"small\" | \"default\", header?: {left: string, right: string, rightVariant?: \"text\" | \"number\"}, footer?: {left: string, right: string, rightVariant?: \"text\" | \"number\"})","description":"Two-column key/value rows (left label, right value). size 'default' supports optional header and footer rows; rightVariant 'number' uses tabular numbers.","tier":"core","pack":"core","examples":[]},{"name":"Text","group":"Data","signature":"Text(variant?: \"text\" | \"number\", value: string, subtext?: string, subtextVariant?: \"text\" | \"number\" | \"metric\", size?: \"xs\" | \"sm\" | \"md\" | \"lg\")","description":"Plain text line with optional subtext. variant 'number' uses tabular number styling; subtextVariant 'metric' colors a leading +/- subtext green/red.","tier":"core","pack":"core","examples":[]},{"name":"BoldText","group":"Data","signature":"BoldText(variant?: \"text\" | \"number\", value: string, subtext?: string, subtextVariant?: \"text\" | \"number\" | \"metric\", size?: \"xs\" | \"sm\" | \"md\" | \"lg\")","description":"Emphasized (bold) text line with optional subtext. variant 'number' uses tabular number styling; subtextVariant 'metric' colors a leading +/- subtext green/red.","tier":"extended","pack":"core","examples":[]},{"name":"IconText","group":"Data","signature":"IconText(icon: Icon, iconVariant?: \"neutral\" | \"info\" | \"success\" | \"warning\" | \"danger\" | \"inverted\" | \"filled\" | \"soft\", iconSize?: \"xs\" | \"s\" | \"m\" | \"l\" | \"xl\" | \"sm\" | \"md\" | \"lg\", title: string, subtitle?: string, bold?: boolean, layout?: \"horizontal\" | \"vertical\")","description":"An icon badge with a title and optional subtitle, laid out horizontally or vertically. iconVariant sets the badge color.","tier":"extended","pack":"core","examples":[]},{"name":"MetricIndicatorInline","group":"Data","signature":"MetricIndicatorInline(value: string, subtext?: string, trend?: {direction: \"up\" | \"down\", value: number})","description":"Headline metric value with an optional +/- percentage trend and subtext, all on one line.","tier":"core","pack":"core","examples":[]},{"name":"OverviewCardItem","group":"Data","signature":"OverviewCardItem(id?: string, top: IconText | Text, bottom?: MetricIndicatorInline)","description":"One overview card: a heading slot at the top (IconText, ImageText or Text) and an optional MetricIndicatorInline at the bottom.","tier":"core","pack":"core","examples":[]},{"name":"OverviewCardBlock","group":"Data","signature":"OverviewCardBlock(items: OverviewCardItem[], layout?: \"grid\" | \"carousel\", responsive?: boolean, action?: ActionExpression, gap?: number | string)","description":"A grid or horizontal carousel of compact overview cards, each with a heading (icon/image/text) on top and an inline metric below; optionally clickable with a shared action.","tier":"core","pack":"core","examples":[]},{"name":"Architecture","group":"Content","signature":"Architecture(nodes: Node[], edges: Edge[], groups?: Group[], direction?: \"auto\" | \"right\" | \"down\" | \"lanes\")","description":"System diagram laid out automatically: write no coordinates. Every Node, Edge and Group is its own statement and click target. direction auto (default) runs right when that fits the page legibly, else down; lanes makes each top-level Group a column.","tier":"core","pack":"core","examples":["platformMap = Architecture([browser, edge, api, sessions, cache], [toEdge, toApi, readSessions, readCache], [vpc])\nbrowser = Node(\"Browser\", \"external\")\nedge = Node(\"Edge worker\", \"cloud\", \"Cloudflare\")\napi = Node(\"API\", \"backend\", \"Node :8080\")\nsessions = Node(\"Sessions\", \"database\", \"Postgres\", \"new\")\ncache = Node(\"Redis\", \"database\", \"read-through\")\ntoEdge = Edge(browser, edge, \"HTTPS\", \"emphasis\")\ntoApi = Edge(edge, api)\nreadSessions = Edge(api, sessions, \"SQL\")\nreadCache = Edge(api, cache, \"cache\", \"dashed\")\nvpc = Group(\"Platform VPC\", [api, sessions, cache])","ownership = Architecture([web, ordersApi, ledger, ordersDb, billingJob], [placeOrder, writeOrder, readOrders, postLedger], [clients, ordersRepo, billingRepo], \"lanes\")\nweb = Node(\"Web app\", \"frontend\")\nordersApi = Node(\"Orders API\", \"backend\", \"Node :8080\")\nordersDb = Node(\"orders table\", \"database\", \"Postgres\", null, \"success\")\nbillingJob = Node(\"Nightly billing\", \"backend\", \"cron 02:00\")\nledger = Node(\"Ledger\", \"database\", \"append-only\", null, \"info\")\nplaceOrder = Edge(web, ordersApi, \"POST /orders\")\nwriteOrder = Edge(ordersApi, ordersDb, \"insert\")\nreadOrders = Edge(billingJob, ordersDb, \"read unbilled\", null, \"danger\")\npostLedger = Edge(billingJob, ledger, \"post\")\nclients = Group(\"CLIENTS\", [web], \"plain\")\nordersRepo = Group(\"orders-service\", [ordersApi, ordersDb])\nbillingRepo = Group(\"billing\", [billingJob, ledger])"]},{"name":"Node","group":"Content","signature":"Node(label: string, kind?: \"frontend\" | \"backend\" | \"database\" | \"messagebus\" | \"cloud\" | \"security\" | \"external\", detail?: string, tag?: string, tone?: \"info\" | \"success\" | \"warning\" | \"danger\" | \"strong\", row?: number)","description":"A box in an Architecture. kind sets icon and color (default backend); detail is monospace, up to 2 lines (\\n breaks); tag is a small chip such as \"new\"; tone tints it (danger = risk, strong = where it ends); row pins it to a row (1, 2, ...) in lanes.","tier":"core","pack":"core","examples":[]},{"name":"Edge","group":"Content","signature":"Edge(from: Node, to: Node, label?: string, style?: \"solid\" | \"dashed\" | \"emphasis\" | \"security\", tone?: \"info\" | \"success\" | \"warning\" | \"danger\")","description":"An arrow between two Node statements (pass their names, not strings). style: dashed for async, emphasis for the main path, security for auth or trust flows. tone colors it, e.g. danger for a risky write, success for a read.","tier":"core","pack":"core","examples":[]},{"name":"Group","group":"Content","signature":"Group(label: string, members: Node[], kind?: \"region\" | \"security\" | \"plain\")","description":"A boundary around Node statements (a VPC, a cluster, a trust zone); members still sit where their edges put them. A group whose members are a subset of another's is drawn inside it; kind security draws it red, plain draws only its name.","tier":"core","pack":"core","examples":[]},{"name":"Diff","group":"Content","signature":"Diff(patch: string, title?: string)","description":"Unified diff (git diff output, one or more files) with line numbers. patch is a raw multi-line string. Clicked lines reach the agent as file:+line.","tier":"core","pack":"core","examples":["retryPatch = Diff(\"diff --git a/src/retry.ts b/src/retry.ts\n--- a/src/retry.ts\n+++ b/src/retry.ts\n@@ -10,2 +10,3 @@\n- for (let i = 0; i < 3; i++) {\n+ for (let i = 0; i < MAX_ATTEMPTS; i++) {\n+ if (i > 0) await sleep(backoff(i));\")"]},{"name":"FileTree","group":"Data","signature":"FileTree(entries: string[])","description":"File tree, one path per entry, optionally prefixed with a git status code (A new, M modified, D deleted, R old -> new) and suffixed with \" # note\". Folders are inferred. Clicked files and folders reach the agent by path.","tier":"core","pack":"core","examples":["changedFiles = FileTree([\"M src/session/store.ts # read-through to Postgres\", \"A src/session/pg-store.ts\", \"A migrations/0042_sessions.sql # table + expiry index\", \"R src/session/redis.ts -> src/session/redis-cache.ts\", \"D src/session/legacy.ts\", \"M src/config.ts # SESSIONS_PG flag\"])"]},{"name":"Mermaid","group":"Content","signature":"Mermaid(source: string, caption?: string)","description":"Diagram from Mermaid source (flowchart, sequence, state, class, ER, gantt). source is a raw multi-line string. Keep node labels to a few words; clicked nodes reach the agent by label.","tier":"core","pack":"@austere-axi/pack-mermaid","examples":["requestPath = Mermaid(\"flowchart LR\n client[Client] --> api[API]\n api --> pg[(Postgres)]\", \"Request path\")"]},{"name":"Progress","group":"Data","signature":"Progress(value: number, label?: string, max?: number)","description":"Progress bar. value counts toward max (default 100); label names what is progressing. Shows a percentage.","tier":"extended","pack":"@austere-axi/pack-shadcn","examples":["migration = Progress(64, \"Rows migrated\")"]},{"name":"Avatar","group":"Data","signature":"Avatar(name: string, src?: string, size?: \"md\" | \"lg\")","description":"Round avatar for a person. Shows the initials of name; src is an optional https image URL.","tier":"extended","pack":"@austere-axi/pack-shadcn","examples":["owner = Avatar(\"Ada Lovelace\")"]},{"name":"AvatarGroup","group":"Data","signature":"AvatarGroup(avatars: Avatar[], max?: number)","description":"Overlapping Avatars, for reviewers or owners. max (default 4) shows the rest as +N.","tier":"extended","pack":"@austere-axi/pack-shadcn","examples":["team = AvatarGroup([ada, grace, linus], 2)\nada = Avatar(\"Ada Lovelace\")\ngrace = Avatar(\"Grace Hopper\")\nlinus = Avatar(\"Linus Torvalds\")"]},{"name":"Tooltip","group":"Content","signature":"Tooltip(text: string, tip: string)","description":"Inline text with a short hint on hover or focus (a term and its definition). Keep tip to one line.","tier":"extended","pack":"@austere-axi/pack-shadcn","examples":["term = Tooltip(\"idempotent\", \"Safe to run twice with the same result.\")"]},{"name":"HoverCard","group":"Content","signature":"HoverCard(text: string, title: string, body?: string)","description":"Inline text that shows a small card (title and a line or two of body) on hover or focus, e.g. a person or a service.","tier":"extended","pack":"@austere-axi/pack-shadcn","examples":["svc = HoverCard(\"billing-api\", \"billing-api\", \"Owns invoices and refunds. On call: payments team.\")"]},{"name":"Breadcrumb","group":"Content","signature":"Breadcrumb(items: BreadcrumbItem[])","description":"Path trail such as Repo / Package / File, from BreadcrumbItems. Each item is a click target.","tier":"extended","pack":"@austere-axi/pack-shadcn","examples":["trail = Breadcrumb([repo, pkg, file])\nrepo = BreadcrumbItem(\"austere-axi\")\npkg = BreadcrumbItem(\"packs\")\nfile = BreadcrumbItem(\"shadcn\")"]},{"name":"BreadcrumbItem","group":"Content","signature":"BreadcrumbItem(label: string, href?: string)","description":"One step of a Breadcrumb. href is optional; the last item is the current page.","tier":"extended","pack":"@austere-axi/pack-shadcn","examples":[]},{"name":"Kbd","group":"Content","signature":"Kbd(keys: string)","description":"Keyboard shortcut. keys joins keys with \"+\", e.g. \"Ctrl+K\"; each key is drawn as a key cap.","tier":"extended","pack":"@austere-axi/pack-shadcn","examples":["shortcut = Kbd(\"Ctrl+Shift+P\")"]},{"name":"Skeleton","group":"Content","signature":"Skeleton(lines?: number, shape?: \"lines\" | \"avatar\")","description":"Grey placeholder block for content not yet written or loaded (default 3 lines). shape avatar adds a round head.","tier":"extended","pack":"@austere-axi/pack-shadcn","examples":["pending = Skeleton(3, \"avatar\")"]},{"name":"Calendar","group":"Data","signature":"Calendar(month: string, highlights?: string[])","description":"Read-only month grid (not a date picker). month is \"YYYY-MM\"; highlights lists \"YYYY-MM-DD\" or \"YYYY-MM-DD label\" entries to mark, e.g. a freeze or a release. Marked days are click targets.","tier":"extended","pack":"@austere-axi/pack-shadcn","examples":["window = Calendar(\"2026-11\", [\"2026-11-04 Code freeze\", \"2026-11-12 Release\", \"2026-11-13\"])"]},{"name":"Timeline","group":"Content","signature":"Timeline(items: TimelineItem[])","description":"Vertical history or schedule of TimelineItems, oldest first. Each item is a click target.","tier":"extended","pack":"@austere-axi/pack-shadcn","examples":["history = Timeline([found, fixed, rollout])\nfound = TimelineItem(\"Bug found\", \"Oct 28\", \"Retries double-charge on timeout.\")\nfixed = TimelineItem(\"Fix merged\", \"Oct 29\", \"Idempotency key added.\", \"current\")\nrollout = TimelineItem(\"Rollout\", \"Nov 3\", \"\", \"upcoming\")"]},{"name":"TimelineItem","group":"Content","signature":"TimelineItem(title: string, date?: string, detail?: string, state?: \"done\" | \"current\" | \"upcoming\")","description":"One event in a Timeline. state: done (default), current or upcoming.","tier":"extended","pack":"@austere-axi/pack-shadcn","examples":[]}]}
1
+ {"format":1,"pack":"core","version":"0.4.1","groups":[{"name":"Layout","notes":["- A page is root = Stack([...sections], \"column\", \"l\"). Each section is a Card([CardHeader(title, subtitle), ...content]).","- Side by side: Stack([a, b], \"row\", \"m\", \"stretch\", \"start\", true). Cards in a row share the width.","- Tabs for alternative views, Accordion for detail the reader may skip, Steps for ordered procedures. Several blocks in one Card: label each with InlineHeader.","- Card children must match its list; anything else (Accordion, Tag, a Callout row) can sit in a Stack inside the Card, or directly in root's Stack."]},{"name":"Content","notes":["- MarkDownRenderer for prose, bullet lists and inline code; one statement per paragraph group so each is annotatable.","- Callout for the one thing the reader must not miss: a risk, a decision needed, a caveat.","- CodeBlock for a whole snippet. Image src may be a path relative to the artifact file.","- Architecture for systems: services, stores, queues, trust boundaries. Name every Node, Edge and Group. Over ~15 nodes, use lanes or split the diagram.","- lanes: for systems split by owner, repo or tier. Groups are the columns, left to right as listed; put every node in one, in reading order (plain groups for a heading without a frame). About 4 columns fit a laptop; wider ones scroll (check warns). Phones get the lanes stacked as bands. Pin row only to put boxes side by side or line them up.","- Diff for code changes: show the hunks that matter, not whole files. Hunk counts are recomputed, so only start lines matter.","- Mermaid for sequences, state machines, ER, gantt and quick flowcharts."]},{"name":"Data","notes":["- Table is COLUMN-oriented: Table([Col(\"Option\", names), Col(\"Effort\", efforts), Col(\"Risk\", risks)]). Col data may mix strings and components, e.g. [\"n/a\", Tag(\"high\", null, \"sm\", \"danger\")]. Cells are plain text (no markdown) and wrap; Col type \"number\" for figures.","- EntityList for key/value facts; OverviewCardBlock + OverviewCardItem + MetricIndicatorInline for a KPI row.","- EditableTable when the reviewer should correct or fill in values; edits arrive with the next @ToAssistant from its Form.","- Icon names are lucide kebab-case (circle-check, triangle-alert, git-branch)."]},{"name":"Charts","notes":["- Charts take column arrays: BarChart(labels, [Series(\"p50\", [..]), Series(\"p99\", [..])], \"grouped\", \"Week\", \"ms\").","- PieChart(labels, values) with plain number arrays."]},{"name":"Forms","notes":["- Form(name, buttons, fields). Define each FormControl as its own statement. Never nest a Form in a Form.","- To ask the reviewer to decide: OptionCards or RadioGroup inside a Form whose button is Button(\"Send decision\", @ToAssistant(\"Decision: rollout strategy\"), \"primary\"). The values arrive with that message as form: {<formName>: {<fieldName>: value}}; no context needed. Reviewers can always annotate and message, so add a notes field only when it belongs with the decision.","- OptionCards(\"pick\", \"single\", [OptionCard(\"a\", \"Option A\", \"why\", Icon(\"zap\")), OptionCard(\"b\", \"Option B\")], {required: true}, \"a\") — the last argument pre-selects.","- rules: {required: true, minLength: 10, ...}; the renderer shows the messages."]},{"name":"Buttons","notes":["- @ToAssistant(message, context?) queues a message to the agent; @OpenUrl(url) opens a link. A Button with no action sends its label.","- Use buttons for follow-ups the reviewer can pick in one click: \"Go with option B\", \"Split this into two PRs\"."]}],"components":[{"name":"Card","group":"Layout","signature":"Card(children: (TextContent | MarkDownRenderer | CardHeader | Callout | CodeBlock | Image | Separator | HorizontalBarChart | PieChart | ScatterChart | AreaChart | BarChart | LineChart | Table | TagBlock | Form | Buttons | Steps | InlineHeader | EntityList | EditableTable | OverviewCardBlock | Tabs | Stack | Architecture | Diff | FileTree | Mermaid | Progress | Avatar | AvatarGroup | Tooltip | HoverCard | Breadcrumb | Kbd | Skeleton | Calendar | Timeline)[], variant?: \"card\" | \"sunk\" | \"clear\", direction?: \"row\" | \"column\", gap?: \"none\" | \"xs\" | \"s\" | \"m\" | \"l\" | \"xl\" | \"2xl\", align?: \"start\" | \"center\" | \"end\" | \"stretch\" | \"baseline\", justify?: \"start\" | \"center\" | \"end\" | \"between\" | \"around\" | \"evenly\", wrap?: boolean)","description":"Styled container. variant: \"card\" (default, elevated) | \"sunk\" (recessed) | \"clear\" (transparent). Always full width. Accepts all Stack flex params (default: direction \"column\"). Cards flex to share space in row/wrap layouts.","tier":"core","pack":"core","examples":[]},{"name":"CardHeader","group":"Layout","signature":"CardHeader(title?: string, subtitle?: string)","description":"Header with optional title and subtitle","tier":"core","pack":"core","examples":[]},{"name":"TextContent","group":"Content","signature":"TextContent(text: string, size?: \"small\" | \"default\" | \"large\" | \"small-heavy\" | \"large-heavy\")","description":"Text block. Supports markdown. Optional size: \"small\" | \"default\" | \"large\" | \"small-heavy\" | \"large-heavy\".","tier":"core","pack":"core","examples":[]},{"name":"MarkDownRenderer","group":"Content","signature":"MarkDownRenderer(textMarkdown: string, variant?: \"clear\" | \"card\" | \"sunk\")","description":"Renders markdown text with optional container variant","tier":"core","pack":"core","examples":[]},{"name":"Callout","group":"Content","signature":"Callout(variant: \"info\" | \"warning\" | \"error\" | \"success\" | \"neutral\", title: string, description: string, visible?: $binding<boolean>)","description":"Callout banner. Optional visible is a reactive $boolean — auto-dismisses after 3s by setting $visible to false.","tier":"core","pack":"core","examples":[]},{"name":"Image","group":"Content","signature":"Image(alt: string, src?: string)","description":"Image with alt text and optional URL","tier":"core","pack":"core","examples":[]},{"name":"CodeBlock","group":"Content","signature":"CodeBlock(language: string, codeString: string)","description":"Syntax-highlighted code block","tier":"core","pack":"core","examples":[]},{"name":"InlineHeader","group":"Layout","signature":"InlineHeader(heading: string, description?: string)","description":"Compact section heading with an optional one-line description, for use inside cards.","tier":"core","pack":"core","examples":[]},{"name":"Table","group":"Data","signature":"Table(columns: Col[])","description":"Data table — column-oriented. Each Col holds its own data array.","tier":"core","pack":"core","examples":[]},{"name":"Col","group":"Data","signature":"Col(label: string, data: any, type?: \"string\" | \"number\" | \"action\")","description":"Column definition — holds label + data array","tier":"core","pack":"core","examples":[]},{"name":"EditableTable","group":"Data","signature":"EditableTable(name?: string, columns?: {type: \"text\" | \"number\" | \"date-single\" | \"select\" | \"url\", key?: string, header?: string, width?: number, options?: {value: string, label: string}[]}[], data?: {id: string, values: (string | number)[]}[])","description":"Spreadsheet-like table whose cells the user can edit inline (text, number, url, date, select columns); edits are saved back as a form field","tier":"extended","pack":"core","examples":[]},{"name":"BarChart","group":"Charts","signature":"BarChart(labels: string[], series: Series[], variant?: \"grouped\" | \"stacked\", xLabel?: string, yLabel?: string, height?: number)","description":"Vertical bars; use for comparing values across categories with one or more series","tier":"core","pack":"core","examples":[]},{"name":"LineChart","group":"Charts","signature":"LineChart(labels: string[], series: Series[], variant?: \"linear\" | \"natural\" | \"step\", xLabel?: string, yLabel?: string, height?: number)","description":"Lines over categories; use for trends and continuous data over time","tier":"core","pack":"core","examples":[]},{"name":"AreaChart","group":"Charts","signature":"AreaChart(labels: string[], series: Series[], variant?: \"linear\" | \"natural\" | \"step\", xLabel?: string, yLabel?: string, height?: number)","description":"Filled area under lines; use for cumulative totals or volume trends over time","tier":"extended","pack":"core","examples":[]},{"name":"HorizontalBarChart","group":"Charts","signature":"HorizontalBarChart(labels: string[], series: Series[], variant?: \"grouped\" | \"stacked\", xLabel?: string, yLabel?: string)","description":"Horizontal bars; prefer when category labels are long or for ranked lists","tier":"extended","pack":"core","examples":[]},{"name":"Series","group":"Charts","signature":"Series(category: string, values: number[])","description":"One data series","tier":"core","pack":"core","examples":[]},{"name":"PieChart","group":"Charts","signature":"PieChart(labels: string[], values: number[], variant?: \"pie\" | \"donut\", appearance?: \"circular\" | \"semiCircular\")","description":"Circular slices; use plucked arrays: PieChart(data.categories, data.values)","tier":"core","pack":"core","examples":[]},{"name":"ScatterChart","group":"Charts","signature":"ScatterChart(datasets: ScatterSeries[], xLabel?: string, yLabel?: string)","description":"X/Y scatter plot; use for correlations, distributions, and clustering","tier":"extended","pack":"core","examples":[]},{"name":"ScatterSeries","group":"Charts","signature":"ScatterSeries(name: string, points: Point[])","description":"Named dataset","tier":"extended","pack":"core","examples":[]},{"name":"Point","group":"Charts","signature":"Point(x: number, y: number, z?: number)","description":"Data point with numeric coordinates","tier":"extended","pack":"core","examples":[]},{"name":"Form","group":"Forms","signature":"Form(name: string, buttons: Buttons, fields?: FormControl[])","description":"Form container with fields and explicit action buttons","tier":"core","pack":"core","examples":[]},{"name":"FormControl","group":"Forms","signature":"FormControl(label: string, input: Input | TextArea | Select | Slider | CheckBoxGroup | RadioGroup | Chips | OptionCards, hint?: string)","description":"Field with label, input component, and optional hint text","tier":"core","pack":"core","examples":[]},{"name":"Label","group":"Forms","signature":"Label(text: string)","description":"Text label","tier":"extended","pack":"core","examples":[]},{"name":"Input","group":"Forms","signature":"Input(name: string, placeholder?: string, type?: \"text\" | \"email\" | \"password\" | \"number\" | \"url\", rules?: {required?: boolean, email?: boolean, url?: boolean, numeric?: boolean, min?: number, max?: number, minLength?: number, maxLength?: number, pattern?: string}, value?: $binding<string>)","description":"","tier":"core","pack":"core","examples":[]},{"name":"TextArea","group":"Forms","signature":"TextArea(name: string, placeholder?: string, rows?: number, rules?: {required?: boolean, email?: boolean, url?: boolean, numeric?: boolean, min?: number, max?: number, minLength?: number, maxLength?: number, pattern?: string}, value?: $binding<string>)","description":"","tier":"core","pack":"core","examples":[]},{"name":"Select","group":"Forms","signature":"Select(name: string, items: SelectItem[], placeholder?: string, rules?: {required?: boolean, email?: boolean, url?: boolean, numeric?: boolean, min?: number, max?: number, minLength?: number, maxLength?: number, pattern?: string}, value?: $binding<string>, size?: \"small\" | \"medium\" | \"large\")","description":"","tier":"core","pack":"core","examples":[]},{"name":"SelectItem","group":"Forms","signature":"SelectItem(value: string, label: string)","description":"Option for Select","tier":"core","pack":"core","examples":[]},{"name":"Slider","group":"Forms","signature":"Slider(name: string, variant: \"continuous\" | \"discrete\", min: number, max: number, step?: number, defaultValue?: number[], label?: string, rules?: {required?: boolean, email?: boolean, url?: boolean, numeric?: boolean, min?: number, max?: number, minLength?: number, maxLength?: number, pattern?: string}, value?: $binding<number[]>)","description":"Numeric slider input; supports continuous and discrete (stepped) variants","tier":"extended","pack":"core","examples":[]},{"name":"CheckBoxGroup","group":"Forms","signature":"CheckBoxGroup(name: string, items: CheckBoxItem[], rules?: {required?: boolean, email?: boolean, url?: boolean, numeric?: boolean, min?: number, max?: number, minLength?: number, maxLength?: number, pattern?: string}, value?: $binding<Record<string, boolean>>)","description":"","tier":"extended","pack":"core","examples":[]},{"name":"CheckBoxItem","group":"Forms","signature":"CheckBoxItem(label: string, description: string, name: string, defaultChecked?: boolean)","description":"","tier":"extended","pack":"core","examples":[]},{"name":"RadioGroup","group":"Forms","signature":"RadioGroup(name: string, items: RadioItem[], defaultValue?: string, rules?: {required?: boolean, email?: boolean, url?: boolean, numeric?: boolean, min?: number, max?: number, minLength?: number, maxLength?: number, pattern?: string}, value?: $binding<string>)","description":"","tier":"core","pack":"core","examples":[]},{"name":"RadioItem","group":"Forms","signature":"RadioItem(label: string, description: string, value: string)","description":"","tier":"core","pack":"core","examples":[]},{"name":"SwitchGroup","group":"Forms","signature":"SwitchGroup(name: string, items: SwitchItem[], variant?: \"clear\" | \"card\" | \"sunk\", value?: $binding<Record<string, boolean>>)","description":"Group of switch toggles","tier":"extended","pack":"core","examples":[]},{"name":"SwitchItem","group":"Forms","signature":"SwitchItem(label?: string, description?: string, name: string, defaultChecked?: boolean)","description":"Individual switch toggle","tier":"extended","pack":"core","examples":[]},{"name":"ChipItem","group":"Forms","signature":"ChipItem(value: string, label: string, icon?: Icon, disabled?: boolean)","description":"A single selectable chip inside a Chips group, with a value, label and optional icon.","tier":"extended","pack":"core","examples":[]},{"name":"Chips","group":"Forms","signature":"Chips(name: string, type?: \"single\" | \"multiple\", items?: ChipItem[], rules?: {required?: boolean, email?: boolean, url?: boolean, numeric?: boolean, min?: number, max?: number, minLength?: number, maxLength?: number, pattern?: string}, defaultValue?: string | string[])","description":"A form field of compact selectable chips for choosing one or many short options; the selection is stored under `name`.","tier":"extended","pack":"core","examples":[]},{"name":"OptionCard","group":"Forms","signature":"OptionCard(value: string, title: string, subtitle?: string, topContent?: Icon | Image, disabled?: boolean)","description":"A single selectable card inside an OptionCards group, with a value, title, optional subtitle and an optional Icon or Image on top.","tier":"core","pack":"core","examples":[]},{"name":"OptionCards","group":"Forms","signature":"OptionCards(name: string, type?: \"single\" | \"multiple\", items?: OptionCard[], rules?: {required?: boolean, email?: boolean, url?: boolean, numeric?: boolean, min?: number, max?: number, minLength?: number, maxLength?: number, pattern?: string}, defaultValue?: string | string[])","description":"A form field of selectable cards (title, optional subtitle, optional icon or image) laid out in a responsive grid for choosing one or many options; the selection is stored under `name`.","tier":"core","pack":"core","examples":[]},{"name":"Button","group":"Buttons","signature":"Button(label: string, action?: ActionExpression, variant?: \"primary\" | \"secondary\" | \"tertiary\", type?: \"normal\" | \"destructive\", size?: \"extra-small\" | \"small\" | \"medium\" | \"large\")","description":"Clickable button","tier":"core","pack":"core","examples":[]},{"name":"Buttons","group":"Buttons","signature":"Buttons(buttons: Button[], direction?: \"row\" | \"column\")","description":"Group of Button components. direction: \"row\" (default) | \"column\".","tier":"core","pack":"core","examples":[]},{"name":"Stack","group":"Layout","signature":"Stack(children: any[], direction?: \"row\" | \"column\", gap?: \"none\" | \"xs\" | \"s\" | \"m\" | \"l\" | \"xl\" | \"2xl\", align?: \"start\" | \"center\" | \"end\" | \"stretch\" | \"baseline\", justify?: \"start\" | \"center\" | \"end\" | \"between\" | \"around\" | \"evenly\", wrap?: boolean)","description":"Flex container. direction: \"row\"|\"column\" (default \"column\"). gap: \"none\"|\"xs\"|\"s\"|\"m\"|\"l\"|\"xl\"|\"2xl\" (default \"m\"). align: \"start\"|\"center\"|\"end\"|\"stretch\"|\"baseline\". justify: \"start\"|\"center\"|\"end\"|\"between\"|\"around\"|\"evenly\".","tier":"core","pack":"core","examples":[]},{"name":"Tabs","group":"Layout","signature":"Tabs(items: TabItem[])","description":"Tabbed container","tier":"core","pack":"core","examples":[]},{"name":"TabItem","group":"Layout","signature":"TabItem(value: string, trigger: string, content: (TextContent | MarkDownRenderer | CardHeader | Callout | CodeBlock | Image | Separator | HorizontalBarChart | PieChart | ScatterChart | AreaChart | BarChart | LineChart | Table | TagBlock | Form | Buttons | Steps | InlineHeader | EntityList | EditableTable | OverviewCardBlock | Architecture | Diff | FileTree | Mermaid | Progress | Avatar | AvatarGroup | Tooltip | HoverCard | Breadcrumb | Kbd | Skeleton | Calendar | Timeline)[])","description":"value is unique id, trigger is tab label, content is array of components","tier":"core","pack":"core","examples":[]},{"name":"Accordion","group":"Layout","signature":"Accordion(items: AccordionItem[])","description":"Collapsible sections","tier":"core","pack":"core","examples":[]},{"name":"AccordionItem","group":"Layout","signature":"AccordionItem(value: string, trigger: string, content: (TextContent | MarkDownRenderer | CardHeader | Callout | CodeBlock | Image | Separator | HorizontalBarChart | PieChart | ScatterChart | AreaChart | BarChart | LineChart | Table | TagBlock | Form | Buttons | Steps | InlineHeader | EntityList | EditableTable | OverviewCardBlock | Architecture | Diff | FileTree | Mermaid | Progress | Avatar | AvatarGroup | Tooltip | HoverCard | Breadcrumb | Kbd | Skeleton | Calendar | Timeline)[])","description":"value is unique id, trigger is section title","tier":"core","pack":"core","examples":[]},{"name":"Steps","group":"Layout","signature":"Steps(items: StepsItem[])","description":"Step-by-step guide","tier":"core","pack":"core","examples":[]},{"name":"StepsItem","group":"Layout","signature":"StepsItem(title: string, details: string)","description":"title and details text for one step","tier":"core","pack":"core","examples":[]},{"name":"Separator","group":"Layout","signature":"Separator(orientation?: \"horizontal\" | \"vertical\", decorative?: boolean)","description":"Visual divider between content sections","tier":"core","pack":"core","examples":[]},{"name":"TagBlock","group":"Data","signature":"TagBlock(tags: string[], size?: \"sm\" | \"md\" | \"lg\")","description":"tags is an array of strings; optional size sm | md | lg","tier":"extended","pack":"core","examples":[]},{"name":"Tag","group":"Data","signature":"Tag(text: string, icon?: Icon, size?: \"sm\" | \"md\" | \"lg\", variant?: \"neutral\" | \"info\" | \"success\" | \"warning\" | \"danger\")","description":"Styled tag/badge with optional Icon and variant","tier":"core","pack":"core","examples":[]},{"name":"Icon","group":"Data","signature":"Icon(name: string, category?: string)","description":"A lucide icon by kebab-case name (e.g. 'circle-check'). Optional category picks a topical fallback when the name doesn't resolve.","tier":"core","pack":"core","examples":[]},{"name":"EntityList","group":"Data","signature":"EntityList(rows?: {left: string, right: string, rightVariant?: \"text\" | \"number\"}[], size?: \"small\" | \"default\", header?: {left: string, right: string, rightVariant?: \"text\" | \"number\"}, footer?: {left: string, right: string, rightVariant?: \"text\" | \"number\"})","description":"Two-column key/value rows (left label, right value). size 'default' supports optional header and footer rows; rightVariant 'number' uses tabular numbers.","tier":"core","pack":"core","examples":[]},{"name":"Text","group":"Data","signature":"Text(variant?: \"text\" | \"number\", value: string, subtext?: string, subtextVariant?: \"text\" | \"number\" | \"metric\", size?: \"xs\" | \"sm\" | \"md\" | \"lg\")","description":"Plain text line with optional subtext. variant 'number' uses tabular number styling; subtextVariant 'metric' colors a leading +/- subtext green/red.","tier":"core","pack":"core","examples":[]},{"name":"BoldText","group":"Data","signature":"BoldText(variant?: \"text\" | \"number\", value: string, subtext?: string, subtextVariant?: \"text\" | \"number\" | \"metric\", size?: \"xs\" | \"sm\" | \"md\" | \"lg\")","description":"Emphasized (bold) text line with optional subtext. variant 'number' uses tabular number styling; subtextVariant 'metric' colors a leading +/- subtext green/red.","tier":"extended","pack":"core","examples":[]},{"name":"IconText","group":"Data","signature":"IconText(icon: Icon, iconVariant?: \"neutral\" | \"info\" | \"success\" | \"warning\" | \"danger\" | \"inverted\" | \"filled\" | \"soft\", iconSize?: \"xs\" | \"s\" | \"m\" | \"l\" | \"xl\" | \"sm\" | \"md\" | \"lg\", title: string, subtitle?: string, bold?: boolean, layout?: \"horizontal\" | \"vertical\")","description":"An icon badge with a title and optional subtitle, laid out horizontally or vertically. iconVariant sets the badge color.","tier":"extended","pack":"core","examples":[]},{"name":"MetricIndicatorInline","group":"Data","signature":"MetricIndicatorInline(value: string, subtext?: string, trend?: {direction: \"up\" | \"down\", value: number})","description":"Headline metric value with an optional +/- percentage trend and subtext, all on one line.","tier":"core","pack":"core","examples":[]},{"name":"OverviewCardItem","group":"Data","signature":"OverviewCardItem(id?: string, top: IconText | Text, bottom?: MetricIndicatorInline)","description":"One overview card: a heading slot at the top (IconText, ImageText or Text) and an optional MetricIndicatorInline at the bottom.","tier":"core","pack":"core","examples":[]},{"name":"OverviewCardBlock","group":"Data","signature":"OverviewCardBlock(items: OverviewCardItem[], layout?: \"grid\" | \"carousel\", responsive?: boolean, action?: ActionExpression, gap?: number | string)","description":"A grid or horizontal carousel of compact overview cards, each with a heading (icon/image/text) on top and an inline metric below; optionally clickable with a shared action.","tier":"core","pack":"core","examples":[]},{"name":"Architecture","group":"Content","signature":"Architecture(nodes: Node[], edges: Edge[], groups?: Group[], direction?: \"auto\" | \"right\" | \"down\" | \"lanes\")","description":"System diagram laid out automatically: write no coordinates. Every Node, Edge and Group is its own statement and click target. direction auto (default) runs right when that fits the page legibly, else down; lanes makes each top-level Group a column.","tier":"core","pack":"core","examples":["platformMap = Architecture([browser, edge, api, sessions, cache], [toEdge, toApi, readSessions, readCache], [vpc])\nbrowser = Node(\"Browser\", \"external\")\nedge = Node(\"Edge worker\", \"cloud\", \"Cloudflare\")\napi = Node(\"API\", \"backend\", \"Node :8080\")\nsessions = Node(\"Sessions\", \"database\", \"Postgres\", \"new\")\ncache = Node(\"Redis\", \"database\", \"read-through\")\ntoEdge = Edge(browser, edge, \"HTTPS\", \"emphasis\")\ntoApi = Edge(edge, api)\nreadSessions = Edge(api, sessions, \"SQL\")\nreadCache = Edge(api, cache, \"cache\", \"dashed\")\nvpc = Group(\"Platform VPC\", [api, sessions, cache])","ownership = Architecture([web, ordersApi, ledger, ordersDb, billingJob], [placeOrder, writeOrder, readOrders, postLedger], [clients, ordersRepo, billingRepo], \"lanes\")\nweb = Node(\"Web app\", \"frontend\")\nordersApi = Node(\"Orders API\", \"backend\", \"Node :8080\")\nordersDb = Node(\"orders table\", \"database\", \"Postgres\", null, \"success\")\nbillingJob = Node(\"Nightly billing\", \"backend\", \"cron 02:00\")\nledger = Node(\"Ledger\", \"database\", \"append-only\", null, \"info\")\nplaceOrder = Edge(web, ordersApi, \"POST /orders\")\nwriteOrder = Edge(ordersApi, ordersDb, \"insert\")\nreadOrders = Edge(billingJob, ordersDb, \"read unbilled\", null, \"danger\")\npostLedger = Edge(billingJob, ledger, \"post\")\nclients = Group(\"CLIENTS\", [web], \"plain\")\nordersRepo = Group(\"orders-service\", [ordersApi, ordersDb])\nbillingRepo = Group(\"billing\", [billingJob, ledger])"]},{"name":"Node","group":"Content","signature":"Node(label: string, kind?: \"frontend\" | \"backend\" | \"database\" | \"messagebus\" | \"cloud\" | \"security\" | \"external\", detail?: string, tag?: string, tone?: \"info\" | \"success\" | \"warning\" | \"danger\" | \"strong\", row?: number)","description":"A box in an Architecture. kind sets icon and color (default backend); detail is monospace, up to 2 lines (\\n breaks); tag is a small chip such as \"new\"; tone tints it (danger = risk, strong = where it ends); row pins it to a row (1, 2, ...) in lanes.","tier":"core","pack":"core","examples":[]},{"name":"Edge","group":"Content","signature":"Edge(from: Node, to: Node, label?: string, style?: \"solid\" | \"dashed\" | \"emphasis\" | \"security\", tone?: \"info\" | \"success\" | \"warning\" | \"danger\")","description":"An arrow between two Node statements (pass their names, not strings). style: dashed for async, emphasis for the main path, security for auth or trust flows. tone colors it, e.g. danger for a risky write, success for a read.","tier":"core","pack":"core","examples":[]},{"name":"Group","group":"Content","signature":"Group(label: string, members: Node[], kind?: \"region\" | \"security\" | \"plain\")","description":"A boundary around Node statements (a VPC, a cluster, a trust zone); members still sit where their edges put them. A group whose members are a subset of another's is drawn inside it; kind security draws it red, plain draws only its name.","tier":"core","pack":"core","examples":[]},{"name":"Diff","group":"Content","signature":"Diff(patch: string, title?: string)","description":"Unified diff (git diff output, one or more files) with line numbers. patch is a raw multi-line string. Clicked lines reach the agent as file:+line.","tier":"core","pack":"core","examples":["retryPatch = Diff(\"diff --git a/src/retry.ts b/src/retry.ts\n--- a/src/retry.ts\n+++ b/src/retry.ts\n@@ -10,2 +10,3 @@\n- for (let i = 0; i < 3; i++) {\n+ for (let i = 0; i < MAX_ATTEMPTS; i++) {\n+ if (i > 0) await sleep(backoff(i));\")"]},{"name":"FileTree","group":"Data","signature":"FileTree(entries: string[])","description":"File tree, one path per entry, optionally prefixed with a git status code (A new, M modified, D deleted, R old -> new) and suffixed with \" # note\". Folders are inferred. Clicked files and folders reach the agent by path.","tier":"core","pack":"core","examples":["changedFiles = FileTree([\"M src/session/store.ts # read-through to Postgres\", \"A src/session/pg-store.ts\", \"A migrations/0042_sessions.sql # table + expiry index\", \"R src/session/redis.ts -> src/session/redis-cache.ts\", \"D src/session/legacy.ts\", \"M src/config.ts # SESSIONS_PG flag\"])"]},{"name":"Mermaid","group":"Content","signature":"Mermaid(source: string, caption?: string)","description":"Diagram from Mermaid source (flowchart, sequence, state, class, ER, gantt). source is a raw multi-line string. Keep node labels to a few words; clicked nodes reach the agent by label.","tier":"core","pack":"@austere-axi/pack-mermaid","examples":["requestPath = Mermaid(\"flowchart LR\n client[Client] --> api[API]\n api --> pg[(Postgres)]\", \"Request path\")"]},{"name":"Progress","group":"Data","signature":"Progress(value: number, label?: string, max?: number)","description":"Progress bar. value counts toward max (default 100); label names what is progressing. Shows a percentage.","tier":"extended","pack":"@austere-axi/pack-shadcn","examples":["migration = Progress(64, \"Rows migrated\")"]},{"name":"Avatar","group":"Data","signature":"Avatar(name: string, src?: string, size?: \"md\" | \"lg\")","description":"Round avatar for a person. Shows the initials of name; src is an optional https image URL.","tier":"extended","pack":"@austere-axi/pack-shadcn","examples":["owner = Avatar(\"Ada Lovelace\")"]},{"name":"AvatarGroup","group":"Data","signature":"AvatarGroup(avatars: Avatar[], max?: number)","description":"Overlapping Avatars, for reviewers or owners. max (default 4) shows the rest as +N.","tier":"extended","pack":"@austere-axi/pack-shadcn","examples":["team = AvatarGroup([ada, grace, linus], 2)\nada = Avatar(\"Ada Lovelace\")\ngrace = Avatar(\"Grace Hopper\")\nlinus = Avatar(\"Linus Torvalds\")"]},{"name":"Tooltip","group":"Content","signature":"Tooltip(text: string, tip: string)","description":"Inline text with a short hint on hover or focus (a term and its definition). Keep tip to one line.","tier":"extended","pack":"@austere-axi/pack-shadcn","examples":["term = Tooltip(\"idempotent\", \"Safe to run twice with the same result.\")"]},{"name":"HoverCard","group":"Content","signature":"HoverCard(text: string, title: string, body?: string)","description":"Inline text that shows a small card (title and a line or two of body) on hover or focus, e.g. a person or a service.","tier":"extended","pack":"@austere-axi/pack-shadcn","examples":["svc = HoverCard(\"billing-api\", \"billing-api\", \"Owns invoices and refunds. On call: payments team.\")"]},{"name":"Breadcrumb","group":"Content","signature":"Breadcrumb(items: BreadcrumbItem[])","description":"Path trail such as Repo / Package / File, from BreadcrumbItems. Each item is a click target.","tier":"extended","pack":"@austere-axi/pack-shadcn","examples":["trail = Breadcrumb([repo, pkg, file])\nrepo = BreadcrumbItem(\"austere-axi\")\npkg = BreadcrumbItem(\"packs\")\nfile = BreadcrumbItem(\"shadcn\")"]},{"name":"BreadcrumbItem","group":"Content","signature":"BreadcrumbItem(label: string, href?: string)","description":"One step of a Breadcrumb. href is optional; the last item is the current page.","tier":"extended","pack":"@austere-axi/pack-shadcn","examples":[]},{"name":"Kbd","group":"Content","signature":"Kbd(keys: string)","description":"Keyboard shortcut. keys joins keys with \"+\", e.g. \"Ctrl+K\"; each key is drawn as a key cap.","tier":"extended","pack":"@austere-axi/pack-shadcn","examples":["shortcut = Kbd(\"Ctrl+Shift+P\")"]},{"name":"Skeleton","group":"Content","signature":"Skeleton(lines?: number, shape?: \"lines\" | \"avatar\")","description":"Grey placeholder block for content not yet written or loaded (default 3 lines). shape avatar adds a round head.","tier":"extended","pack":"@austere-axi/pack-shadcn","examples":["pending = Skeleton(3, \"avatar\")"]},{"name":"Calendar","group":"Data","signature":"Calendar(month: string, highlights?: string[])","description":"Read-only month grid (not a date picker). month is \"YYYY-MM\"; highlights lists \"YYYY-MM-DD\" or \"YYYY-MM-DD label\" entries to mark, e.g. a freeze or a release. Marked days are click targets.","tier":"extended","pack":"@austere-axi/pack-shadcn","examples":["window = Calendar(\"2026-11\", [\"2026-11-04 Code freeze\", \"2026-11-12 Release\", \"2026-11-13\"])"]},{"name":"Timeline","group":"Content","signature":"Timeline(items: TimelineItem[])","description":"Vertical history or schedule of TimelineItems, oldest first. Each item is a click target.","tier":"extended","pack":"@austere-axi/pack-shadcn","examples":["history = Timeline([found, fixed, rollout])\nfound = TimelineItem(\"Bug found\", \"Oct 28\", \"Retries double-charge on timeout.\")\nfixed = TimelineItem(\"Fix merged\", \"Oct 29\", \"Idempotency key added.\", \"current\")\nrollout = TimelineItem(\"Rollout\", \"Nov 3\", \"\", \"upcoming\")"]},{"name":"TimelineItem","group":"Content","signature":"TimelineItem(title: string, date?: string, detail?: string, state?: \"done\" | \"current\" | \"upcoming\")","description":"One event in a Timeline. state: done (default), current or upcoming.","tier":"extended","pack":"@austere-axi/pack-shadcn","examples":[]}]}
package/dist/cli.mjs CHANGED
@@ -608,7 +608,7 @@ var init_paths = __esm({
608
608
  HERE = path.dirname(fileURLToPath(import.meta.url));
609
609
  BUNDLED = true;
610
610
  ASSET_DIR = BUNDLED ? HERE : path.resolve(HERE, "../../dist");
611
- VERSION = true ? "0.4.0" : JSON.parse(fs.readFileSync(path.resolve(HERE, "../../package.json"), "utf8")).version;
611
+ VERSION = true ? "0.4.1" : JSON.parse(fs.readFileSync(path.resolve(HERE, "../../package.json"), "utf8")).version;
612
612
  CLI_ENTRY = BUNDLED ? fileURLToPath(import.meta.url) : path.resolve(HERE, "main.js");
613
613
  }
614
614
  });
@@ -1916,6 +1916,19 @@ function allowedHosts(extra = []) {
1916
1916
  for (const h of String(process.env.AUSTERE_AXI_ALLOWED_HOSTS || "").split(/\s+/)) if (h) set2.add(h.toLowerCase());
1917
1917
  return set2;
1918
1918
  }
1919
+ function linkUrl() {
1920
+ const raw = String(process.env.AUSTERE_AXI_LINK_URL || "").trim();
1921
+ if (!raw) return null;
1922
+ let u;
1923
+ try {
1924
+ u = new URL(raw);
1925
+ } catch {
1926
+ throw new Error(`AUSTERE_AXI_LINK_URL is not a URL: ${raw}`);
1927
+ }
1928
+ if (u.protocol !== "http:" && u.protocol !== "https:") throw new Error(`AUSTERE_AXI_LINK_URL must be http or https: ${raw}`);
1929
+ if (u.pathname !== "/" || u.search || u.hash || u.username || u.password) throw new Error(`AUSTERE_AXI_LINK_URL must be just scheme, host and optional port, like https://austere.example.com: ${raw}`);
1930
+ return { base: u.origin, host: u.hostname.toLowerCase() };
1931
+ }
1919
1932
  function hostOnly(hostHeader) {
1920
1933
  const h = String(hostHeader || "").toLowerCase();
1921
1934
  if (h.startsWith("[")) return h.slice(0, h.indexOf("]") + 1);
@@ -2113,6 +2126,7 @@ async function runServer({ port: port2, verbose = false, alsoListen = [] }) {
2113
2126
  const byeAt = /* @__PURE__ */ new Map();
2114
2127
  const browserSeen = /* @__PURE__ */ new Set();
2115
2128
  let tailnet = null;
2129
+ const linkBase = linkUrl();
2116
2130
  let lastActivity = Date.now();
2117
2131
  const longGraceMs = msEnv("AUSTERE_AXI_DISCONNECT_GRACE_MS", 5 * 6e4);
2118
2132
  const idleMs = hub ? 0 : msEnv("AUSTERE_AXI_IDLE_TIMEOUT_MS", 30 * 6e4);
@@ -2122,6 +2136,7 @@ async function runServer({ port: port2, verbose = false, alsoListen = [] }) {
2122
2136
  function hostsAllowed() {
2123
2137
  const extra = [];
2124
2138
  if (tailnet) extra.push(tailnet.ip, tailnet.dnsName);
2139
+ if (linkBase) extra.push(linkBase.host);
2125
2140
  for (const h of listeners.keys()) extra.push(h);
2126
2141
  return allowedHosts(extra);
2127
2142
  }
@@ -2133,9 +2148,14 @@ async function runServer({ port: port2, verbose = false, alsoListen = [] }) {
2133
2148
  if (explicit && !/^(0\.0\.0\.0|::|\*)$/.test(explicit) && listeners.has(explicit)) return explicit;
2134
2149
  return null;
2135
2150
  }
2136
- function remoteUrl(id) {
2151
+ function remoteBase() {
2152
+ if (linkBase) return linkBase.base;
2137
2153
  const host = remoteHost();
2138
- return host ? `http://${host}:${port2}/s/${id}` : null;
2154
+ return host ? `http://${host}:${port2}` : null;
2155
+ }
2156
+ function remoteUrl(id) {
2157
+ const base = remoteBase();
2158
+ return base ? `${base}/s/${id}` : null;
2139
2159
  }
2140
2160
  function links(id) {
2141
2161
  const out = { local: `http://127.0.0.1:${port2}/s/${id}` };
@@ -2358,6 +2378,7 @@ data: ${JSON.stringify({ state: "revoked" })}
2358
2378
  stateDir: stateDir(),
2359
2379
  listen: Array.from(listeners.keys()),
2360
2380
  tailnet: tailnet || void 0,
2381
+ remote: remoteBase() || void 0,
2361
2382
  hub: Boolean(hub),
2362
2383
  hubWanted
2363
2384
  });
@@ -2406,9 +2427,9 @@ data: ${JSON.stringify({ state: "revoked" })}
2406
2427
  if (route === "share") {
2407
2428
  if (!fs12.existsSync(file2)) return send(res, 404, { error: `no such file: ${file2}` });
2408
2429
  await ensureTailnetListener();
2409
- if (!remoteHost()) {
2430
+ if (!remoteBase()) {
2410
2431
  return send(res, 409, {
2411
- error: "no tailnet address: Tailscale is not running here and AUSTERE_AXI_HOST is not set, so a teammate could not open the link"
2432
+ error: "no tailnet address: Tailscale is not running here and neither AUSTERE_AXI_HOST nor AUSTERE_AXI_LINK_URL is set, so a teammate could not open the link"
2412
2433
  });
2413
2434
  }
2414
2435
  let inv;
@@ -29070,7 +29091,7 @@ COMMANDS
29070
29091
  ENV
29071
29092
  AUSTERE_AXI_PORT (4388) AUSTERE_AXI_STATE_DIR (~/.austere-axi) AUSTERE_AXI_NO_OPEN=1
29072
29093
  AUSTERE_AXI_HOST AUSTERE_AXI_NO_TAILSCALE=1 AUSTERE_AXI_DISCONNECT_GRACE_MS (300000)
29073
- AUSTERE_AXI_IDLE_TIMEOUT_MS (1800000) AUSTERE_AXI_ALLOWED_HOSTS AUSTERE_AXI_NPM_REGISTRY
29094
+ AUSTERE_AXI_IDLE_TIMEOUT_MS (1800000) AUSTERE_AXI_ALLOWED_HOSTS AUSTERE_AXI_LINK_URL AUSTERE_AXI_NPM_REGISTRY
29074
29095
  `;
29075
29096
  function parseArgs(argv) {
29076
29097
  const flags = {};
@@ -29584,7 +29605,7 @@ async function cmdHub(sub, args, flags) {
29584
29605
  return print({
29585
29606
  status: "on",
29586
29607
  url: url2,
29587
- phone: h.tailnet ? `http://${h.tailnet.dnsName || h.tailnet.ip}:${port()}/` : void 0,
29608
+ phone: h.remote ? `${h.remote}/` : h.tailnet ? `http://${h.tailnet.dnsName || h.tailnet.ip}:${port()}/` : void 0,
29588
29609
  index: hubDbFile(),
29589
29610
  note: "The server now stays up and indexes every artifact you open or check, by project and date. After a reboot, `austere-axi hub open` (or opening any review) starts it again."
29590
29611
  });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "format": 1,
3
3
  "pack": "@austere-axi/pack-mermaid",
4
- "version": "0.4.0",
4
+ "version": "0.4.1",
5
5
  "groups": [
6
6
  {
7
7
  "name": "Content",
@@ -29,7 +29,7 @@
29
29
  }
30
30
  ],
31
31
  "api": 1,
32
- "integrity": "sha384-GpGRe0zuS3HGqdLLXFhR3BCOsY+8rsN2SPv0yaA3gNvcB69o4HJ4aF/01oUOP2u0",
32
+ "integrity": "sha384-lVZbNOFGPfPn/KKSpEEGyMzn8Yu2UbWqt+3WMPcM0OpjyEvwUQRdW64/0kV6q6HZ",
33
33
  "size": 5782,
34
34
  "css": true,
35
35
  "defs": {
@@ -22,4 +22,4 @@
22
22
  font-size: 12.5px;
23
23
  color: var(--austere-muted);
24
24
  }
25
- `;var s={components:[N],css:V};var $=l(w());(0,$.registerPack)({name:"@austere-axi/pack-mermaid",version:"0.4.0",api:1,components:[].concat(s&&s.components?s.components:s),css:s&&s.css});})();
25
+ `;var s={components:[N],css:V};var $=l(w());(0,$.registerPack)({name:"@austere-axi/pack-mermaid",version:"0.4.1",api:1,components:[].concat(s&&s.components?s.components:s),css:s&&s.css});})();
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "format": 1,
3
3
  "pack": "@austere-axi/pack-shadcn",
4
- "version": "0.4.0",
4
+ "version": "0.4.1",
5
5
  "groups": [
6
6
  {
7
7
  "name": "Data",
@@ -195,7 +195,7 @@
195
195
  }
196
196
  ],
197
197
  "api": 1,
198
- "integrity": "sha384-CIYnYWZ/pwcwzMxA737O2dQSLE8ta7pk7PH4aMDoNOvM7KFwYz3n7ETeb/91s2pE",
198
+ "integrity": "sha384-8WWrxnmgighZHZ+zx/etLrmzSugBMWUCLxv3T0ON6+o5AVXqfBD9dYlPCpKF9n1O",
199
199
  "size": 16313,
200
200
  "css": true,
201
201
  "defs": {
@@ -66,4 +66,4 @@ rollout = TimelineItem("Rollout", "Nov 3", "", "upcoming")`});var ue=`.sh-muted
66
66
  .sh-timeline-item.is-upcoming .sh-timeline-dot { background: transparent; border: 1px solid var(--austere-muted); }
67
67
  .sh-timeline-body { min-width: 0; font-size: 13.5px; overflow-wrap: anywhere; }
68
68
  .sh-timeline-head { display: flex; flex-wrap: wrap; gap: 4px 12px; }
69
- `;var y={components:[X,M,ae,re,te,oe,H,ne,pe,de,ce,_],css:ue};var he=r(l());(0,he.registerPack)({name:"@austere-axi/pack-shadcn",version:"0.4.0",api:1,components:[].concat(y&&y.components?y.components:y),css:y&&y.css});})();
69
+ `;var y={components:[X,M,ae,re,te,oe,H,ne,pe,de,ce,_],css:ue};var he=r(l());(0,he.registerPack)({name:"@austere-axi/pack-shadcn",version:"0.4.1",api:1,components:[].concat(y&&y.components?y.components:y),css:y&&y.css});})();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "austere-axi",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Agent-authored review artifacts in OpenUI Lang: a few hundred tokens instead of a whole HTML page, with statement-level annotations fed back to the agent.",
5
5
  "type": "module",
6
6
  "license": "MIT",