@lovek4d/kickflip 0.10.0 → 0.10.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/board/domain.mjs CHANGED
@@ -1,10 +1,10 @@
1
- var Vt=Object.defineProperty;var d=(e,t)=>Vt(e,"name",{value:t,configurable:!0});import{routeRecord as R}from"../hub/routes.mjs";import Xt from"node:fs";import{ticketBadge as pt,ticketIn as Yt}from"./tickets.mjs";import{prLinks as zt,reviewLinks as Jt,reviewTime as Zt,reviewersOf as en}from"./review.mjs";import{remoteFromInput as tn}from"../hub/repos.mjs";import{reviewsOf as nn,reviewNeeds as sn,reviewActions as on}from"./auto-review.mjs";import{cardPRs as rn,hasPR as et,leastReady as an,prKeyOf as ln,prURL as dn}from"../shared/card-prs.mjs";import ft from"node:vm";import{checkWords as cn,parseCheck as un}from"../shared/owner-check.mjs";import{buildScriptOf as pn}from"./build-script.mjs";import{HUB as Qe,hubLineOf as gt}from"./hub-lines.mjs";const fn="card-merged",tt=ft.createContext({});for(const e of["ui-enums.js","ui-order.js"])ft.runInContext(Xt.readFileSync(new URL(`./public/${e}`,import.meta.url),"utf8"),tt);const{BUTTON:ce,ACTION_KIND:q,BUILDER_KIND:ue,PR_STATE:V,PHASES:kt}=tt.BoardUI,{laneOrder:gn}=tt.BoardUI,kn=["item-message","mark-ready","grill-more","prototype-request","visual-request","reask-request"],mt="question-reopen",So=Object.freeze({todo:"Todo",progress:"In Progress",review:"In Review",done:"Done"}),$e="move",mn=10*6e4,hn=5*6e4,ht="build-refused",De=Object.freeze({ask:"builder-ask",answer:"builder-answer"}),wn=864e5,pe=d(e=>e.lane==="done","closedLane"),yn=d(e=>`file:${e}`,"fileFold"),wt=7*wn,yt=d((e,t)=>({id:e,url:t?.workspace?`https://linear.app/${t.workspace}/issue/${e}`:null}),"linearRef"),bn=d(e=>String(e).replace(/^S(\d+)$/,"INT-$1"),"internalRef"),nt=d((e,t,n)=>Yt(n,t.ticket)?t.ticket:e.internal===!0?bn(t.id):t.id,"refOf"),Sn=d((e,t,n)=>pt(e,t,n)||(t?{id:String(t),url:null,source:null}:null),"ticketOf");function vn(e,t,n,s,r,a,c,l,u){return!a&&(e.lane==="done"||t?.state==="MERGED"||t?.state==="CLOSED")?"done":(n||s)&&!l?"progress":r?"review":t?.state==="OPEN"?t.checks?.state==="fail"&&(!t.isDraft||c)?"progress":"review":t||u?"progress":"todo"}d(vn,"laneOf");const He="Lander",bt=d(e=>e.kind!==ue.install&&e.label!==He,"changesCode");function xn(e,t){return e?.state==="MERGED"||t?V.merged:e?.state==="CLOSED"?V.closed:e?.state==="OPEN"?e.isDraft?V.draft:V.open:null}d(xn,"prStateOf");const St=d(e=>e.pr?.status===V.closed,"unlanded"),On=d(e=>e.some(t=>t.kind==="work")&&e.every(t=>t.lane==="done"),"finishedOf");function st(e){const t=vt(e);return t?.done&&t.handed_back?t.handed_back:null}d(st,"handedBackAt");const vt=d(e=>e.filter(t=>t?.started&&Q(t)&&t.kind!==ue.install&&t.kind!==ge).sort((t,n)=>String(t.started).localeCompare(String(n.started))).at(-1),"newestBuild");function En(e,t,n){return Object.fromEntries(e.map(s=>{const r=R(t?.reviews,s.github),a=r==="APPROVED"?"approved":r==="CHANGES_REQUESTED"?"changes":Object.hasOwn(n,s.name)?"sent":"unsent";return[s.name,{state:a,since:a==="sent"?R(n,s.name):null,github:s.github||null}]}))}d(En,"reviewOf");function $n(e,t,n,s,r,a,{held:c,needsApproval:l}){return n?.kind===ue.install?"builder":s?"checking":n?"builder":c&&e==="review"?"merge":r&&e==="review"?"owner":e==="review"&&t?.state==="OPEN"&&t.isDraft?a?"ci":"ready":e==="review"?xt(t,l)?"merge":"reviewers":null}d($n,"waitsOnOf");const xt=d((e,t)=>e?.reviewDecision==="APPROVED"||!t&&!["REVIEW_REQUIRED","CHANGES_REQUESTED"].includes(e?.reviewDecision),"reviewed"),Ne=/(^|\/)prototype-[\w-]+\.html/,An={reviewers:"Waiting for PR reviewers",merge:"Approved, merges once CI and the merge state allow",ci:"Marked ready for review once CI passes"},Ve={waiting:"Approved, Merge shows once CI and the merge state allow",ready:"Approved and green: tap Merge",merging:"Merging"},ot={auto:"Ready, merges once CI and the merge state allow",waiting:"Ready, Merge shows once CI and the merge state allow",ready:"Ready and green: tap Merge"},Bn=d(e=>`Verified: tap Merge to land it on ${e}`,"heldNext"),_n="Fix the failing CI",Rn="Your reply waits on the orchestrator",Mn="Tap Done",In=["status","agent","ask-link","command",$e,De.answer],qn=d(e=>(e.who==="claude"||e.via==="github")&&!In.includes(e.kind)&&(e.kind!==Qe||gt(e).fault),"countsUnread"),Cn=["lander-handoff","land-waiting"];function Dn({slug:e,itemFile:t,it:n,order:s,prs:r,repo:a,remote:c,base:l,builder:u,reachedPhase:m,agents:f,worktree:p,eta:h,thread:v,events:_,working:x,said:P,askedAt:C,topicItems:L,prSlack:T,boardReviewers:W,now:w,bumpedAt:b,holdAsk:i,closedAt:S,mergeAt:oe,autoMerge:me,skipDraft:he,tickets:K}){const F=r.map(k=>({repo:k.repo,number:k.number,...k.gh||k.kept||{},url:(k.gh||k.kept)?.url||dn(k),status:xn(k.gh,k.merged_at)})),ee=an(r.map(k=>k.gh)),I=r[ee]?.gh||null,G=F[ee]?.status??null,ze=n.lane||null,U=!!S&&String(S)>String(n.lane_at||"");U&&(n={...n,lane:"done",closed_at:S,reopened_at:void 0});const D=[n.closed_at,...r.flatMap(k=>[k.merged_at,k.gh?.mergedAt])].filter(Boolean).map(String).sort().pop()||null,we=String(D||""),ye=F.length>0&&F.every(k=>k.status===V.closed),be=(n.lane==="done"||n.reopened_at||G===V.merged)&&!ye&&([...P.filter(k=>k.kind==="item-message").map(k=>k.ts),...C].map(String).filter(k=>k>we).sort().pop()||n.reopened_at)||void 0,Le=n.answered_at&&!n.phase&&!n.closed_at&&!be?String(n.answered_at):void 0,j=be||Le,Re=n.owner_step&&!U?String(n.owner_step):null,Je=(n.lane==="done"||G===V.merged)&&!ye,H=!!j&&!!n.next&&String(n.next_at||"")>String(n.reopened_at||D||"");j&&!H&&(n={...n,next:void 0});const X=st(f),Te=j&&P.filter(k=>String(k.ts)>we&&(k.state==="waiting"||k.state==="working")).map(k=>String(k.ts)).sort()[0]||null,Se=!!Te,ve=`${e}/${n.id}`,xe=en(t,W),Pe=Object.fromEntries(Object.entries(Jt(t,n,ve,L,zt(T,r))).map(([k,de])=>[k,Zt(de.ts)])),te=Object.values(Pe).filter(Boolean).sort()[0]||null,Ue=[...v.map(k=>k.ts),..._.map(k=>k.ts)].sort().pop()||null,je=[...v.filter(k=>qn({...k,who:k.who||"claude"})).map(k=>k.ts),..._.map(k=>k.reply?.at)].filter(Boolean).sort(),Y=!et(n)&&n.pushed?.branch?{branch:n.pushed.branch,at:n.pushed.at||null,host:c?.host||null}:null,z=!et(n)&&n.merge_ready?.branch?{branch:n.merge_ready.branch,at:n.merge_ready.at||null,base:l}:null,Me=!!n.rebase_run&&I?.state==="OPEN",Ie=(n.rebased||[]).at(-1),re=f.filter(k=>k.started&&!k.done&&Q(k)),Ze=!!X&&bt(vt(f));let A=vn(n,I,re.some(bt),Ze,Y||z,!!j,he,Me,re.length>0||!!X);j&&A==="todo"&&(A="progress");const We=A==="review"&&!!te&&w-Date.parse(te)>wt,ae=!!Re&&Je&&A==="done";ae&&(A="review");let N=$n(A,I,u,!!X&&A!=="done",Y,he,{held:z,needsApproval:xe.length>0});const Fe=N==="builder"&&I?.state==="OPEN"&&!I.isDraft,qe=A==="progress"&&!N&&I?.state==="OPEN"&&(!I.isDraft||he),Oe=ae||!!j&&A==="progress"&&!N&&!qe&&!Se&&!H;Oe&&(N="owner");const o=!!j&&f.some(k=>Q(k)&&String(k.started||"")>String(j)),g=A==="progress"&&!N&&(qe||!!j&&!o);g&&(A="review");const E=i&&String(i.ts)>String(n.held_at||""),$=A==="todo"&&(E?!!i.data.hold:!!n.held),y=$&&(E?i.data.until:n.pick_up_at)||null,M=A!=="done",B=!!b&&!(we&&String(b)<=we),Ke=v.filter(k=>(k.who||"claude")==="claude"&&!["status","command",$e].includes(k.kind)&&!Cn.includes(k.line)).map(k=>String(k.ts)).sort().pop()||"",le=[oe,z&&n.merge_ready.tapped].filter(Boolean).map(String).sort().pop(),Ce=N==="merge"&&!!le&&le>Ke,Ee=v.filter(k=>(k.kind==="status"||k.kind===Qe)&&String(k.ts)>String(le)).sort((k,de)=>String(k.ts).localeCompare(String(de.ts))).at(-1),ne=Ee?.line==="land-waiting"?`${Ve.merging} \xB7 ${Ee.text[0].toLowerCase()}${Ee.text.slice(1)}`:Ve.merging,J=v.filter(k=>!["status",$e].includes(k.kind)).sort((k,de)=>String(k.ts).localeCompare(String(de.ts))).at(-1),O=A==="todo"&&J?.line===ht?J.text:null,ie=j||f.filter(Q).map(k=>k.started).filter(Boolean).map(String).sort().pop()||null,Ge=I?.reviewDecision==="APPROVED",Kt=Y&&N==="owner"?`merge ${Y.branch} into ${l}`:z&&N==="merge"?Ce?ne:Bn(l):N==="merge"&&!me?Ce?ne:(Ge?Ve:ot).waiting:N==="merge"&&!Ge?ot.auto:An[N],Gt=G===V.draft?`Review draft PR #${F[ee].number}`:null,Qt=!Me&&Ie?.to&&Ie.to===I?.headRefOid?`Rebased onto ${Ie.base||l}, pushed${I.mergeable==="MERGEABLE"?" \xB7 PR is clean again":""}`:null,Ht=Me&&N==="checking"?f.flatMap(k=>k.calls).sort((k,de)=>String(k.at).localeCompare(String(de.at))).at(-1)?.text??null:null;return{key:ve,kind:"work",order:s,file:e,fileTitle:t.title||e,id:n.id,ref:nt(t,n,K),title:n.title,storedLane:ze,created:n.created||null,reviewSince:g&&j||n.review_since||te||n.merge_ready?.at||n.pushed?.at||n.created||null,progressSince:ie,closedAt:A==="done"?D:null,internal:t.internal===!0,repo:t.internal===!0&&!t.repo?null:a,lane:A,waitsOn:N,builder:N==="checking"?null:u,reachedPhase:m,agents:f,worktree:p??null,eta:h,working:x,reopened:!!be,answered:!!Le,awaitsDone:Oe,ownerStep:Oe?Re:null,ownerCheck:Oe&&Re&&n.owner_check?{words:cn(un(n.owner_check.check)),broken:n.owner_check.broken||null}:null,stagedAt:A==="todo"&&n.phase==="staging"&&n.staged_at||null,roundAt:n.round_at||null,deps:n.deps||[],blockedUntil:n.blocked_until||null,blockReason:n.block_reason||null,held:$,heldUntil:y,risky:!!n.risky,bumpable:M,bumpedAt:M&&B?b:null,holdable:A!=="done",hasNotes:!!(n.plan||String(n.notes||"").trim()),finishable:A!=="done",merging:Ce,refusal:O,manualSteps:n.manual_steps||[],fromAsk:n.from_ask||null,unfiled:!!n.unfiled,filedHeld:!!n.filed_held,next:Fe||A==="done"||N==="checking"?null:g&&Se?Rn:qe?_n:Qt||Kt||n.next||Gt||null,rebaseFailed:Ht,branch:n.branch||I?.headRefName||Y?.branch||null,pushed:A==="done"||ae?null:Y,mergeReady:A==="review"?z:null,stale:A!=="done"&&(We||!!u?.stale),stalled:n.stalled?.reason||null,landStopped:A==="review"&&z&&n.merge_ready.stopped&&String(n.merge_ready.stopped)>String(n.merge_ready.tapped||"")?{words:At("land"),at:n.merge_ready.stopped}:null,ticket:K?.source==="linear"&&!n.ticket&&n.linear?.length?null:Sn(K,n.ticket,{repo:a}),ticketSource:K?.source||null,linear:(n.linear||[]).map(k=>K?.source==="linear"&&pt(K,k,{repo:a})||yt(k,K)),pr:F[ee]||null,prs:F,reviewers:xe.map(k=>k.name),review:En(xe,I||r[ee]?.kept,Pe),inReviewSince:te,threadCount:v.length+_.length,lastTs:Ue,othersAt:je,grilling:Bt("grill",_,f,C),prototyping:Bt("prototype",_,f,C),storedPhase:n.phase||null,phase:null,recheckAt:n.recheck_at||null,updatedAt:I?.mergedAt||I?.updatedAt||r[ee]?.merged_at||u?.at||Ue||null,ask:null,fold:null,action:null,install:A==="done"?null:Ln(n.install,I),waitFacts:Nn(n,{agents:f,thread:v,events:_,refusedAt:O?J.ts:null,replyAt:g?Te:null})}}d(Dn,"workItem");function Nn(e,{agents:t,thread:n,events:s,refusedAt:r,replyAt:a}){const c=new Set(n.filter(f=>f.kind===De.answer).map(f=>f.ask)),l=n.filter(f=>f.kind===De.ask&&!c.has(f.ask)).sort((f,p)=>String(f.ts).localeCompare(String(p.ts)))[0],u=d(f=>s.filter(p=>at(p,f)).pop()?.ts||null,"requestAt"),m=n.filter(f=>(f.who||"claude")==="claude"&&!["status","agent","command",$e].includes(f.kind)&&f.line!==ht).map(f=>f.ts);return{handedBackAt:st(t),next:e.next||null,ask:l?{at:l.ts,text:l.text}:null,unasked:n.filter(f=>f.line==="planner-unasked").sort((f,p)=>String(f.ts).localeCompare(String(p.ts))).map(f=>({at:f.ts,kind:f.detail})).pop()||null,grillAt:u("grill"),prototypeAt:u("prototype"),refusedAt:r,replyAt:a,unblockedAt:e.unblocked_at||null,unverified:t.some(f=>f.calls.some(p=>!f.verified||String(p.at)>String(f.verified.at))),writes:[...m,...t.flatMap(f=>[f.verified?.at,f.started])].filter(Boolean).map(String).sort()}}d(Nn,"waitFactsOf");function Ln(e,t){if(!e?.url)return null;const n=e.sha?String(e.sha).toLowerCase():null,s=t?.headRefOid?String(t.headRefOid).toLowerCase():null;return{url:e.url,label:e.label||null,builtAt:e.built_at||null,sha:n,older:!!(n&&s&&!s.startsWith(n))}}d(Ln,"installOf");function Tn(e,{needsApproval:t=!0}={}){if(e?.state!=="OPEN")return[`PR is ${String(e?.state||"unknown").toLowerCase()}`];const{state:n,pending:s,failing:r}=e.checks||{};return[e.isDraft&&"it is a draft",!xt(e,t)&&`review: ${String(e.reviewDecision||"none").toLowerCase().replace(/_/g," ")}`,n==="fail"&&`failing: ${r?.length?r.join(", "):"a check"}`,n==="pending"&&`pending: ${s?se(s,"check"):"checks"}`,e.mergeStateStatus!=="CLEAN"&&`merge state: ${String(e.mergeStateStatus||"unknown").toLowerCase()}`].filter(Boolean)}d(Tn,"mergeBlockers");const Pn=d((e,t)=>!Tn(e,{needsApproval:t}).length,"mergeable");function Un(e,t,n){if(pe(e))return null;const s=`#selectedCard=${encodeURIComponent(e.key)}`;if(e.stalled)return{kind:q.stalled,url:s,label:`stalled: ${e.stalled}`};if(jn(e)){const r=t.get(e.questions.first);return Ne.test(r?.url||"")?{kind:q.pick,url:`questions.html#${r.id}`,label:"Prototypes ready: pick a take"}:{kind:q.questions,url:`questions.html#${e.questions.first}`,label:`Answer ${se(e.questions.open,"question")}`}}return e.waitsOn==="ready"?{kind:q.ready,url:e.pr.url,label:`Review draft PR #${e.pr.number}`}:e.waitsOn==="merge"&&e.manualSteps.length&&!e.mergeReady?{kind:q.steps,url:s,label:"Run the manual steps, then merge"}:e.waitsOn==="merge"&&(!n||e.mergeReady)&&!e.merging&&e.prs.every(r=>["MERGED","CLOSED"].includes(r.state)||Pn(r,e.reviewers.length>0))?{kind:q.merge,url:s,label:"Merge"}:null}d(Un,"actionOf");const jn=d(e=>!!(e.questions&&e.questions.state!=="resolved"&&e.questions.open>0),"asking"),rt=new Set([q.pick,q.questions]);function Ot(e){return e.questions?ce.openQuestions:null}d(Ot,"actionButtonOf");const Wn=d(e=>e.kind==="work"&&e.lane==="todo"&&!e.action&&!e.unpicked,"buildableOf"),Fn=d((e,t)=>/^item:/.test(e.topic||"")&&Ne.test(e.url||"")&&!(e.options||[]).some(n=>n.id===t),"picksNoTake");function Kn(e,t){const n=e.filter(a=>a.topic===`item:${t}`),r=[...n.filter(a=>Ne.test(a.url||"")).flatMap(a=>a.answers.map(c=>({q:a,a:c,again:Fn(a,c.choice)}))),...n.filter(a=>a.request).map(a=>({q:a,a:a.request,again:!0}))].sort((a,c)=>String(a.a.ts).localeCompare(String(c.a.ts))).at(-1);return r?.again?{id:r.q.id,note:r.a.note.replace(/\s+/g," ").trim()}:null}d(Kn,"noTakePicked");const Gn="Prototype again",Qn=Object.freeze({[q.pick]:ce.openQuestions,[q.questions]:ce.openQuestions,[q.ready]:ce.github,[q.steps]:ce.github,[q.merge]:ce.merge,[q.stalled]:ce.reply}),Hn="Merges once unblocked",Vn=d(e=>`Blocked ${[e.blockedBy.length&&`by ${e.blockedBy.map(t=>t.ref).join(", ")}`,e.blockedUntil&&`until ${e.blockedUntil}`].filter(Boolean).join(" and ")}`,"mergeBlockedWords");function Xn(e,t,n){if(e.action=Un(e,t,n),e.mergeBlocked=e.action?.kind===q.merge&&e.blocked?Vn(e):null,e.mergeBlocked&&Object.assign(e,{action:null,next:Hn}),e.action?.kind===q.questions&&e.questions.fromAsks){Object.assign(e,{action:null,waitsOn:"owner",next:e.action.label,actionButton:Ot(e),resolvedBy:null});return}e.awaitsDone&&=!e.action,e.awaitsDone||Object.assign(e,{ownerStep:null,ownerCheck:null}),e.actionButton=Ot(e),e.resolvedBy=Qn[e.action?.kind]||null,e.action?.kind===q.merge&&!e.mergeReady&&(e.next=(e.pr?.reviewDecision==="APPROVED"?Ve:ot).ready),e.awaitsDone&&Object.assign(e,{lane:"review",next:e.ownerStep||Mn,bumpable:!1,bumpedAt:null});const s=e.lane==="todo"||e.waitsOn==="ready"||e.lane==="progress"&&e.waitsOn==="owner";if(!e.action||!s||e.stalled)return;const r=e.lane==="todo"&&rt.has(e.action.kind)?"todo":"review";Object.assign(e,{lane:r,waitsOn:e.waitsOn==="ready"?"ready":"owner",next:e.action.label})}d(Xn,"placeOwnerWait");const Et=d((e,t)=>e?e!=="todo":t.some(Q),"started"),$t="none set yet";function vo(e,t){const n=t.filter(s=>s?.started);return e.next||et(e)||e.pushed||e.held||e.lane==="done"||st(n)?!1:!n.some(s=>!s.done)&&Et(e.lane,n)}d(vo,"owesNext");const fe="visual",ge="helper",Yn=new Set(["grill","prototype",fe]),Z=d(e=>Yn.has(e.kind),"plans"),Q=d(e=>!Z(e),"builds"),zn={grill:"grilling",prototype:"prototyping"},Jn=d((e,t)=>`${e} ${t} by orchestrator`,"orchestratorDid"),At=d(e=>Jn(e,"stopped"),"stoppedWords"),Zn={grill:"grill-more",prototype:"prototype-request"},at=d((e,t)=>e.kind===Zn[t]&&!e.data?.question,"isRequest");function Bt(e,t,n,s){if(n.some(a=>a.kind===e&&a.started&&!a.done))return!0;const r=t.filter(a=>at(a,e)).pop();return!!(r&&!r.reply&&!s.some(a=>String(a)>String(r.ts))&&!n.some(a=>a.kind===e&&a.done&&String(a.started)>=String(r.ts)))}d(Bt,"planningOf");const xo=30*6e4;function Oo(e,t){const n=[];for(const s of e.items){if(pe(s)||(s.agents||[]).some(a=>a.kind==="prototype"&&a.started&&!a.done))continue;const r=s.prototyping&&t.filter(a=>a.data?.key===s.key&&at(a,"prototype")).pop();r&&n.push({key:s.key,request:r.id,since:r.ts});for(const a of e.owner.filter(c=>c.topic===`item:${s.key}`&&c.state==="requested"))n.push({key:s.key,request:a.request.id,question:a.id,since:a.request.ts})}return n}d(Oo,"prototypeWaits");function lt(e={},t=!1){return Object.entries(e).filter(([n,s])=>s&&typeof s=="object"&&(t||!n.startsWith("past-"))).map(([n,s])=>({name:n,kind:_t(s.kind)||null,model:s.model||(s.kind==="opus"||s.kind==="sonnet"?s.kind:null),label:s.label||null,started:s.started||null,eta:s.done?null:s.eta||null,done:s.done||null,log:s.session?.log||null,pid:s.session?.pid??null,calls:Array.isArray(s.calls)?s.calls:[],verified:s.verified||null,stopped:s.stopped||null,handed_back:s.handed_back||null,...s.pause?{pause:s.pause}:{},...s.kind===ge?{writes:!!s.writes,requested:s.requested||null}:{},...s.waits?{waits:s.waits}:{}})).sort((n,s)=>String(n.started||"").localeCompare(String(s.started||""))||n.name.localeCompare(s.name))}d(lt,"agentsOf");function it(e={}){return Object.values(e).filter(t=>t?.worktree?.path).sort((t,n)=>String(t.started||"").localeCompare(String(n.started||""))).at(-1)?.worktree.path??null}d(it,"worktreeOf");function es(e,t=[]){const n=e.filter(Q).map(s=>String(s.started||"")).filter(Boolean).sort().pop();return n===void 0?null:t.filter(s=>s.kind==="status"&&s.phase&&!s.planner&&String(s.ts)>=n).sort((s,r)=>String(s.ts).localeCompare(String(r.ts))).pop()?.phase||null}d(es,"reachedPhaseOf");function ts(e,t=[],n,s=null,r=null,a=!1){const c=e.filter(b=>b.started&&!b.done&&b.kind!==fe),l=c.every(Z),u=l?c:c.filter(Q),m=u.map(b=>String(b.started||"")).sort()[0];if(m===void 0)return null;const f=t.filter(b=>b.kind==="status"&&String(b.ts)>=m&&(l||!b.planner)).sort((b,i)=>String(b.ts).localeCompare(String(i.ts))).pop(),p=u.filter(b=>b.kind!==ge).at(-1),h=u.some(b=>b.kind===ue.install)?ue.install:p?_t(p.kind):"claude",v=l?null:u.find(b=>b.stopped&&String(b.stopped.at)>=String(b.started))?.stopped,_=f?.ts||m,x=v?v.at:s!==null&&s>Date.parse(_)?new Date(s).toISOString():_,P=f?f.phase||f.planner?f.text.replace(": "," \xB7 "):f.text||null:l?zn[h]:null,C=n-Date.parse(x),L=l?null:u.find(b=>b.pause)?.pause??null,T=!l&&!v&&(r==="held"||!!L?.paused),W=l||T?null:v?!1:r,w=T?!1:W===null?C>=mn:!W;return{progress:P,phase:f?.phase||null,at:_,kind:h,planner:l,silentSince:x,stale:w,alive:W,held:T,quiet:!!L||W===!0&&!a&&C>=hn,stopped:v?At(v.run):null,...L?{pause:{cause:L.cause,words:L.words,paused:!!L.paused}}:{}}}d(ts,"builderOf");const Eo={blocked:["nothing blocks it","nothing blocks it"],held:["you unhold it","unheld"],usage:["the usage pause lifts","the usage pause lifted"],bumped:["a slot frees","a slot freed"]},ns=15*6e4,ss=d(e=>(e.agents||[]).find(t=>t.name==="main"&&t.started&&!t.done&&Q(t))??null,"mainBuild"),os=d((e,t,n,{now:s,usageHeld:r})=>e.blocked||e.held||r(n??"claude")||!!(t.until&&s<Date.parse(t.until)),"stillPaused");function rs(e,{now:t=Date.now(),usageHeld:n}){const s={ask:[],drop:[],stop:[],resume:[]},r=[];for(const a of e){if(a.kind!=="work"||a.lane!=="progress")continue;const c=ss(a);if(!c)continue;const{pause:l}=c;if(!l){a.builder?.phase!=="landing"&&n(c.kind??"claude")&&s.ask.push(a.key);continue}const u=os(a,l,c.kind,{now:t,usageHeld:n});if(l.paused){u||r.push({key:a.key,at:l.paused});continue}!u&&l.cause!=="bumped"?s.drop.push(a.key):t-Date.parse(l.at)>=ns&&s.stop.push(a.key)}return s.resume=r.sort((a,c)=>String(a.at).localeCompare(String(c.at))).map(a=>a.key),s}d(rs,"pauseWork");function $o(e,t,n){const s=rs(t,n).drop;return e.filter(r=>s.includes(r))}d($o,"confirmedDrops");const dt={opus:"Claude Opus",sonnet:"Claude Sonnet",codex:"Codex"},as=d(({model:e,kind:t})=>dt[e]||dt[t]||(t==="claude"?dt.opus:null),"modelName"),_t=d(e=>e==="opus"||e==="sonnet"?"claude":e,"providerOf");function Ae(e){const t=as(e),{kind:n}=e,s=e.label?.replace(/\bbuilder\b/,"implementer").replace(/\bBuilder\b/,"Implementer"),r=d(c=>c[0].toLowerCase()+c.slice(1),"lower");if(t&&Z(e))return`${t} ${n}`;if(t)return`${t} ${r(s?.replace(/^(?:claude\s+)?(?:opus|sonnet|codex)\b\s*/i,"")||"implementer")}`;const a=n?n[0].toUpperCase()+n.slice(1):"";return a?s?s.toLowerCase().includes(n.toLowerCase())?s:`${a} ${r(s)}`:a:s||"Implementer"}d(Ae,"agentName");const Rt=d((e,t)=>!!e.resolved&&(!e.resolved_at||String(e.resolved_at)>=String(t)),"resolvedSince");function ls(e,t,n=[],s=[],r=[]){const a=t.filter(x=>x.data?.id===e.id),c=a[a.length-1]||null,l=r.filter(x=>x.data?.question===e.id).at(-1),u=d((x,...P)=>!!x&&P.every(C=>String(x.ts)>String(C||"")),"after"),m=!!(e.resolved&&e.resolved_at&&c&&String(c.ts)>e.resolved_at),f=n.filter(x=>x.data?.question===e.id&&u(x,c?.ts,l?.ts)).at(-1),p=u(l,c?.ts,f?.ts,e.resolved_at),h=e.resolved&&!m&&!p,v=h?s.filter(x=>x.data?.question===e.id&&String(x.ts)>String(e.resolved_at||"")).at(-1):null,_=p?"open":h?"resolved":f?"requested":c?c.reply?"replied":"answered":"open";return{...e,state:_,reopened:p,request:f?{id:f.id,ts:f.ts,note:f.data.note||"",choice:f.data.choice||null}:null,reask:v?{id:v.id,ts:v.ts}:null,answers:a.map(x=>({ts:x.ts,choice:x.data.choice||null,note:x.data.note||"",files:x.data.files||[],reply:x.reply,replies:x.replies,acted:Rt(e,x.ts)}))}}d(ls,"ownerQuestion");const ke=d(e=>e.round??(Ne.test(e.url||"")?1:0),"roundOf");function is(e,t){const n=d((s,r)=>s.confirm_of||(s.options||[]).some(a=>a.id===r.choice),"settles");return e.filter(s=>ke(s)===t&&s.answers.length).map(s=>[s,s.answers.at(-1)]).filter(([s,r])=>n(s,r)).map(([,s])=>s).sort((s,r)=>String(s.ts).localeCompare(String(r.ts))).at(-1)}d(is,"roundSettledBy");function ds(e){const t=e.find(r=>r.request&&!e.some(a=>ke(a)>ke(r)));if(t)return{n:ke(t)+1,state:"making"};const n=Math.max(0,...e.map(ke));if(!n)return null;const s=is(e,n);return s?{n,state:"closed",take:s.choice}:e.some(r=>ke(r)===n&&r.state!=="resolved")?{n,state:"back"}:null}d(ds,"roundState");function cs(e){const t=e.filter(n=>Ne.test(n.url||"")).map(n=>({n:ke(n),q:n.id,url:n.url,confirm:!!n.confirm_of,state:n.state})).sort((n,s)=>n.n-s.n);return t.length?{open:t.some(n=>n.state==="open"),rounds:t}:null}d(cs,"prototypesOf");const Be=d((e,t,n)=>{const s=e.get(t);s?s.push(n):e.set(t,[n])},"add"),_e=d((e,t)=>{const n=new Map;for(const s of e)Be(n,t(s),s);return n},"groupBy");function us(e,t,n=new Set){const s=new Set(e.map(c=>c.id)),r=[...e];t.some(c=>!s.has(c.topic))&&r.push({id:"",title:"Other"});const a=_e(t,c=>s.has(c.topic)?c.topic:"");return r.map(c=>{const l=a.get(c.id)||[],u=d(p=>l.filter(h=>h.state===p).length,"count"),m=l.every(p=>p.state==="resolved")||n.has(c.id)&&!u("open"),f=l.length&&m?"resolved":"open";return{...c,state:f,questions:l.map(p=>p.id),open:u("open"),answered:u("answered"),replied:u("replied"),resolved:u("resolved"),round:ds(l)}}).filter(c=>c.questions.length)}d(us,"ownerTopics");const Xe={implement:"Implementation requested",prototype:"Prototype requested",grill:"Grill requested",visual:"Visual requested",reask:"Re-ask requested",hold:"Held",resume:"Resumed",done:"Marked done",staged:"Marked as staging",readied:"Marked as ready",related:"Related to",unrelated:"Unrelated from",followup:"Follow-up","followup-of":"Follow-up of"},Mt={"grill-more":"grill","prototype-request":"prototype","visual-request":"visual","reask-request":"reask"},It={"mark-ready":"Mark ready",...Object.fromEntries(Object.entries(Mt).map(([e,t])=>[e,Xe[t]]))},ps=["grill-more","prototype-request","visual-request"],fs="answers submitted",qt={reply:"Reply",build:"Implement"},gs=d(e=>qt[e.data.intent],"intentLabel"),ks=d((e,t)=>{if(e.kind!=="prototype-request"||!t.has(e.data.question))return e.data.text||(e.data.files?.length?"":R(It,e.kind))||"";const n=t.get(e.data.question),s=(n.options||[]).find(r=>r.id===e.data.choice);return[Dt(t,n),s&&`${s.id}. ${s.label}`,e.data.note].filter(Boolean).join(`
2
- \u2192 `)},"actionText"),Ct=d((e,t)=>[...e.keys()].indexOf(t)+1,"numberOf"),Dt=d((e,t)=>`Question ${Ct(e,t.id)} \xB7 ${t.question}`,"numbered"),ms="question reopened",Ye=d(e=>e.data?.shared_by?.login?{by:e.data.shared_by.name||e.data.shared_by.login}:{},"byOf"),hs=d((e,t)=>{const n=t.get(e.card);return{...e,text:`${Xe[e.command]} ${n?`${n.ref}: ${n.title}`:e.card}`,link:`#selectedCard=${encodeURIComponent(e.card)}`}},"linkLine"),Nt=d(e=>({kind:"command",command:e,request:!0,mark:Xe[e]}),"commandLine"),ws=d((e,t)=>{if(e.kind==="item-message")return qt[e.data.intent]?{status:gs(e)}:{};const n={request:!0,command:Mt[e.kind]};return ps.includes(e.kind)?(e.data.question?t.has(e.data.question):String(e.data.text||"").trim()||e.data.files?.length)?{status:It[e.kind]}:n:e.kind==="reask-request"?n:{}},"requestOf"),Lt=d(e=>[...e.filter(t=>t.who==="claude"&&!["status","agent","command",$e,De.answer].includes(t.kind)).map(t=>t.ts),...e.filter(t=>t.kind==="ask").map(t=>t.reply?.at)].filter(Boolean).map(String).sort().pop()||"","claudeAtOf");function Tt(e,t={}){const n=new Map,s=new Map;for(const p of t.items||[])Be(n,p.topic,p),s.set(p.id,p.topic);const r=new Map,a=new Map,c=new Map,l=new Map,u=new Map,m=new Map,f=new Map;e.forEach((p,h)=>{f.set(p,h),p.data?.key&&kn.includes(p.kind)&&Be(r,p.data.key,p),p.kind==="owner-answer"&&s.has(p.data?.id)&&Be(a,s.get(p.data.id),p),p.kind===mt&&s.has(p.data?.question)&&Be(c,s.get(p.data.question),p),p.kind==="ask"&&l.set(p.id,p)});for(const p of e.filter(h=>h.kind==="ask-edited").sort((h,v)=>String(h.ts).localeCompare(String(v.ts)))){u.set(p.data?.id,p.data?.text);for(const h of p.data?.files||[])Be(m,p.data?.id,h)}return{byKey:r,answers:a,reopens:c,asks:l,edits:u,editFiles:m,questions:n,order:f}}d(Tt,"threadIndex");const ct=d((e,t)=>{const n=t.filter(s=>s?.length);return n.length>1?n.flat().sort((s,r)=>e.order.get(s)-e.order.get(r)):[...n[0]||[]]},"inLogOrder"),ys=/; its log is \S*\/\.claude\/kickflip\/logs\/([^/\s]+\.log)\.$/,bs=d(e=>{const t=!e.log&&typeof e.text=="string"?ys.exec(e.text):null;return t?{...e,text:`${e.text.slice(0,t.index)}.`,log:t[1]}:e},"withLog");function Ss(e,t){const n=t.run?e.find(s=>s.started===t.run):e.filter(s=>Z(s)===!!t.planner&&s.started&&String(s.started)<=String(t.ts)&&(!s.done||String(t.ts)<=String(s.done))).pop();return n?{agent:Ae(n),run:ut(e,n),running:!n.done}:{}}d(Ss,"agentOfLine");function ut(e,t){return t.label!==He?t.started:e.filter(n=>Q(n)&&n.label!==He&&n.started&&String(n.started)<String(t.started)).pop()?.started??t.started}d(ut,"sectionOf");function vs(e,{lines:t=[],events:n=[],owner:s={},working:r={},fromAsk:a=null,links:c=[],agents:l={},moved:u=[],cards:m=new Map,index:f=Tt(n,s)}){const p=e.startsWith("ask:")?[e.slice(4)]:[a,...u].filter(Boolean),h=[...new Set([e,...p.map(i=>`ask:${i}`)])],v=new Map((f.questions.get(`item:${e}`)||[]).map(i=>[i.id,i])),_=f.answers.get(`item:${e}`)||[],x=new Set([...v.values()].map(i=>i.round_of).filter(Boolean)),P=lt(l,!0).filter(Q).map(i=>String(i.started||"")).filter(Boolean),C=new Set(_.filter(i=>Rt(v.get(i.data.id),i.ts)||x.has(i.data.id)||P.some(S=>S>String(i.ts))).map(i=>i.id)),L=lt(l,!0),T=[...L.flatMap(i=>[...i.started?[{ts:i.started,who:"claude",kind:"agent",text:`${Ae(i)} started`,agent:Ae(i),run:ut(L,i),finished:!1,eta:i.done?null:i.eta,...i.log?{log:i.log}:{},...Z(i)?{planner:!0}:{}}]:[],...i.done?[{ts:i.done,who:"claude",kind:"agent",text:`${Ae(i)} finished`,agent:Ae(i),run:ut(L,i),finished:!0,eta:null,...Z(i)?{planner:!0}:{}}]:[],...i.calls.length?[{ts:i.done||i.calls.at(-1).at,who:"claude",kind:"calls",text:Ae(i),calls:i.calls.map(S=>S.text),verified:i.verified}]:[]]),...t.map(bs).map(i=>i.card?hs(i,m):i).map(i=>({ts:i.ts,who:i.who||"claude",...i.log?{log:i.log}:{},...i.kind==="status"?{kind:"status",...i.planner?{planner:!0}:{},...Ss(L,i)}:{},...i.kind===fe||i.kind===$e?{kind:i.kind}:{},...i.kind===Qe?{kind:Qe,line:i.line,...gt(i),...i.detail?{detail:i.detail}:{}}:{},...i.command?Nt(i.command):{},...i.card?{card:i.card}:{},...Object.values(De).includes(i.kind)?{kind:i.kind,ask:i.ask}:{},text:i.text||"",html:i.html||null,link:i.link||null,via:i.via||null,...i.by?{by:i.by}:{}})),...ct(f,h.map(i=>f.byKey.get(i))).map(i=>({ts:i.ts,who:"love",via:"site",kind:i.kind,text:ks(i,v),files:i.data.files||[],eventId:i.id,reply:i.reply,replies:i.replies,...ws(i,v),...Ye(i)})),..._.map(i=>{const S=[i.data.label||i.data.choice,i.data.note].filter(Boolean).join(" \xB7 ");return{ts:i.ts,who:"love",via:"questions",kind:i.kind,text:`${Dt(v,v.get(i.data.id))}
3
- \u2192 ${S}`,files:i.data.files||[],eventId:i.id,reply:i.reply,replies:i.replies,status:fs,...Ye(i)}}),...(f.reopens.get(`item:${e}`)||[]).map(i=>({ts:i.ts,who:"love",kind:i.kind,request:!0,mark:ms,text:`Question ${Ct(v,i.data.question)} was reopened`,...Ye(i)})),...ct(f,[...new Set(p)].map(i=>f.asks.has(i)?[f.asks.get(i)]:[])).flatMap(i=>[{ts:i.ts,who:"love",via:"ask",kind:i.kind,text:i.data?.text||"",files:i.data?.files||[],eventId:i.id,reply:i.reply,replies:i.replies,...u.includes(i.id)?{moved:!0}:{},...Ye(i),...typeof i.data?.from?.title=="string"?{from:i.data.from.title}:{}},...i.data?.quick===!0?[{ts:i.ts,who:"love",...Nt("implement"),text:Xe.implement}]:[],...c.map(S=>({ts:i.ts,who:"claude",kind:"ask-link",text:S.folded?`Moved to ${S.ref}: ${S.title}`:`${S.became?"Became":"Added to"} ${S.ref}`,link:`#selectedCard=${encodeURIComponent(S.key)}`,html:null,via:null}))])].sort((i,S)=>String(i.ts).localeCompare(String(S.ts))),W=Lt(T),{edits:w,editFiles:b}=f;for(const i of T){if(!i.eventId)continue;(i.kind==="ask"||i.kind==="item-message")&&typeof w.get(i.eventId)=="string"&&Object.assign(i,{text:w.get(i.eventId),edited:!0,files:[...i.files,...b.get(i.eventId)||[]]});const S=i.kind==="ask"&&(c.length>0||!!i.moved);i.state=i.reply?"replied":S||C.has(i.eventId)||W>String(i.ts)?"answered":R(r,i.eventId)?"working":"waiting",i.canDelete=i.kind==="item-message"&&(i.state==="working"||i.state==="waiting"),i.canEdit=i.canDelete}return xs(T)}d(vs,"threadOf");const Pt=["waiting","working","answered","replied"];function xs(e){const t=new Map;for(const n of e)n.kind==="owner-answer"&&t.set(n.ts,[...t.get(n.ts)||[],n]);return e.flatMap(n=>{if(n.kind!=="owner-answer")return[n];const s=t.get(n.ts);return s.length===1?[n]:s[0]!==n?[]:[{...n,text:s.map(r=>r.text).join(`
1
+ var Vt=Object.defineProperty;var d=(e,t)=>Vt(e,"name",{value:t,configurable:!0});import{routeRecord as R}from"../hub/routes.mjs";import Xt from"node:fs";import{ticketBadge as pt,ticketIn as Yt}from"./tickets.mjs";import{prLinks as zt,reviewLinks as Jt,reviewTime as Zt,reviewersOf as en}from"./review.mjs";import{remoteFromInput as tn}from"../hub/repos.mjs";import{reviewsOf as nn,reviewNeeds as sn,reviewActions as on}from"./auto-review.mjs";import{cardPRs as rn,hasPR as et,leastReady as an,prKeyOf as ln,prURL as dn}from"../shared/card-prs.mjs";import ft from"node:vm";import{checkWords as cn,parseCheck as un}from"../shared/owner-check.mjs";import{buildScriptOf as pn}from"./build-script.mjs";import{HUB as Qe,hubLineOf as gt}from"./hub-lines.mjs";const fn="card-merged",tt=ft.createContext({});for(const e of["ui-enums.js","ui-order.js"])ft.runInContext(Xt.readFileSync(new URL(`./public/${e}`,import.meta.url),"utf8"),tt);const{BUTTON:ce,ACTION_KIND:q,BUILDER_KIND:ue,PR_STATE:X,PHASES:kt}=tt.BoardUI,{laneOrder:gn}=tt.BoardUI,kn=["item-message","mark-ready","grill-more","prototype-request","visual-request","reask-request"],mt="question-reopen",So=Object.freeze({todo:"Todo",progress:"In Progress",review:"In Review",done:"Done"}),$e="move",mn=10*6e4,hn=5*6e4,ht="build-refused",De=Object.freeze({ask:"builder-ask",answer:"builder-answer"}),wn=864e5,pe=d(e=>e.lane==="done","closedLane"),yn=d(e=>`file:${e}`,"fileFold"),wt=7*wn,yt=d((e,t)=>({id:e,url:t?.workspace?`https://linear.app/${t.workspace}/issue/${e}`:null}),"linearRef"),bn=d(e=>String(e).replace(/^S(\d+)$/,"INT-$1"),"internalRef"),nt=d((e,t,n)=>Yt(n,t.ticket)?t.ticket:e.internal===!0?bn(t.id):t.id,"refOf"),Sn=d((e,t,n)=>pt(e,t,n)||(t?{id:String(t),url:null,source:null}:null),"ticketOf");function vn(e,t,n,s,r,a,u,i,p){return!a&&(e.lane==="done"||t?.state==="MERGED"||t?.state==="CLOSED")?"done":(n||s)&&!i?"progress":r?"review":t?.state==="OPEN"?t.checks?.state==="fail"&&(!t.isDraft||u)?"progress":"review":t||p?"progress":"todo"}d(vn,"laneOf");const He="Lander",bt=d(e=>e.kind!==ue.install&&e.label!==He,"changesCode");function xn(e,t){return e?.state==="MERGED"||t?X.merged:e?.state==="CLOSED"?X.closed:e?.state==="OPEN"?e.isDraft?X.draft:X.open:null}d(xn,"prStateOf");const St=d(e=>e.pr?.status===X.closed,"unlanded"),On=d(e=>e.some(t=>t.kind==="work")&&e.every(t=>t.lane==="done"),"finishedOf");function st(e){const t=vt(e);return t?.done&&t.handed_back?t.handed_back:null}d(st,"handedBackAt");const vt=d(e=>e.filter(t=>t?.started&&H(t)&&t.kind!==ue.install&&t.kind!==ge).sort((t,n)=>String(t.started).localeCompare(String(n.started))).at(-1),"newestBuild");function En(e,t,n){return Object.fromEntries(e.map(s=>{const r=R(t?.reviews,s.github),a=r==="APPROVED"?"approved":r==="CHANGES_REQUESTED"?"changes":Object.hasOwn(n,s.name)?"sent":"unsent";return[s.name,{state:a,since:a==="sent"?R(n,s.name):null,github:s.github||null}]}))}d(En,"reviewOf");function $n(e,t,n,s,r,a,{held:u,needsApproval:i}){return n?.kind===ue.install?"builder":s?"checking":n?"builder":u&&e==="review"?"merge":r&&e==="review"?"owner":e==="review"&&t?.state==="OPEN"&&t.isDraft?a?"ci":"ready":e==="review"?xt(t,i)?"merge":"reviewers":null}d($n,"waitsOnOf");const xt=d((e,t)=>e?.reviewDecision==="APPROVED"||!t&&!["REVIEW_REQUIRED","CHANGES_REQUESTED"].includes(e?.reviewDecision),"reviewed"),Ne=/(^|\/)prototype-[\w-]+\.html/,An={reviewers:"Waiting for PR reviewers",merge:"Approved, merges once CI and the merge state allow",ci:"Marked ready for review once CI passes"},Ve={waiting:"Approved, Merge shows once CI and the merge state allow",ready:"Approved and green: tap Merge",merging:"Merging"},ot={auto:"Ready, merges once CI and the merge state allow",waiting:"Ready, Merge shows once CI and the merge state allow",ready:"Ready and green: tap Merge"},Bn=d(e=>`Verified: tap Merge to land it on ${e}`,"heldNext"),_n="Fix the failing CI",Rn="Your reply waits on the orchestrator",Mn="Tap Done",In=["status","agent","ask-link","command",$e,De.answer],qn=d(e=>(e.who==="claude"||e.via==="github")&&!In.includes(e.kind)&&(e.kind!==Qe||gt(e).fault),"countsUnread"),Cn=["lander-handoff","land-waiting"];function Dn({slug:e,itemFile:t,it:n,order:s,prs:r,repo:a,remote:u,base:i,builder:p,reachedPhase:m,agents:f,worktree:c,eta:h,thread:O,events:A,working:S,said:P,askedAt:D,topicItems:U,prSlack:N,boardReviewers:j,now:w,bumpedAt:b,holdAsk:C,closedAt:l,mergeAt:B,autoMerge:me,skipDraft:he,tickets:G}){const K=r.map(k=>({repo:k.repo,number:k.number,...k.gh||k.kept||{},url:(k.gh||k.kept)?.url||dn(k),status:xn(k.gh,k.merged_at)})),te=an(r.map(k=>k.gh)),I=r[te]?.gh||null,Q=K[te]?.status??null,ze=n.lane||null,W=!!l&&String(l)>String(n.lane_at||"");W&&(n={...n,lane:"done",closed_at:l,reopened_at:void 0});const L=[n.closed_at,...r.flatMap(k=>[k.merged_at,k.gh?.mergedAt])].filter(Boolean).map(String).sort().pop()||null,we=String(L||""),ye=K.length>0&&K.every(k=>k.status===X.closed),be=(n.lane==="done"||n.reopened_at||Q===X.merged)&&!ye&&([...P.filter(k=>k.kind==="item-message").map(k=>k.ts),...D].map(String).filter(k=>k>we).sort().pop()||n.reopened_at)||void 0,Le=n.answered_at&&!n.phase&&!n.closed_at&&!be?String(n.answered_at):void 0,F=be||Le,Re=n.owner_step&&!W?String(n.owner_step):null,Je=(n.lane==="done"||Q===X.merged)&&!ye,V=!!F&&!!n.next&&String(n.next_at||"")>String(n.reopened_at||L||"");F&&!V&&(n={...n,next:void 0});const Y=st(f),Te=F&&P.filter(k=>String(k.ts)>we&&(k.state==="waiting"||k.state==="working")).map(k=>String(k.ts)).sort()[0]||null,Se=!!Te,ve=`${e}/${n.id}`,xe=en(t,j),Pe=Object.fromEntries(Object.entries(Jt(t,n,ve,U,zt(N,r))).map(([k,de])=>[k,Zt(de.ts)])),ne=Object.values(Pe).filter(Boolean).sort()[0]||null,Ue=[...O.map(k=>k.ts),...A.map(k=>k.ts)].sort().pop()||null,je=[...O.filter(k=>qn({...k,who:k.who||"claude"})).map(k=>k.ts),...A.map(k=>k.reply?.at)].filter(Boolean).sort(),z=!et(n)&&n.pushed?.branch?{branch:n.pushed.branch,at:n.pushed.at||null,host:u?.host||null}:null,J=!et(n)&&n.merge_ready?.branch?{branch:n.merge_ready.branch,at:n.merge_ready.at||null,base:i}:null,Me=!!n.rebase_run&&I?.state==="OPEN",Ie=(n.rebased||[]).at(-1),re=f.filter(k=>k.started&&!k.done&&H(k)),Ze=!!Y&&bt(vt(f));let $=vn(n,I,re.some(bt),Ze,z||J,!!F,he,Me,re.length>0||!!Y);F&&$==="todo"&&($="progress");const We=$==="review"&&!!ne&&w-Date.parse(ne)>wt,ae=!!Re&&Je&&$==="done";ae&&($="review");let T=$n($,I,p,!!Y&&$!=="done",z,he,{held:J,needsApproval:xe.length>0});const Fe=T==="builder"&&I?.state==="OPEN"&&!I.isDraft,qe=$==="progress"&&!T&&I?.state==="OPEN"&&(!I.isDraft||he),Oe=ae||!!F&&$==="progress"&&!T&&!qe&&!Se&&!V;Oe&&(T="owner");const o=!!F&&f.some(k=>H(k)&&String(k.started||"")>String(F)),g=$==="progress"&&!T&&(qe||!!F&&!o);g&&($="review");const x=C&&String(C.ts)>String(n.held_at||""),E=$==="todo"&&(x?!!C.data.hold:!!n.held),y=E&&(x?C.data.until:n.pick_up_at)||null,M=$!=="done",_=!!b&&!(we&&String(b)<=we),Ke=O.filter(k=>(k.who||"claude")==="claude"&&!["status","command",$e].includes(k.kind)&&!Cn.includes(k.line)).map(k=>String(k.ts)).sort().pop()||"",le=[B,J&&n.merge_ready.tapped].filter(Boolean).map(String).sort().pop(),Ce=T==="merge"&&!!le&&le>Ke,Ee=O.filter(k=>(k.kind==="status"||k.kind===Qe)&&String(k.ts)>String(le)).sort((k,de)=>String(k.ts).localeCompare(String(de.ts))).at(-1),se=Ee?.line==="land-waiting"?`${Ve.merging} \xB7 ${Ee.text[0].toLowerCase()}${Ee.text.slice(1)}`:Ve.merging,Z=O.filter(k=>!["status",$e].includes(k.kind)).sort((k,de)=>String(k.ts).localeCompare(String(de.ts))).at(-1),v=$==="todo"&&Z?.line===ht?Z.text:null,ie=F||f.filter(H).map(k=>k.started).filter(Boolean).map(String).sort().pop()||null,Ge=I?.reviewDecision==="APPROVED",Kt=z&&T==="owner"?`merge ${z.branch} into ${i}`:J&&T==="merge"?Ce?se:Bn(i):T==="merge"&&!me?Ce?se:(Ge?Ve:ot).waiting:T==="merge"&&!Ge?ot.auto:An[T],Gt=Q===X.draft?`Review draft PR #${K[te].number}`:null,Qt=!Me&&Ie?.to&&Ie.to===I?.headRefOid?`Rebased onto ${Ie.base||i}, pushed${I.mergeable==="MERGEABLE"?" \xB7 PR is clean again":""}`:null,Ht=Me&&T==="checking"?f.flatMap(k=>k.calls).sort((k,de)=>String(k.at).localeCompare(String(de.at))).at(-1)?.text??null:null;return{key:ve,kind:"work",order:s,file:e,fileTitle:t.title||e,id:n.id,ref:nt(t,n,G),title:n.title,storedLane:ze,created:n.created||null,reviewSince:g&&F||n.review_since||ne||n.merge_ready?.at||n.pushed?.at||n.created||null,progressSince:ie,closedAt:$==="done"?L:null,internal:t.internal===!0,repo:t.internal===!0&&!t.repo?null:a,lane:$,waitsOn:T,builder:T==="checking"?null:p,reachedPhase:m,agents:f,worktree:c??null,eta:h,working:S,reopened:!!be,answered:!!Le,awaitsDone:Oe,ownerStep:Oe?Re:null,ownerCheck:Oe&&Re&&n.owner_check?{words:cn(un(n.owner_check.check)),broken:n.owner_check.broken||null}:null,stagedAt:$==="todo"&&n.phase==="staging"&&n.staged_at||null,roundAt:n.round_at||null,deps:n.deps||[],blockedUntil:n.blocked_until||null,blockReason:n.block_reason||null,held:E,heldUntil:y,risky:!!n.risky,bumpable:M,bumpedAt:M&&_?b:null,holdable:$!=="done",hasNotes:!!(n.plan||String(n.notes||"").trim()),finishable:$!=="done",merging:Ce,refusal:v,manualSteps:n.manual_steps||[],fromAsk:n.from_ask||null,unfiled:!!n.unfiled,filedHeld:!!n.filed_held,next:Fe||$==="done"||T==="checking"?null:g&&Se?Rn:qe?_n:Qt||Kt||n.next||Gt||null,rebaseFailed:Ht,branch:n.branch||I?.headRefName||z?.branch||null,pushed:$==="done"||ae?null:z,mergeReady:$==="review"?J:null,stale:$!=="done"&&(We||!!p?.stale),stalled:n.stalled?.reason||null,landStopped:$==="review"&&J&&n.merge_ready.stopped&&String(n.merge_ready.stopped)>String(n.merge_ready.tapped||"")?{words:At("land"),at:n.merge_ready.stopped}:null,ticket:G?.source==="linear"&&!n.ticket&&n.linear?.length?null:Sn(G,n.ticket,{repo:a}),ticketSource:G?.source||null,linear:(n.linear||[]).map(k=>G?.source==="linear"&&pt(G,k,{repo:a})||yt(k,G)),pr:K[te]||null,prs:K,reviewers:xe.map(k=>k.name),review:En(xe,I||r[te]?.kept,Pe),inReviewSince:ne,threadCount:O.length+A.length,lastTs:Ue,othersAt:je,grilling:Bt("grill",A,f,D),prototyping:Bt("prototype",A,f,D),storedPhase:n.phase||null,phase:null,recheckAt:n.recheck_at||null,updatedAt:I?.mergedAt||I?.updatedAt||r[te]?.merged_at||p?.at||Ue||null,ask:null,fold:null,action:null,install:$==="done"?null:Ln(n.install,I),waitFacts:Nn(n,{agents:f,thread:O,events:A,refusedAt:v?Z.ts:null,replyAt:g?Te:null})}}d(Dn,"workItem");function Nn(e,{agents:t,thread:n,events:s,refusedAt:r,replyAt:a}){const u=new Set(n.filter(f=>f.kind===De.answer).map(f=>f.ask)),i=n.filter(f=>f.kind===De.ask&&!u.has(f.ask)).sort((f,c)=>String(f.ts).localeCompare(String(c.ts)))[0],p=d(f=>s.filter(c=>at(c,f)).pop()?.ts||null,"requestAt"),m=n.filter(f=>(f.who||"claude")==="claude"&&!["status","agent","command",$e].includes(f.kind)&&f.line!==ht).map(f=>f.ts);return{handedBackAt:st(t),next:e.next||null,ask:i?{at:i.ts,text:i.text}:null,unasked:n.filter(f=>f.line==="planner-unasked").sort((f,c)=>String(f.ts).localeCompare(String(c.ts))).map(f=>({at:f.ts,kind:f.detail})).pop()||null,grillAt:p("grill"),prototypeAt:p("prototype"),refusedAt:r,replyAt:a,unblockedAt:e.unblocked_at||null,unverified:t.some(f=>f.calls.some(c=>!f.verified||String(c.at)>String(f.verified.at))),writes:[...m,...t.flatMap(f=>[f.verified?.at,f.started])].filter(Boolean).map(String).sort()}}d(Nn,"waitFactsOf");function Ln(e,t){if(!e?.url)return null;const n=e.sha?String(e.sha).toLowerCase():null,s=t?.headRefOid?String(t.headRefOid).toLowerCase():null;return{url:e.url,label:e.label||null,builtAt:e.built_at||null,sha:n,older:!!(n&&s&&!s.startsWith(n))}}d(Ln,"installOf");function Tn(e,{needsApproval:t=!0}={}){if(e?.state!=="OPEN")return[`PR is ${String(e?.state||"unknown").toLowerCase()}`];const{state:n,pending:s,failing:r}=e.checks||{};return[e.isDraft&&"it is a draft",!xt(e,t)&&`review: ${String(e.reviewDecision||"none").toLowerCase().replace(/_/g," ")}`,n==="fail"&&`failing: ${r?.length?r.join(", "):"a check"}`,n==="pending"&&`pending: ${s?oe(s,"check"):"checks"}`,e.mergeStateStatus!=="CLEAN"&&`merge state: ${String(e.mergeStateStatus||"unknown").toLowerCase()}`].filter(Boolean)}d(Tn,"mergeBlockers");const Pn=d((e,t)=>!Tn(e,{needsApproval:t}).length,"mergeable");function Un(e,t,n){if(pe(e))return null;const s=`#selectedCard=${encodeURIComponent(e.key)}`;if(e.stalled)return{kind:q.stalled,url:s,label:`stalled: ${e.stalled}`};if(jn(e)){const r=t.get(e.questions.first);return Ne.test(r?.url||"")?{kind:q.pick,url:`questions.html#${r.id}`,label:"Prototypes ready: pick a take"}:{kind:q.questions,url:`questions.html#${e.questions.first}`,label:`Answer ${oe(e.questions.open,"question")}`}}return e.waitsOn==="ready"?{kind:q.ready,url:e.pr.url,label:`Review draft PR #${e.pr.number}`}:e.waitsOn==="merge"&&e.manualSteps.length&&!e.mergeReady?{kind:q.steps,url:s,label:"Run the manual steps, then merge"}:e.waitsOn==="merge"&&(!n||e.mergeReady)&&!e.merging&&e.prs.every(r=>["MERGED","CLOSED"].includes(r.state)||Pn(r,e.reviewers.length>0))?{kind:q.merge,url:s,label:"Merge"}:null}d(Un,"actionOf");const jn=d(e=>!!(e.questions&&e.questions.state!=="resolved"&&e.questions.open>0),"asking"),rt=new Set([q.pick,q.questions]);function Ot(e){return e.questions?ce.openQuestions:null}d(Ot,"actionButtonOf");const Wn=d(e=>e.kind==="work"&&e.lane==="todo"&&!e.action&&!e.unpicked,"buildableOf"),Fn=d((e,t)=>/^item:/.test(e.topic||"")&&Ne.test(e.url||"")&&!(e.options||[]).some(n=>n.id===t),"picksNoTake");function Kn(e,t){const n=e.filter(a=>a.topic===`item:${t}`),r=[...n.filter(a=>Ne.test(a.url||"")).flatMap(a=>a.answers.map(u=>({q:a,a:u,again:Fn(a,u.choice)}))),...n.filter(a=>a.request).map(a=>({q:a,a:a.request,again:!0}))].sort((a,u)=>String(a.a.ts).localeCompare(String(u.a.ts))).at(-1);return r?.again?{id:r.q.id,note:r.a.note.replace(/\s+/g," ").trim()}:null}d(Kn,"noTakePicked");const Gn="Prototype again",Qn=Object.freeze({[q.pick]:ce.openQuestions,[q.questions]:ce.openQuestions,[q.ready]:ce.github,[q.steps]:ce.github,[q.merge]:ce.merge,[q.stalled]:ce.reply}),Hn="Merges once unblocked",Vn=d(e=>`Blocked ${[e.blockedBy.length&&`by ${e.blockedBy.map(t=>t.ref).join(", ")}`,e.blockedUntil&&`until ${e.blockedUntil}`].filter(Boolean).join(" and ")}`,"mergeBlockedWords");function Xn(e,t,n){if(e.action=Un(e,t,n),e.mergeBlocked=e.action?.kind===q.merge&&e.blocked?Vn(e):null,e.mergeBlocked&&Object.assign(e,{action:null,next:Hn}),e.action?.kind===q.questions&&e.questions.fromAsks){Object.assign(e,{action:null,waitsOn:"owner",next:e.action.label,actionButton:Ot(e),resolvedBy:null});return}e.awaitsDone&&=!e.action,e.awaitsDone||Object.assign(e,{ownerStep:null,ownerCheck:null}),e.actionButton=Ot(e),e.resolvedBy=Qn[e.action?.kind]||null,e.action?.kind===q.merge&&!e.mergeReady&&(e.next=(e.pr?.reviewDecision==="APPROVED"?Ve:ot).ready),e.awaitsDone&&Object.assign(e,{lane:"review",next:e.ownerStep||Mn,bumpable:!1,bumpedAt:null});const s=e.lane==="todo"||e.waitsOn==="ready"||e.lane==="progress"&&e.waitsOn==="owner";if(!e.action||!s||e.stalled)return;const r=e.lane==="todo"&&rt.has(e.action.kind)?"todo":"review";Object.assign(e,{lane:r,waitsOn:e.waitsOn==="ready"?"ready":"owner",next:e.action.label})}d(Xn,"placeOwnerWait");const Et=d((e,t)=>e?e!=="todo":t.some(H),"started"),$t="none set yet";function vo(e,t){const n=t.filter(s=>s?.started);return e.next||et(e)||e.pushed||e.held||e.lane==="done"||st(n)?!1:!n.some(s=>!s.done)&&Et(e.lane,n)}d(vo,"owesNext");const fe="visual",ge="helper",Yn=new Set(["grill","prototype",fe]),ee=d(e=>Yn.has(e.kind),"plans"),H=d(e=>!ee(e),"builds"),zn={grill:"grilling",prototype:"prototyping"},Jn=d((e,t)=>`${e} ${t} by orchestrator`,"orchestratorDid"),At=d(e=>Jn(e,"stopped"),"stoppedWords"),Zn={grill:"grill-more",prototype:"prototype-request"},at=d((e,t)=>e.kind===Zn[t]&&!e.data?.question,"isRequest");function Bt(e,t,n,s){if(n.some(a=>a.kind===e&&a.started&&!a.done))return!0;const r=t.filter(a=>at(a,e)).pop();return!!(r&&!r.reply&&!s.some(a=>String(a)>String(r.ts))&&!n.some(a=>a.kind===e&&a.done&&String(a.started)>=String(r.ts)))}d(Bt,"planningOf");const xo=30*6e4;function Oo(e,t){const n=[];for(const s of e.items){if(pe(s)||(s.agents||[]).some(a=>a.kind==="prototype"&&a.started&&!a.done))continue;const r=s.prototyping&&t.filter(a=>a.data?.key===s.key&&at(a,"prototype")).pop();r&&n.push({key:s.key,request:r.id,since:r.ts});for(const a of e.owner.filter(u=>u.topic===`item:${s.key}`&&u.state==="requested"))n.push({key:s.key,request:a.request.id,question:a.id,since:a.request.ts})}return n}d(Oo,"prototypeWaits");function lt(e={},t=!1){return Object.entries(e).filter(([n,s])=>s&&typeof s=="object"&&(t||!n.startsWith("past-"))).map(([n,s])=>({name:n,kind:_t(s.kind)||null,model:s.model||(s.kind==="opus"||s.kind==="sonnet"?s.kind:null),label:s.label||null,started:s.started||null,eta:s.done?null:s.eta||null,done:s.done||null,log:s.session?.log||null,pid:s.session?.pid??null,calls:Array.isArray(s.calls)?s.calls:[],verified:s.verified||null,stopped:s.stopped||null,handed_back:s.handed_back||null,...s.pause?{pause:s.pause}:{},...s.kind===ge?{writes:!!s.writes,requested:s.requested||null}:{},...s.waits?{waits:s.waits}:{}})).sort((n,s)=>String(n.started||"").localeCompare(String(s.started||""))||n.name.localeCompare(s.name))}d(lt,"agentsOf");function it(e={}){return Object.values(e).filter(t=>t?.worktree?.path).sort((t,n)=>String(t.started||"").localeCompare(String(n.started||""))).at(-1)?.worktree.path??null}d(it,"worktreeOf");function es(e,t=[]){const n=e.filter(H).map(s=>String(s.started||"")).filter(Boolean).sort().pop();return n===void 0?null:t.filter(s=>s.kind==="status"&&s.phase&&!s.planner&&String(s.ts)>=n).sort((s,r)=>String(s.ts).localeCompare(String(r.ts))).pop()?.phase||null}d(es,"reachedPhaseOf");function ts(e,t=[],n,s=null,r=null,a=!1){const u=e.filter(b=>b.started&&!b.done&&b.kind!==fe),i=u.every(ee),p=i?u:u.filter(H),m=p.map(b=>String(b.started||"")).sort()[0];if(m===void 0)return null;const f=t.filter(b=>b.kind==="status"&&String(b.ts)>=m&&(i||!b.planner)).sort((b,C)=>String(b.ts).localeCompare(String(C.ts))).pop(),c=p.filter(b=>b.kind!==ge).at(-1),h=p.some(b=>b.kind===ue.install)?ue.install:c?_t(c.kind):"claude",O=i?null:p.find(b=>b.stopped&&String(b.stopped.at)>=String(b.started))?.stopped,A=f?.ts||m,S=O?O.at:s!==null&&s>Date.parse(A)?new Date(s).toISOString():A,P=f?f.phase||f.planner?f.text.replace(": "," \xB7 "):f.text||null:i?zn[h]:null,D=n-Date.parse(S),U=i?null:p.find(b=>b.pause)?.pause??null,N=!i&&!O&&(r==="held"||!!U?.paused),j=i||N?null:O?!1:r,w=N?!1:j===null?D>=mn:!j;return{progress:P,phase:f?.phase||null,at:A,kind:h,planner:i,silentSince:S,stale:w,alive:j,held:N,quiet:!!U||j===!0&&!a&&D>=hn,stopped:O?At(O.run):null,...U?{pause:{cause:U.cause,words:U.words,paused:!!U.paused}}:{}}}d(ts,"builderOf");const Eo={blocked:["nothing blocks it","nothing blocks it"],held:["you unhold it","unheld"],usage:["the usage pause lifts","the usage pause lifted"],bumped:["a slot frees","a slot freed"]},ns=15*6e4,ss=d(e=>(e.agents||[]).find(t=>t.name==="main"&&t.started&&!t.done&&H(t))??null,"mainBuild"),os=d((e,t,n,{now:s,usageHeld:r})=>e.blocked||e.held||r(n??"claude")||!!(t.until&&s<Date.parse(t.until)),"stillPaused");function rs(e,{now:t=Date.now(),usageHeld:n}){const s={ask:[],drop:[],stop:[],resume:[]},r=[];for(const a of e){if(a.kind!=="work"||a.lane!=="progress")continue;const u=ss(a);if(!u)continue;const{pause:i}=u;if(!i){a.builder?.phase!=="landing"&&n(u.kind??"claude")&&s.ask.push(a.key);continue}const p=os(a,i,u.kind,{now:t,usageHeld:n});if(i.paused){p||r.push({key:a.key,at:i.paused});continue}!p&&i.cause!=="bumped"?s.drop.push(a.key):t-Date.parse(i.at)>=ns&&s.stop.push(a.key)}return s.resume=r.sort((a,u)=>String(a.at).localeCompare(String(u.at))).map(a=>a.key),s}d(rs,"pauseWork");function $o(e,t,n){const s=rs(t,n).drop;return e.filter(r=>s.includes(r))}d($o,"confirmedDrops");const dt={opus:"Claude Opus",sonnet:"Claude Sonnet",codex:"Codex"},as=d(({model:e,kind:t})=>dt[e]||dt[t]||(t==="claude"?dt.opus:null),"modelName"),_t=d(e=>e==="opus"||e==="sonnet"?"claude":e,"providerOf");function Ae(e){const t=as(e),{kind:n}=e,s=e.label?.replace(/\bbuilder\b/,"implementer").replace(/\bBuilder\b/,"Implementer"),r=d(u=>u[0].toLowerCase()+u.slice(1),"lower");if(t&&ee(e))return`${t} ${n}`;if(t)return`${t} ${r(s?.replace(/^(?:claude\s+)?(?:opus|sonnet|codex)\b\s*/i,"")||"implementer")}`;const a=n?n[0].toUpperCase()+n.slice(1):"";return a?s?s.toLowerCase().includes(n.toLowerCase())?s:`${a} ${r(s)}`:a:s||"Implementer"}d(Ae,"agentName");const Rt=d((e,t)=>!!e.resolved&&(!e.resolved_at||String(e.resolved_at)>=String(t)),"resolvedSince");function ls(e,t,n=[],s=[],r=[]){const a=t.filter(S=>S.data?.id===e.id),u=a[a.length-1]||null,i=r.filter(S=>S.data?.question===e.id).at(-1),p=d((S,...P)=>!!S&&P.every(D=>String(S.ts)>String(D||"")),"after"),m=!!(e.resolved&&e.resolved_at&&u&&String(u.ts)>e.resolved_at),f=n.filter(S=>S.data?.question===e.id&&p(S,u?.ts,i?.ts)).at(-1),c=p(i,u?.ts,f?.ts,e.resolved_at),h=e.resolved&&!m&&!c,O=h?s.filter(S=>S.data?.question===e.id&&String(S.ts)>String(e.resolved_at||"")).at(-1):null,A=c?"open":h?"resolved":f?"requested":u?u.reply?"replied":"answered":"open";return{...e,state:A,reopened:c,request:f?{id:f.id,ts:f.ts,note:f.data.note||"",choice:f.data.choice||null}:null,reask:O?{id:O.id,ts:O.ts}:null,answers:a.map(S=>({ts:S.ts,choice:S.data.choice||null,note:S.data.note||"",files:S.data.files||[],reply:S.reply,replies:S.replies,acted:Rt(e,S.ts)}))}}d(ls,"ownerQuestion");const ke=d(e=>e.round??(Ne.test(e.url||"")?1:0),"roundOf");function is(e,t){const n=d((s,r)=>s.confirm_of||(s.options||[]).some(a=>a.id===r.choice),"settles");return e.filter(s=>ke(s)===t&&s.answers.length).map(s=>[s,s.answers.at(-1)]).filter(([s,r])=>n(s,r)).map(([,s])=>s).sort((s,r)=>String(s.ts).localeCompare(String(r.ts))).at(-1)}d(is,"roundSettledBy");function ds(e){const t=e.find(r=>r.request&&!e.some(a=>ke(a)>ke(r)));if(t)return{n:ke(t)+1,state:"making"};const n=Math.max(0,...e.map(ke));if(!n)return null;const s=is(e,n);return s?{n,state:"closed",take:s.choice}:e.some(r=>ke(r)===n&&r.state!=="resolved")?{n,state:"back"}:null}d(ds,"roundState");function cs(e){const t=e.filter(n=>Ne.test(n.url||"")).map(n=>({n:ke(n),q:n.id,url:n.url,confirm:!!n.confirm_of,state:n.state})).sort((n,s)=>n.n-s.n);return t.length?{open:t.some(n=>n.state==="open"),rounds:t}:null}d(cs,"prototypesOf");const Be=d((e,t,n)=>{const s=e.get(t);s?s.push(n):e.set(t,[n])},"add"),_e=d((e,t)=>{const n=new Map;for(const s of e)Be(n,t(s),s);return n},"groupBy");function us(e,t,n=new Set){const s=new Set(e.map(u=>u.id)),r=[...e];t.some(u=>!s.has(u.topic))&&r.push({id:"",title:"Other"});const a=_e(t,u=>s.has(u.topic)?u.topic:"");return r.map(u=>{const i=a.get(u.id)||[],p=d(c=>i.filter(h=>h.state===c).length,"count"),m=i.every(c=>c.state==="resolved")||n.has(u.id)&&!p("open"),f=i.length&&m?"resolved":"open";return{...u,state:f,questions:i.map(c=>c.id),open:p("open"),answered:p("answered"),replied:p("replied"),resolved:p("resolved"),round:ds(i)}}).filter(u=>u.questions.length)}d(us,"ownerTopics");const Xe={implement:"Implementation requested",prototype:"Prototype requested",grill:"Grill requested",visual:"Visual requested",reask:"Re-ask requested",hold:"Held",resume:"Resumed",done:"Marked done",staged:"Marked as staging",readied:"Marked as ready",related:"Related to",unrelated:"Unrelated from",followup:"Follow-up","followup-of":"Follow-up of"},Mt={"grill-more":"grill","prototype-request":"prototype","visual-request":"visual","reask-request":"reask"},It={"mark-ready":"Mark ready",...Object.fromEntries(Object.entries(Mt).map(([e,t])=>[e,Xe[t]]))},ps=["grill-more","prototype-request","visual-request"],fs="answers submitted",qt={reply:"Reply",build:"Implement"},gs=d(e=>qt[e.data.intent],"intentLabel"),ks=d((e,t)=>{if(e.kind!=="prototype-request"||!t.has(e.data.question))return e.data.text||(e.data.files?.length?"":R(It,e.kind))||"";const n=t.get(e.data.question),s=(n.options||[]).find(r=>r.id===e.data.choice);return[Dt(t,n),s&&`${s.id}. ${s.label}`,e.data.note].filter(Boolean).join(`
2
+ \u2192 `)},"actionText"),Ct=d((e,t)=>[...e.keys()].indexOf(t)+1,"numberOf"),Dt=d((e,t)=>`Question ${Ct(e,t.id)} \xB7 ${t.question}`,"numbered"),ms="question reopened",Ye=d(e=>e.data?.shared_by?.login?{by:e.data.shared_by.name||e.data.shared_by.login}:{},"byOf"),hs=d((e,t)=>{const n=t.get(e.card);return{...e,text:`${Xe[e.command]} ${n?`${n.ref}: ${n.title}`:e.card}`,link:`#selectedCard=${encodeURIComponent(e.card)}`}},"linkLine"),Nt=d(e=>({kind:"command",command:e,request:!0,mark:Xe[e]}),"commandLine"),ws=d((e,t)=>{if(e.kind==="item-message")return qt[e.data.intent]?{status:gs(e)}:{};const n={request:!0,command:Mt[e.kind]};return ps.includes(e.kind)?(e.data.question?t.has(e.data.question):String(e.data.text||"").trim()||e.data.files?.length)?{status:It[e.kind]}:n:e.kind==="reask-request"?n:{}},"requestOf"),Lt=d(e=>[...e.filter(t=>t.who==="claude"&&!["status","agent","command",$e,De.answer].includes(t.kind)).map(t=>t.ts),...e.filter(t=>t.kind==="ask").map(t=>t.reply?.at)].filter(Boolean).map(String).sort().pop()||"","claudeAtOf");function Tt(e,t={}){const n=new Map,s=new Map;for(const c of t.items||[])Be(n,c.topic,c),s.set(c.id,c.topic);const r=new Map,a=new Map,u=new Map,i=new Map,p=new Map,m=new Map,f=new Map;e.forEach((c,h)=>{f.set(c,h),c.data?.key&&kn.includes(c.kind)&&Be(r,c.data.key,c),c.kind==="owner-answer"&&s.has(c.data?.id)&&Be(a,s.get(c.data.id),c),c.kind===mt&&s.has(c.data?.question)&&Be(u,s.get(c.data.question),c),c.kind==="ask"&&i.set(c.id,c)});for(const c of e.filter(h=>h.kind==="ask-edited").sort((h,O)=>String(h.ts).localeCompare(String(O.ts)))){p.set(c.data?.id,c.data?.text);for(const h of c.data?.files||[])Be(m,c.data?.id,h)}return{byKey:r,answers:a,reopens:u,asks:i,edits:p,editFiles:m,questions:n,order:f}}d(Tt,"threadIndex");const ct=d((e,t)=>{const n=t.filter(s=>s?.length);return n.length>1?n.flat().sort((s,r)=>e.order.get(s)-e.order.get(r)):[...n[0]||[]]},"inLogOrder"),ys=/; its log is \S*\/\.claude\/kickflip\/logs\/([^/\s]+\.log)\.$/,bs=d(e=>{const t=!e.log&&typeof e.text=="string"?ys.exec(e.text):null;return t?{...e,text:`${e.text.slice(0,t.index)}.`,log:t[1]}:e},"withLog");function Ss(e,t){const n=t.run?e.find(s=>s.started===t.run):e.filter(s=>ee(s)===!!t.planner&&s.started&&String(s.started)<=String(t.ts)&&(!s.done||String(t.ts)<=String(s.done))).pop();return n?{agent:Ae(n),run:ut(e,n),running:!n.done}:{}}d(Ss,"agentOfLine");function ut(e,t){return t.label!==He?t.started:e.filter(n=>H(n)&&n.label!==He&&n.started&&String(n.started)<String(t.started)).pop()?.started??t.started}d(ut,"sectionOf");function vs(e,{lines:t=[],events:n=[],owner:s={},working:r={},fromAsk:a=null,sorted:u=!1,links:i=[],agents:p={},moved:m=[],cards:f=new Map,index:c=Tt(n,s)}){const h=e.startsWith("ask:")?[e.slice(4)]:[a,...m].filter(Boolean),O=[...new Set([e,...h.map(l=>`ask:${l}`)])],A=new Map((c.questions.get(`item:${e}`)||[]).map(l=>[l.id,l])),S=c.answers.get(`item:${e}`)||[],P=new Set([...A.values()].map(l=>l.round_of).filter(Boolean)),D=lt(p,!0).filter(H).map(l=>String(l.started||"")).filter(Boolean),U=new Set(S.filter(l=>Rt(A.get(l.data.id),l.ts)||P.has(l.data.id)||D.some(B=>B>String(l.ts))).map(l=>l.id)),N=lt(p,!0),j=[...N.flatMap(l=>[...l.started?[{ts:l.started,who:"claude",kind:"agent",text:`${Ae(l)} started`,agent:Ae(l),run:ut(N,l),finished:!1,eta:l.done?null:l.eta,...l.log?{log:l.log}:{},...ee(l)?{planner:!0}:{}}]:[],...l.done?[{ts:l.done,who:"claude",kind:"agent",text:`${Ae(l)} finished`,agent:Ae(l),run:ut(N,l),finished:!0,eta:null,...ee(l)?{planner:!0}:{}}]:[],...l.calls.length?[{ts:l.done||l.calls.at(-1).at,who:"claude",kind:"calls",text:Ae(l),calls:l.calls.map(B=>B.text),verified:l.verified}]:[]]),...t.map(bs).map(l=>l.card?hs(l,f):l).map(l=>({ts:l.ts,who:l.who||"claude",...l.log?{log:l.log}:{},...l.kind==="status"?{kind:"status",...l.planner?{planner:!0}:{},...Ss(N,l)}:{},...l.kind===fe||l.kind===$e?{kind:l.kind}:{},...l.kind===Qe?{kind:Qe,line:l.line,...gt(l),...l.detail?{detail:l.detail}:{}}:{},...l.command?Nt(l.command):{},...l.card?{card:l.card}:{},...Object.values(De).includes(l.kind)?{kind:l.kind,ask:l.ask}:{},text:l.text||"",html:l.html||null,link:l.link||null,via:l.via||null,...l.by?{by:l.by}:{}})),...ct(c,O.map(l=>c.byKey.get(l))).map(l=>({ts:l.ts,who:"love",via:"site",kind:l.kind,text:ks(l,A),files:l.data.files||[],eventId:l.id,reply:l.reply,replies:l.replies,...ws(l,A),...Ye(l)})),...S.map(l=>{const B=[l.data.label||l.data.choice,l.data.note].filter(Boolean).join(" \xB7 ");return{ts:l.ts,who:"love",via:"questions",kind:l.kind,text:`${Dt(A,A.get(l.data.id))}
3
+ \u2192 ${B}`,files:l.data.files||[],eventId:l.id,reply:l.reply,replies:l.replies,status:fs,...Ye(l)}}),...(c.reopens.get(`item:${e}`)||[]).map(l=>({ts:l.ts,who:"love",kind:l.kind,request:!0,mark:ms,text:`Question ${Ct(A,l.data.question)} was reopened`,...Ye(l)})),...ct(c,[...new Set(h)].map(l=>c.asks.has(l)?[c.asks.get(l)]:[])).flatMap(l=>[{ts:l.ts,who:"love",via:"ask",kind:l.kind,text:l.data?.text||"",files:l.data?.files||[],eventId:l.id,reply:l.reply,replies:l.replies,...m.includes(l.id)?{moved:!0}:{},...Ye(l),...typeof l.data?.from?.title=="string"?{from:l.data.from.title}:{}},...l.data?.quick===!0?[{ts:l.ts,who:"love",...Nt("implement"),text:Xe.implement}]:[],...i.map(B=>({ts:l.ts,who:"claude",kind:"ask-link",text:B.folded?`Moved to ${B.ref}: ${B.title}`:`${B.became?"Became":"Added to"} ${B.ref}`,link:`#selectedCard=${encodeURIComponent(B.key)}`,html:null,via:null}))])].sort((l,B)=>String(l.ts).localeCompare(String(B.ts))),w=Lt(j),{edits:b,editFiles:C}=c;for(const l of j){if(!l.eventId)continue;(l.kind==="ask"||l.kind==="item-message")&&typeof b.get(l.eventId)=="string"&&Object.assign(l,{text:b.get(l.eventId),edited:!0,files:[...l.files,...C.get(l.eventId)||[]]});const B=l.kind==="ask"&&(i.length>0||!!l.moved||u&&l.eventId===a);l.state=l.reply?"replied":B||U.has(l.eventId)||w>String(l.ts)?"answered":R(r,l.eventId)?"working":"waiting",l.canDelete=l.kind==="item-message"&&(l.state==="working"||l.state==="waiting"),l.canEdit=l.canDelete}return xs(j)}d(vs,"threadOf");const Pt=["waiting","working","answered","replied"];function xs(e){const t=new Map;for(const n of e)n.kind==="owner-answer"&&t.set(n.ts,[...t.get(n.ts)||[],n]);return e.flatMap(n=>{if(n.kind!=="owner-answer")return[n];const s=t.get(n.ts);return s.length===1?[n]:s[0]!==n?[]:[{...n,text:s.map(r=>r.text).join(`
4
4
 
5
- `),files:s.flatMap(r=>r.files),state:s.map(r=>r.state).sort((r,a)=>Pt.indexOf(r)-Pt.indexOf(a))[0],reply:void 0,answers:s.map(r=>({eventId:r.eventId,state:r.state,reply:r.reply,replies:r.replies,text:r.text}))}]})}d(xs,"foldSubmits");const Ut=d(e=>(e.answers||[e]).filter(t=>t.eventId).map(t=>[t.eventId,t.state]),"stateEntries");function Os(e,t,n,s,r,a){const c=d((p,h)=>{const v=a.filter(x=>x.kind==="wave-triage"&&x.data?.[p]===h).pop(),_=r.filter(x=>x.topic===`${p}:${h}`&&(x.state==="open"||x.state==="answered")).length;return{running:!!(v&&!v.reply),questions:_}},"triageOf"),l=new Map(s.map(p=>[p.key,p])),u=d(p=>p.filter(h=>h.lane==="done"&&!St(h)).length,"merged"),m=n.map(p=>({l:p,cards:p.keys.map(h=>l.get(h)).filter(Boolean)})).map(p=>({...p,open:!p.l.finished_at&&p.cards.some(h=>h.lane!=="done")})).sort((p,h)=>Number(p.open)-Number(h.open));for(const{l:p,cards:h}of m)for(const v of h)v.wave={slug:p.slug,title:p.title};const f=m.filter(p=>p.open).map(({l:p,cards:h})=>({kind:"wave",slug:p.slug,title:p.title,goal:p.goal,keys:h.map(v=>v.key),next:Es(h),merged:u(h),total:h.length,triage:c("wave",p.slug)}));return e.fromWaves?[...f,...t.filter(p=>!p.finished).map(p=>({w:p,cards:s.filter(h=>h.file===p.slug&&h.kind!=="ask")})).filter(({cards:p})=>p.length).map(({w:p,cards:h})=>({kind:"file",slug:p.slug,title:p.title,goal:null,keys:h.map(v=>v.key),next:null,merged:u(h),total:h.length,triage:c("file",p.slug)}))]:f}d(Os,"waveBandsOf");function Es(e){const t=e.filter(a=>a.lane==="todo"),n=t.find(a=>a.ready&&a.buildable)||t[0];if(!n)return null;const s=n.ready&&n.buildable,r=s?"ready":n.held?"held":n.blockedBy.length?`waits on ${n.blockedBy.map(a=>a.ref).join(", ")}`:n.blockedUntil?`blocked until ${n.blockedUntil}`:n.action?"needs you":n.phase==="new"?"not ready":n.phase;return{key:n.key,ref:n.ref,ready:s,word:r}}d(Es,"nextOf");const $s=d(e=>e.items||e.phases.flat(),"waveItems");function Ao(e){const{owner:t={},topicItems:n={},events:s=[],threads:r={},prs:a={},working:c={}}=e,l=e.now??Date.now(),u=Tt(s,t),m=new Set(s.filter(o=>o.kind==="ask-withdrawn").map(o=>o.data?.id)),f=new Map(s.filter(o=>o.kind==="ask"&&!m.has(o.id)).map(o=>[o.id,o])),p=d((o,g)=>new Map(s.filter(E=>E.kind===o).sort((E,$)=>String(E.ts).localeCompare(String($.ts))).map(E=>[E.data?.[g],E.ts])),"latest"),h=p("card-unbump","key"),v=new Map([...p("card-bump","key")].filter(([o,g])=>!(String(h.get(o)||"")>String(g)))),_=p("card-done","key"),x=p("card-merge","key"),P=e.autoMerge===!0,C=e.tickets,L=new Map(s.filter(o=>o.kind==="card-hold").sort((o,g)=>String(o.ts).localeCompare(String(g.ts))).map(o=>[o.data?.key,o])),T=new Map,W=new Map(e.files.flatMap(({slug:o,itemFile:g})=>g.items.map(E=>[`${o}/${E.id}`,{ref:nt(g,E,C),title:E.title}]))),w=e.askLinks||{},b=new Map;for(const[o,g]of Object.entries(w))f.has(o)&&b.set(g,[...b.get(g)||[],o]);const i=[],S=[],oe=new Map,me=new Map,he=new Map,K=new Map,F=e.github?null:tn(e.remote);for(const{slug:o,itemFile:g,agents:E={}}of e.files){const $=e.github||g.repo||null;i.push({slug:o,title:g.title||o,status:g.status||null,internal:!!g.internal,finished:!1,reviewers:(g.reviewers||[]).map(y=>y.name)}),g.items.forEach((y,M)=>{const B=`${o}/${y.id}`,Ke=(b.get(B)||[]).filter(O=>O!==y.from_ask),le=[y.from_ask,...Ke].filter(O=>f.has(O)),Ce=new Set(le.map(O=>`ask:${O}`)),Ee=[...R(r,B)||[],...le.flatMap(O=>R(r,`ask:${O}`)||[])];y.from_ask&&T.set(y.from_ask,[...T.get(y.from_ask)||[],{key:B,ref:nt(g,y,C),became:!0}]);const ne=vs(B,{lines:Ee,events:s,owner:t,working:c,fromAsk:y.from_ask||null,moved:Ke,cards:W,index:u});for(const O of ne)for(const[ie,Ge]of Ut(O))oe.set(ie,Ge);me.set(B,Lt(ne)),S.push(Dn({slug:o,itemFile:g,it:y,order:M,repo:$,remote:F,base:e.branch||"main",prs:rn(y,$).map(O=>({...O,gh:R(a,ln(O))||null})),...(O=>({agents:O,builder:ts(O,R(r,B),l,e.moved?.[it(R(E,y.id))]??null,R(e.alive,B)??null,!!e.commandRunning?.[it(R(E,y.id))]),reachedPhase:es(O,R(r,B)),eta:O.map(ie=>ie.eta).filter(Boolean).sort().pop()||null}))(lt(R(E,y.id))),worktree:it(R(E,y.id))??y.worktree?.path??null,thread:Ee,events:[...ct(u,[B,...Ce].map(O=>u.byKey.get(O))),...le.map(O=>f.get(O))],working:ne.some(O=>Ut(O).some(([,ie])=>ie==="working")),said:ne.filter(O=>O.who==="love"&&(O.kind==="item-message"||O.kind==="owner-answer")),askedAt:(u.questions.get(`item:${B}`)||[]).filter(O=>O.asked_at).map(O=>O.asked_at),topicItems:n,prSlack:e.prSlack||{},boardReviewers:e.reviewers||[],now:l,bumpedAt:v.get(B),holdAsk:L.get(B),closedAt:_.get(B),mergeAt:x.get(B),autoMerge:P,skipDraft:e.skipDraft===!0,tickets:C}));const J=S[S.length-1];f.has(y.from_ask)&&(J.created=f.get(y.from_ask).ts),J.askMsg=ne.find(O=>O.kind==="ask"&&O.eventId===y.from_ask)||null,K.set(B,{parent:y.parent?Wt(o,y.parent):null,related:(y.related||[]).map(O=>Wt(o,O))}),J.lane==="done"&&he.set(B,J.closedAt||String(J.updatedAt||""))})}const ee=new Map(S.map(o=>[o.key,o])),I=new Map,G=e.try||{},ze=e.autoBuild===!0,U=["web","app"].includes(G.kind)?G.kind:e.files.some(({itemFile:o})=>o.items.some(g=>g.install?.url))?"app":"web";for(const o of S){if(o.kind!=="work"||o.lane==="done")continue;const g=R(G.cards,o.key)||{},E=g.head||o.pr?.headRefOid,$=U==="web"?g.sha:o.install?.sha,y=o.builder?.kind===ue.install?"running":g.state||(U==="app"&&o.install?"ready":null);o.try={kind:U,state:y,stale:!!($&&E&&!String(E).toLowerCase().startsWith(String($).toLowerCase())),url:U==="web"?null:o.install?.url||null,port:U==="web"&&y==="ready"&&g.https||null,sha:$||null,changes:g.ahead>0,script:g.script===!0,autoBuild:ze}}const D=G.main,we=D?.script&&D.sha?{slug:D.slug,branch:D.branch||"main",sha:D.sha.slice(0,7),at:D.at,kind:U,state:D.state||null,url:U==="web"?null:e.main?.install?.url||null,port:U==="web"&&(D.state==="ready"?D.https:D.serving?.https)||null,serving:U==="web"&&D.serving?{sha:D.serving.sha.slice(0,7),at:D.serving.at}:null,pull:G.pull?{running:G.pull.running===!0,error:G.pull.error||null}:null,script:pn(D.target)}:null,ye=[],be=[],Le=_e(s.filter(o=>o.kind==="owner-answer"),o=>o.data?.id),j=_e(s.filter(o=>o.kind==="prototype-request"),o=>o.data?.question),Re=_e(s.filter(o=>o.kind==="reask-request"),o=>o.data?.question),Je=_e(s.filter(o=>o.kind===mt),o=>o.data?.question),H=(t.items||[]).map(o=>ls(o,Le.get(o.id)||[],j.get(o.id)||[],Re.get(o.id)||[],Je.get(o.id)||[])),X=new Map(H.map(o=>[o.id,o])),Te=_e(H,o=>o.topic);for(const o of f.values()){const g=`ask:${o.id}`,E=R(r,g)||[],$=T.get(o.id)?.[0].key||R(w,o.id);if(!$){E.some(B=>B.line==="ask-moved")||be.push(o.id);continue}I.set(g,$);const y=E.filter(B=>B.line===fn).at(-1),M=ee.get($);if(y&&M){const B=y.title||String(u.edits.get(o.id)??o.data?.text??"").trim().split(`
6
- `)[0]||"Ask";ye.push({key:M.key,ref:M.ref,text:B,at:String(y.ts)})}}for(const o of i){const g=S.filter(E=>E.file===o.slug);if(o.finished=On(g),o.finished)for(const E of g)E.lane==="done"&&(E.fold=yn(o.slug))}for(const o of S)o.wave=null;const Se=new Set((t.topics||[]).map(o=>o.id)),ve=e.files.flatMap(({slug:o,itemFile:g})=>(g.waves||[]).map(E=>({...E,keys:$s(E).map($=>`${o}/${$}`)}))),xe=d(()=>us([...t.topics||[],...S.filter(o=>!Se.has(`item:${o.key}`)).map(o=>({id:`item:${o.key}`,card:o.key,title:`${o.ref}: ${o.title}`})),...ve.filter(o=>!Se.has(`wave:${o.slug}`)).map(o=>({id:`wave:${o.slug}`,title:o.title,summary:"Wave triage."})),...(e.fromWaves?i:[]).filter(o=>!Se.has(`file:${o.slug}`)).map(o=>({id:`file:${o.slug}`,title:o.title,summary:"Wave triage."}))],H,new Set(S.filter(pe).map(o=>`item:${o.key}`))),"topicsOf"),Pe=new Map(xe().map(o=>[o.id,o]));for(const o of S){const g=Pe.get(`item:${o.key}`);if(!g){o.questions=null,o.prototypes=null;continue}const E=g.questions.filter(M=>X.get(M)?.state!=="resolved"),$=g.questions.find(M=>X.get(M)?.state==="open")||E[0]||g.questions[0],y=g.questions.map(M=>X.get(M)).filter(M=>M?.state==="open");o.questions={topic:g.id,state:g.state,open:g.open,answered:g.answered,replied:g.replied,resolved:g.resolved,first:$,askedAt:y.map(M=>M.asked_at).filter(Boolean).sort(),fromAsks:y.length>0&&y.every(M=>M.ask)},o.prototypes=cs(g.questions.map(M=>X.get(M)))}const te=new Map(S.map(o=>[o.key,o])),Ue=new Map(ve.map(o=>[`wave:${o.slug}`,o.keys]));for(const o of S){const g=o.kind==="work"&&o.lane!=="done",E=new Set(o.deps.flatMap(y=>Ue.get(y)||[String(y).includes("/")?y:`${o.file}/${y}`]));o.blockedBy=g?[...E].map(y=>te.get(y)).filter(y=>y&&y.lane!=="done"&&!y.reopened).map(jt):[],o.blockedUntil=g?o.blockedUntil??null:null,o.blockReason=g?o.blockReason??null:null,o.blocked=o.blockedBy.length>0||!!o.blockedUntil,Xn(o,X,P),o.lane==="todo"&&o.kind==="work"&&!o.held&&Et(o.storedLane,o.agents)&&(o.lane="progress"),o.unpicked=o.kind==="work"&&o.lane==="todo"&&!!Kn(H,o.key),o.kind==="work"&&(o.phase=zs(o)),o.unpicked&&!o.action&&(o.next=Gn),o.buildable=Wn(o);const $=o.kind==="work"&&o.lane==="todo";if(o.unsorted=$&&(!!o.fromAsk||o.unfiled)&&!o.filedHeld&&o.phase==="new",o.kind==="work"){const y=o.askMsg;delete o.askMsg,o.unsorted&&y&&(o.isNew=!0,o.ask={eventId:y.eventId,state:y.state,askedAt:y.ts,edited:!!y.edited,canEdit:!0,text:y.text,files:y.files,from:y.from||null})}o.ready=$&&o.phase==="ready"&&!o.blocked&&!o.recheckAt&&!o.action,o.staging=$&&o.phase==="staging",o.kind==="work"&&(o.hostHeld=!!e.hostBusy&&o.ready&&!!o.bumpedAt&&!o.held)}_s(S,te,K);const je=new Map((t.items||[]).map(o=>[o.id,o.topic])),Y=d(o=>o.kind==="owner-answer"?je.has(o.data?.id)?/^item:(.+)$/.exec(je.get(o.data.id))?.[1]||null:"":o.kind==="ask"?`ask:${o.id}`:o.data?.key||(o.kind.startsWith("ask-")&&o.data?.id?`ask:${o.data.id}`:null),"ownCardOf"),z=d(o=>{const g=Y(o);return I.get(g)||g},"cardOf"),Me=d(o=>[_.get(o),he.get(o)].filter(Boolean).sort().pop()||"","closedAt"),Ie=d(o=>{const g=z(o);return g===null?!o.reply:!te.has(g)||!(Me(g)<String(R(c,o.id)))?!1:oe.has(o.id)?oe.get(o.id)==="working":!o.reply&&!I.has(Y(o))&&!(String(me.get(g)||"")>String(o.ts))},"unanswered"),re=s.filter(o=>R(c,o.id)&&Ie(o)).map(o=>({id:o.id,kind:o.kind,key:z(o),data:o.data,since:R(c,o.id)}));for(const o of S)o.working=re.some(g=>g.key===o.key),o.kind==="work"&&o.lane==="progress"&&!o.builder&&!o.next&&o.waitsOn!=="checking"&&!o.working&&(o.next=$t),o.sortAt=Rs[o.lane](o)||(o.lane==="review"?null:o.updatedAt)||null;for(const o of S){if(o.kind!=="work")continue;const g=(Te.get(`item:${o.key}`)||[]).filter($=>$.state==="answered").map($=>$.answers.at(-1).ts).sort().pop(),E=re.filter($=>$.key===o.key).map($=>String($.since)).sort()[0];o.orchestrator=!o.stalled&&(["progress","todo"].includes(o.lane)||o.waitsOn==="checking"||o.lane==="review"&&(o.stale||o.waitFacts.unblockedAt||o.waitFacts.replyAt))?so(o,o.waitFacts,{answeredAt:g,workingSince:E}):null,delete o.waitFacts}Gs(S,s,c,I),Qs(S,e.screening||{}),S.sort(gn);const Ze=Os(e,i,ve,S,H,s),A=xe(),We=nn({on:e.autoReview,incoming:e.incoming,records:e.reviewRecords,events:s,now:l,refs:new Map(S.filter(o=>o.kind==="work").map(o=>[o.key,o.ref]))}),ae=[...Is(S,A,H),...sn(We)],N=new Map(ae.filter(o=>o.questions).map((o,g)=>[o.topic||`item:${o.key}`,g])),Fe=d(o=>N.get(o.id)??N.size,"rankOf");A.sort((o,g)=>Fe(o)-Fe(g));const qe=new Set(ae.filter(o=>o.questions&&o.progress).map(o=>`item:${o.key}`));for(const o of A)qe.has(o.id)&&(o.progress=!0);const Oe=S.flatMap(o=>o.answered&&me.get(o.key)?[{key:o.key,id:o.id,text:o.title,at:me.get(o.key)}]:[]).sort((o,g)=>g.at.localeCompare(o.at));return ye.sort((o,g)=>g.at.localeCompare(o.at)),As({kind:U,main:we,remote:F&&{host:F.host,name:`${F.host}/${F.path}`},itemFiles:i,waveBands:Ze,items:S,moved:Object.fromEntries(I),answers:Oe,merges:ye,unfiled:be,owner:H,ownerTopics:A,reviews:We,needs:ae,working:re.length,workingOn:re,checks:Ms(e,l)},e)}d(Ao,"buildBoard");function As(e,{listen:t,prompts:n}={}){const s=[...Cs(n),...e.needs];return{...e,needs:s,needsSummary:Ds(s),checks:{...e.checks,watcher:{listening:"running",asleep:"asleep",waking:"waking"}[t]||"stopped"}}}d(As,"withHub");const jt=d(e=>({key:e.key,ref:e.ref}),"linkOf"),Wt=d((e,t)=>String(t).includes("/")?String(t):`${e}/${t}`,"linkKey"),Bs=d((e,t)=>e.ref.localeCompare(t.ref,void 0,{numeric:!0}),"byRef");function _s(e,t,n){const s=new Map,r=new Map,a=new Map,c=d((u,m,f)=>u.set(m,[...u.get(m)||[],f]),"add");for(const[u,m]of n){m.parent&&c(s,m.parent,u);for(const f of m.related)f!==u&&(c(r,u,f),c(r,f,u))}for(const u of e)for(const m of u.blockedBy||[])c(a,m.key,u.key);const l=d((u=[])=>[...new Set(u)].map(m=>t.get(m)).filter(Boolean).map(jt).sort(Bs),"cards");for(const u of e){const m=n.get(u.key)?.parent;u.parent=m?{key:m,ref:t.get(m)?.ref||m.slice(m.indexOf("/")+1)}:null,u.children=l(s.get(u.key)),u.related=l(r.get(u.key)),u.waitsOnIt=l(a.get(u.key))}}d(_s,"linkCards");const Rs={todo:d(e=>e.created,"todo"),progress:d(e=>e.progressSince,"progress"),review:d(e=>e.reviewSince,"review"),done:d(e=>e.closedAt,"done")},se=d((e,t)=>`${e} ${t}${e===1?"":"s"}`,"plural");function Ms({checked:e,watcher:t,replyAt:n,threads:s={},working:r={},files:a=[]},c){const l=Number(e?.every)||30,m=[n,t?.since,...Object.values(r),...Object.values(s).flat().filter(p=>(p.who||"claude")==="claude").map(p=>p.ts),...a.flatMap(p=>Object.values(p.agents||{}).flatMap(h=>Object.values(h||{})).map(h=>h?.done||h?.started))].filter(p=>typeof p=="string"&&!Number.isNaN(Date.parse(p))).reduce((p,h)=>p&&Date.parse(p)>Date.parse(h)?p:h,null),f=!!e?.idle;return{at:e?.at||null,every:f?null:l,idle:f,late:!f&&!!e?.at&&c-Date.parse(e.at)>2*l*6e4,claudeAt:m}}d(Ms,"checksOf");function Is(e,t,n){const s=new Set(e.map(l=>l.questions?.topic).filter(Boolean)),r=d(l=>l.find(u=>n.find(m=>m.id===u)?.state==="open")||l[0],"firstOpen"),a=t.filter(l=>l.state!=="resolved"&&l.open>0&&!s.has(l.id)).map(l=>{const u=`questions.html#${r(l.questions)}`,m=/^(?:wave|file):(.+)$/.exec(l.id)?.[1]||null;return{kind:"Question",title:l.title,text:`${l.title} \xB7 ${se(l.open,"question")}`,url:u,topic:l.id,band:m,questions:{open:l.open,url:u},ready:null,since:n.filter(f=>f.topic===l.id&&f.state==="open").map(f=>f.asked_at).filter(Boolean).sort()[0]||null}}),c=e.map(l=>{const u=l.questions&&l.questions.state!=="resolved"&&l.questions.open>0?{open:l.questions.open,url:`questions.html#${l.questions.first}`}:null,m=l.waitsOn==="ready"?l.pr.url:null,f=!u&&!m&&l.action?{label:l.action.label,url:l.action.url}:null;if(!u&&!m&&!f)return null;const p=`${l.ref}: ${l.title}`,h=[p,u&&se(u.open,"question"),m&&"Ready on GitHub",f?.label].filter(Boolean).join(" \xB7 ");return{kind:u?"Question":m?"Tap Ready":"Action needed",title:p,text:h,url:`#selectedCard=${encodeURIComponent(l.key)}`,key:l.key,questions:u,ready:m,action:f,since:l.created||null,progress:l.lane==="progress"}}).filter(Boolean);return[...a,...c].sort(qs)}d(Is,"needsOf");const qs=d((e,t)=>!!t.progress-!!e.progress||(e.since&&t.since?e.since.localeCompare(t.since):!!t.since-!!e.since),"oldestFirst");function Cs(e=[]){return e.map(t=>{const n=t.questions?.length?`Question: ${t.questions.map(s=>s.question).join(" \xB7 ")}`:`${t.tool}: ${t.detail||t.action}`;return{kind:"Prompt",title:n,text:n,url:"",prompt:t}})}d(Cs,"promptLines");function Ds(e){const t=e.filter(a=>a.prompt).length,n=e.reduce((a,c)=>a+(c.questions?.open||0),0),s=e.filter(a=>a.ready).length,r=e.filter(a=>a.review).length;return[t&&se(t,"prompt"),n&&se(n,"question"),s&&`${se(s,"draft")} waiting on Ready`,r&&`${se(r,"review")} held`].filter(Boolean).join(" \xB7 ")}d(Ds,"needsSummary");function Bo(e){const t=d((...n)=>e.items.filter(s=>n.includes(s.lane)).length,"count");return{items:e.needs.map(({kind:n,text:s,url:r})=>({kind:n,text:s,url:r})),working:e.working,summary:e.needsSummary,lanes:{review:t("review"),progress:t("progress"),todo:t("todo")},building:e.items.some(n=>(n.agents||[]).some(s=>!s.done))}}d(Bo,"needsAnswer");const Ns=d(e=>e.buildable&&e.phase==="ready"&&!e.held&&!e.blocked&&!e.recheckAt,"pullableCard"),Ls=10;function Ts(e,{cap:t=Ls,pulled:n=[],hold:s=[],taken:r=0}={}){const a=e.filter(w=>!pe(w)),c=d((w,b)=>w.started&&!w.done&&Q(w)&&!(w.kind===ge&&!w.writes)&&!w.pause?.paused&&!(w.waits&&b.some(i=>i.name===w.waits&&!i.done)),"counts"),l=a.filter(w=>!w.stalled).reduce((w,b)=>w+(b.agents||[]).filter((i,S,oe)=>c(i,oe)).length,0),u=a.filter(w=>Ns(w)&&!w.working).map(w=>w.key),m=a.filter(w=>n.includes(w.key)&&(u.includes(w.key)||w.buildable&&w.working)).map(w=>w.key),f=a.flatMap(w=>(w.agents||[]).filter(b=>b.kind===ge&&!b.started&&!b.done).map(b=>({key:w.key,name:b.name,writes:b.writes}))),p=Math.max(0,t-l-m.length-r),h=f.filter(w=>w.writes),v=[...f.filter(w=>!w.writes),...h.slice(0,p)].map(({key:w,name:b})=>({key:w,name:b})),_=Math.max(0,p-h.length),x=new Set(a.filter(w=>w.bumpedAt).map(w=>w.key)),P=u.filter(w=>!m.includes(w)&&!s.includes(w)),C=P.filter(w=>x.has(w)),L=[...C,...P.filter(w=>!x.has(w)).slice(0,Math.max(0,_-C.length))],T=C.slice(_),W=Ps(a,c).slice(0,T.length).map((w,b)=>({key:w,by:T[b]}));return{idle:!l&&!a.some(w=>w.waitsOn==="checking"),pullable:u,waiting:m,pulls:L,helpers:v,bumpOuts:W,pastCap:T,free:p}}d(Ts,"pullOf");function Ps(e,t){const n=d(s=>{const r=s.agents||[],a=r.filter(u=>t(u,r)),[c]=a;return s.lane==="progress"&&!s.bumpedAt&&!s.stalled&&s.waitsOn!=="checking"&&s.builder?.phase!=="landing"&&!(s.prs||[]).length&&!s.pushed&&!s.mergeReady&&a.length===1&&c.kind!==ge&&c.pid&&c.label!==He&&!c.pause&&!r.some(u=>u.kind===ge&&u.started&&!u.done)?String(c.started):null},"newest");return e.map(s=>({key:s.key,at:n(s)})).filter(s=>s.at).sort((s,r)=>r.at.localeCompare(s.at)).map(s=>s.key)}d(Ps,"bumpOutsOf");function _o(e,t,n){return e.filter(s=>s.kind==="work"&&!pe(s)&&s.waitsOn!=="checking"&&!(s.agents||[]).some(r=>r.started&&!r.done)).flatMap(s=>{const r=(s.prs||[]).find(c=>c.state==="OPEN"&&c.mergeable==="CONFLICTING"&&c.repo===s.repo&&c.headRefOid),a=r&&`${r.headRefOid}@${n}`;return r&&R(t,s.key)!==a?[{key:s.key,pr:r.number,branch:r.headRefName,conflict:a}]:[]})}d(_o,"rebasesDue");const Us=3;function Ro(e,{cap:t=Us}={}){const n=e.filter(l=>!pe(l)),s=n.filter(l=>(l.agents||[]).some(u=>Z(u)&&u.started&&!u.done)).length,r=n.filter(l=>l.buildable&&!["ready","staging"].includes(l.phase)&&!l.unsorted&&!l.held&&!l.working&&!l.grilling&&!l.prototyping&&!l.questions&&!(l.agents||[]).some(u=>Z(u)&&u.kind!==fe)),a=[...r.filter(l=>!l.blocked),...r.filter(l=>l.blocked)],c=a.filter(l=>l.bumpedAt);return[...c,...a.filter(l=>!l.bumpedAt).slice(0,Math.max(0,t-s-c.length))].map(l=>l.key)}d(Ro,"arrivalsOf");function Mo(e){return e.filter(t=>t.kind==="work"&&t.lane==="todo"&&t.roundAt&&["grilling","prototyping"].includes(t.phase)&&!(t.agents||[]).some(n=>Z(n)&&n.kind!==fe&&n.started&&(String(n.started)>=String(t.roundAt)||!n.done))).map(t=>({key:t.key,kind:t.phase==="grilling"?"grill":"prototype"}))}d(Mo,"roundsDue");const js=d(e=>`${fe}-${e.id}`,"visualAgent");function Io(e,t){const n=[];for(const s of e){const r=s.agents||[];if(r.some(l=>l.kind===fe&&l.started&&!l.done))continue;const a=new Set(r.map(l=>l.name)),c=t.find(l=>l.kind==="visual-request"&&l.data?.key===s.key&&!a.has(js(l)));c&&n.push({key:s.key,request:c})}return n}d(Io,"visualsDue");const Ws=d((e,t)=>e.kind==="ask"?t.get(`ask:${e.id}`)??null:e.kind==="card-added"?e.data?.key??null:null,"intakeKeyOf");function Fs(e,t,n){const s=e.get(n);return s?t.kind==="ask"?!!s.isNew:s.unfiled:!1}d(Fs,"unsorted");const Ks={ask:d((e,t)=>`Sort the new task: item notes, then item set ${e} --phase grilling or prototyping (ready, with --size, for a Quick build); merge it into a card with item relate-ask ${t.id} <card> --summary; or answer it with reply ${e}`,"ask"),"card-added":d(()=>"Link and file the card an agent added: item notes, then item set --relate and --phase","card-added")};function Gs(e,t,n,s){const r=new Map(e.map(c=>[c.key,c])),a=new Set;for(const c of t){const l=Ws(c,s);!l||a.has(l)||!Fs(r,c,l)||(a.add(l),r.get(l).orchestrator={kind:"intake",since:String(c.ts),picked:R(n,c.id)||null,next:Ks[c.kind](l,c)})}}d(Gs,"intakeWaits");function Qs(e,t){for(const n of e){const s=R(t,n.key);s&&(n.orchestrator={kind:"screen",since:s.since,picked:null,next:`Screen the grill's questions ${s.ids.join(", ")}: question settle <id> --note for each the board already answers, then question publish ${n.key}; left 15 minutes unscreened, they reach the owner as they are`})}}d(Qs,"screenWaits");function Hs(e){return e.filter(t=>t.lane==="todo"||t.lane==="review"&&t.stale).map(t=>JSON.stringify([t.key,t.title,t.deps||[]])).sort().join(`
7
- `)}d(Hs,"todoOf");function qo(e){const t=e.filter(s=>s.kind==="work"&&s.lane!=="done"&&s.questions?.open>0),n=t.map(s=>JSON.stringify([s.key,s.questions.open,s.questions.askedAt||[]])).sort().join(`
5
+ `),files:s.flatMap(r=>r.files),state:s.map(r=>r.state).sort((r,a)=>Pt.indexOf(r)-Pt.indexOf(a))[0],reply:void 0,answers:s.map(r=>({eventId:r.eventId,state:r.state,reply:r.reply,replies:r.replies,text:r.text}))}]})}d(xs,"foldSubmits");const Ut=d(e=>(e.answers||[e]).filter(t=>t.eventId).map(t=>[t.eventId,t.state]),"stateEntries");function Os(e,t,n,s,r,a){const u=d((c,h)=>{const O=a.filter(S=>S.kind==="wave-triage"&&S.data?.[c]===h).pop(),A=r.filter(S=>S.topic===`${c}:${h}`&&(S.state==="open"||S.state==="answered")).length;return{running:!!(O&&!O.reply),questions:A}},"triageOf"),i=new Map(s.map(c=>[c.key,c])),p=d(c=>c.filter(h=>h.lane==="done"&&!St(h)).length,"merged"),m=n.map(c=>({l:c,cards:c.keys.map(h=>i.get(h)).filter(Boolean)})).map(c=>({...c,open:!c.l.finished_at&&c.cards.some(h=>h.lane!=="done")})).sort((c,h)=>Number(c.open)-Number(h.open));for(const{l:c,cards:h}of m)for(const O of h)O.wave={slug:c.slug,title:c.title};const f=m.filter(c=>c.open).map(({l:c,cards:h})=>({kind:"wave",slug:c.slug,title:c.title,goal:c.goal,keys:h.map(O=>O.key),next:Es(h),merged:p(h),total:h.length,triage:u("wave",c.slug)}));return e.fromWaves?[...f,...t.filter(c=>!c.finished).map(c=>({w:c,cards:s.filter(h=>h.file===c.slug&&h.kind!=="ask")})).filter(({cards:c})=>c.length).map(({w:c,cards:h})=>({kind:"file",slug:c.slug,title:c.title,goal:null,keys:h.map(O=>O.key),next:null,merged:p(h),total:h.length,triage:u("file",c.slug)}))]:f}d(Os,"waveBandsOf");function Es(e){const t=e.filter(a=>a.lane==="todo"),n=t.find(a=>a.ready&&a.buildable)||t[0];if(!n)return null;const s=n.ready&&n.buildable,r=s?"ready":n.held?"held":n.blockedBy.length?`waits on ${n.blockedBy.map(a=>a.ref).join(", ")}`:n.blockedUntil?`blocked until ${n.blockedUntil}`:n.action?"needs you":n.phase==="new"?"not ready":n.phase;return{key:n.key,ref:n.ref,ready:s,word:r}}d(Es,"nextOf");const $s=d(e=>e.items||e.phases.flat(),"waveItems");function Ao(e){const{owner:t={},topicItems:n={},events:s=[],threads:r={},prs:a={},working:u={}}=e,i=e.now??Date.now(),p=Tt(s,t),m=new Set(s.filter(o=>o.kind==="ask-withdrawn").map(o=>o.data?.id)),f=new Map(s.filter(o=>o.kind==="ask"&&!m.has(o.id)).map(o=>[o.id,o])),c=d((o,g)=>new Map(s.filter(x=>x.kind===o).sort((x,E)=>String(x.ts).localeCompare(String(E.ts))).map(x=>[x.data?.[g],x.ts])),"latest"),h=c("card-unbump","key"),O=new Map([...c("card-bump","key")].filter(([o,g])=>!(String(h.get(o)||"")>String(g)))),A=c("card-done","key"),S=c("card-merge","key"),P=e.autoMerge===!0,D=e.tickets,U=new Map(s.filter(o=>o.kind==="card-hold").sort((o,g)=>String(o.ts).localeCompare(String(g.ts))).map(o=>[o.data?.key,o])),N=new Map,j=new Map(e.files.flatMap(({slug:o,itemFile:g})=>g.items.map(x=>[`${o}/${x.id}`,{ref:nt(g,x,D),title:x.title}]))),w=e.askLinks||{},b=new Map;for(const[o,g]of Object.entries(w))f.has(o)&&b.set(g,[...b.get(g)||[],o]);const C=[],l=[],B=new Map,me=new Map,he=new Map,G=new Map,K=e.github?null:tn(e.remote);for(const{slug:o,itemFile:g,agents:x={}}of e.files){const E=e.github||g.repo||null;C.push({slug:o,title:g.title||o,status:g.status||null,internal:!!g.internal,finished:!1,reviewers:(g.reviewers||[]).map(y=>y.name)}),g.items.forEach((y,M)=>{const _=`${o}/${y.id}`,Ke=(b.get(_)||[]).filter(v=>v!==y.from_ask),le=[y.from_ask,...Ke].filter(v=>f.has(v)),Ce=new Set(le.map(v=>`ask:${v}`)),Ee=[...R(r,_)||[],...le.flatMap(v=>R(r,`ask:${v}`)||[])];y.from_ask&&N.set(y.from_ask,[...N.get(y.from_ask)||[],{key:_,ref:nt(g,y,D),became:!0}]);const se=vs(_,{lines:Ee,events:s,owner:t,working:u,fromAsk:y.from_ask||null,sorted:!!y.phase,moved:Ke,cards:j,index:p});for(const v of se)for(const[ie,Ge]of Ut(v))B.set(ie,Ge);me.set(_,Lt(se)),l.push(Dn({slug:o,itemFile:g,it:y,order:M,repo:E,remote:K,base:e.branch||"main",prs:rn(y,E).map(v=>({...v,gh:R(a,ln(v))||null})),...(v=>({agents:v,builder:ts(v,R(r,_),i,e.moved?.[it(R(x,y.id))]??null,R(e.alive,_)??null,!!e.commandRunning?.[it(R(x,y.id))]),reachedPhase:es(v,R(r,_)),eta:v.map(ie=>ie.eta).filter(Boolean).sort().pop()||null}))(lt(R(x,y.id))),worktree:it(R(x,y.id))??y.worktree?.path??null,thread:Ee,events:[...ct(p,[_,...Ce].map(v=>p.byKey.get(v))),...le.map(v=>f.get(v))],working:se.some(v=>Ut(v).some(([,ie])=>ie==="working")),said:se.filter(v=>v.who==="love"&&(v.kind==="item-message"||v.kind==="owner-answer")),askedAt:(p.questions.get(`item:${_}`)||[]).filter(v=>v.asked_at).map(v=>v.asked_at),topicItems:n,prSlack:e.prSlack||{},boardReviewers:e.reviewers||[],now:i,bumpedAt:O.get(_),holdAsk:U.get(_),closedAt:A.get(_),mergeAt:S.get(_),autoMerge:P,skipDraft:e.skipDraft===!0,tickets:D}));const Z=l[l.length-1];f.has(y.from_ask)&&(Z.created=f.get(y.from_ask).ts),Z.askMsg=se.find(v=>v.kind==="ask"&&v.eventId===y.from_ask)||null,G.set(_,{parent:y.parent?Wt(o,y.parent):null,related:(y.related||[]).map(v=>Wt(o,v))}),Z.lane==="done"&&he.set(_,Z.closedAt||String(Z.updatedAt||""))})}const te=new Map(l.map(o=>[o.key,o])),I=new Map,Q=e.try||{},ze=e.autoBuild===!0,W=["web","app"].includes(Q.kind)?Q.kind:e.files.some(({itemFile:o})=>o.items.some(g=>g.install?.url))?"app":"web";for(const o of l){if(o.kind!=="work"||o.lane==="done")continue;const g=R(Q.cards,o.key)||{},x=g.head||o.pr?.headRefOid,E=W==="web"?g.sha:o.install?.sha,y=o.builder?.kind===ue.install?"running":g.state||(W==="app"&&o.install?"ready":null);o.try={kind:W,state:y,stale:!!(E&&x&&!String(x).toLowerCase().startsWith(String(E).toLowerCase())),url:W==="web"?null:o.install?.url||null,port:W==="web"&&y==="ready"&&g.https||null,sha:E||null,changes:g.ahead>0,script:g.script===!0,autoBuild:ze,hostBusy:!!e.hostBusy}}const L=Q.main,we=L?.script&&L.sha?{slug:L.slug,branch:L.branch||"main",sha:L.sha.slice(0,7),at:L.at,kind:W,state:L.state||null,url:W==="web"?null:e.main?.install?.url||null,port:W==="web"&&(L.state==="ready"?L.https:L.serving?.https)||null,serving:W==="web"&&L.serving?{sha:L.serving.sha.slice(0,7),at:L.serving.at}:null,pull:Q.pull?{running:Q.pull.running===!0,error:Q.pull.error||null}:null,script:pn(L.target)}:null,ye=[],be=[],Le=_e(s.filter(o=>o.kind==="owner-answer"),o=>o.data?.id),F=_e(s.filter(o=>o.kind==="prototype-request"),o=>o.data?.question),Re=_e(s.filter(o=>o.kind==="reask-request"),o=>o.data?.question),Je=_e(s.filter(o=>o.kind===mt),o=>o.data?.question),V=(t.items||[]).map(o=>ls(o,Le.get(o.id)||[],F.get(o.id)||[],Re.get(o.id)||[],Je.get(o.id)||[])),Y=new Map(V.map(o=>[o.id,o])),Te=_e(V,o=>o.topic);for(const o of f.values()){const g=`ask:${o.id}`,x=R(r,g)||[],E=N.get(o.id)?.[0].key||R(w,o.id);if(!E){x.some(_=>_.line==="ask-moved")||be.push(o.id);continue}I.set(g,E);const y=x.filter(_=>_.line===fn).at(-1),M=te.get(E);if(y&&M){const _=y.title||String(p.edits.get(o.id)??o.data?.text??"").trim().split(`
6
+ `)[0]||"Ask";ye.push({key:M.key,ref:M.ref,text:_,at:String(y.ts)})}}for(const o of C){const g=l.filter(x=>x.file===o.slug);if(o.finished=On(g),o.finished)for(const x of g)x.lane==="done"&&(x.fold=yn(o.slug))}for(const o of l)o.wave=null;const Se=new Set((t.topics||[]).map(o=>o.id)),ve=e.files.flatMap(({slug:o,itemFile:g})=>(g.waves||[]).map(x=>({...x,keys:$s(x).map(E=>`${o}/${E}`)}))),xe=d(()=>us([...t.topics||[],...l.filter(o=>!Se.has(`item:${o.key}`)).map(o=>({id:`item:${o.key}`,card:o.key,title:`${o.ref}: ${o.title}`})),...ve.filter(o=>!Se.has(`wave:${o.slug}`)).map(o=>({id:`wave:${o.slug}`,title:o.title,summary:"Wave triage."})),...(e.fromWaves?C:[]).filter(o=>!Se.has(`file:${o.slug}`)).map(o=>({id:`file:${o.slug}`,title:o.title,summary:"Wave triage."}))],V,new Set(l.filter(pe).map(o=>`item:${o.key}`))),"topicsOf"),Pe=new Map(xe().map(o=>[o.id,o]));for(const o of l){const g=Pe.get(`item:${o.key}`);if(!g){o.questions=null,o.prototypes=null;continue}const x=g.questions.filter(M=>Y.get(M)?.state!=="resolved"),E=g.questions.find(M=>Y.get(M)?.state==="open")||x[0]||g.questions[0],y=g.questions.map(M=>Y.get(M)).filter(M=>M?.state==="open");o.questions={topic:g.id,state:g.state,open:g.open,answered:g.answered,replied:g.replied,resolved:g.resolved,first:E,askedAt:y.map(M=>M.asked_at).filter(Boolean).sort(),fromAsks:y.length>0&&y.every(M=>M.ask)},o.prototypes=cs(g.questions.map(M=>Y.get(M)))}const ne=new Map(l.map(o=>[o.key,o])),Ue=new Map(ve.map(o=>[`wave:${o.slug}`,o.keys]));for(const o of l){const g=o.kind==="work"&&o.lane!=="done",x=new Set(o.deps.flatMap(y=>Ue.get(y)||[String(y).includes("/")?y:`${o.file}/${y}`]));o.blockedBy=g?[...x].map(y=>ne.get(y)).filter(y=>y&&y.lane!=="done"&&!y.reopened).map(jt):[],o.blockedUntil=g?o.blockedUntil??null:null,o.blockReason=g?o.blockReason??null:null,o.blocked=o.blockedBy.length>0||!!o.blockedUntil,Xn(o,Y,P),o.lane==="todo"&&o.kind==="work"&&!o.held&&Et(o.storedLane,o.agents)&&(o.lane="progress"),o.unpicked=o.kind==="work"&&o.lane==="todo"&&!!Kn(V,o.key),o.kind==="work"&&(o.phase=zs(o)),o.unpicked&&!o.action&&(o.next=Gn),o.buildable=Wn(o);const E=o.kind==="work"&&o.lane==="todo";if(o.unsorted=E&&(!!o.fromAsk||o.unfiled)&&!o.filedHeld&&o.phase==="new",o.kind==="work"){const y=o.askMsg;delete o.askMsg,o.unsorted&&y&&(o.isNew=!0,o.ask={eventId:y.eventId,state:y.state,askedAt:y.ts,edited:!!y.edited,canEdit:!0,text:y.text,files:y.files,from:y.from||null})}o.ready=E&&o.phase==="ready"&&!o.blocked&&!o.recheckAt&&!o.action,o.staging=E&&o.phase==="staging",o.kind==="work"&&(o.hostHeld=!!e.hostBusy&&o.ready&&!!o.bumpedAt&&!o.held)}_s(l,ne,G);const je=new Map((t.items||[]).map(o=>[o.id,o.topic])),z=d(o=>o.kind==="owner-answer"?je.has(o.data?.id)?/^item:(.+)$/.exec(je.get(o.data.id))?.[1]||null:"":o.kind==="ask"?`ask:${o.id}`:o.data?.key||(o.kind.startsWith("ask-")&&o.data?.id?`ask:${o.data.id}`:null),"ownCardOf"),J=d(o=>{const g=z(o);return I.get(g)||g},"cardOf"),Me=d(o=>[A.get(o),he.get(o)].filter(Boolean).sort().pop()||"","closedAt"),Ie=d(o=>{const g=J(o);return g===null?!o.reply:!ne.has(g)||!(Me(g)<String(R(u,o.id)))?!1:B.has(o.id)?B.get(o.id)==="working":!o.reply&&!I.has(z(o))&&!(String(me.get(g)||"")>String(o.ts))},"unanswered"),re=s.filter(o=>R(u,o.id)&&Ie(o)).map(o=>({id:o.id,kind:o.kind,key:J(o),data:o.data,since:R(u,o.id)}));for(const o of l)o.working=re.some(g=>g.key===o.key),o.kind==="work"&&o.lane==="progress"&&!o.builder&&!o.next&&o.waitsOn!=="checking"&&!o.working&&(o.next=$t),o.sortAt=Rs[o.lane](o)||(o.lane==="review"?null:o.updatedAt)||null;for(const o of l){if(o.kind!=="work")continue;const g=(Te.get(`item:${o.key}`)||[]).filter(E=>E.state==="answered").map(E=>E.answers.at(-1).ts).sort().pop(),x=re.filter(E=>E.key===o.key).map(E=>String(E.since)).sort()[0];o.orchestrator=!o.stalled&&(["progress","todo"].includes(o.lane)||o.waitsOn==="checking"||o.lane==="review"&&(o.stale||o.waitFacts.unblockedAt||o.waitFacts.replyAt))?so(o,o.waitFacts,{answeredAt:g,workingSince:x}):null,delete o.waitFacts}Gs(l,s,u,I),Qs(l,e.screening||{}),l.sort(gn);const Ze=Os(e,C,ve,l,V,s),$=xe(),We=nn({on:e.autoReview,incoming:e.incoming,records:e.reviewRecords,events:s,now:i,refs:new Map(l.filter(o=>o.kind==="work").map(o=>[o.key,o.ref]))}),ae=[...Is(l,$,V),...sn(We)],T=new Map(ae.filter(o=>o.questions).map((o,g)=>[o.topic||`item:${o.key}`,g])),Fe=d(o=>T.get(o.id)??T.size,"rankOf");$.sort((o,g)=>Fe(o)-Fe(g));const qe=new Set(ae.filter(o=>o.questions&&o.progress).map(o=>`item:${o.key}`));for(const o of $)qe.has(o.id)&&(o.progress=!0);const Oe=l.flatMap(o=>o.answered&&me.get(o.key)?[{key:o.key,id:o.id,text:o.title,at:me.get(o.key)}]:[]).sort((o,g)=>g.at.localeCompare(o.at));return ye.sort((o,g)=>g.at.localeCompare(o.at)),As({kind:W,main:we,remote:K&&{host:K.host,name:`${K.host}/${K.path}`},itemFiles:C,waveBands:Ze,items:l,moved:Object.fromEntries(I),answers:Oe,merges:ye,unfiled:be,owner:V,ownerTopics:$,reviews:We,needs:ae,working:re.length,workingOn:re,checks:Ms(e,i)},e)}d(Ao,"buildBoard");function As(e,{listen:t,prompts:n}={}){const s=[...Cs(n),...e.needs];return{...e,needs:s,needsSummary:Ds(s),checks:{...e.checks,watcher:{listening:"running",asleep:"asleep",waking:"waking"}[t]||"stopped"}}}d(As,"withHub");const jt=d(e=>({key:e.key,ref:e.ref}),"linkOf"),Wt=d((e,t)=>String(t).includes("/")?String(t):`${e}/${t}`,"linkKey"),Bs=d((e,t)=>e.ref.localeCompare(t.ref,void 0,{numeric:!0}),"byRef");function _s(e,t,n){const s=new Map,r=new Map,a=new Map,u=d((p,m,f)=>p.set(m,[...p.get(m)||[],f]),"add");for(const[p,m]of n){m.parent&&u(s,m.parent,p);for(const f of m.related)f!==p&&(u(r,p,f),u(r,f,p))}for(const p of e)for(const m of p.blockedBy||[])u(a,m.key,p.key);const i=d((p=[])=>[...new Set(p)].map(m=>t.get(m)).filter(Boolean).map(jt).sort(Bs),"cards");for(const p of e){const m=n.get(p.key)?.parent;p.parent=m?{key:m,ref:t.get(m)?.ref||m.slice(m.indexOf("/")+1)}:null,p.children=i(s.get(p.key)),p.related=i(r.get(p.key)),p.waitsOnIt=i(a.get(p.key))}}d(_s,"linkCards");const Rs={todo:d(e=>e.created,"todo"),progress:d(e=>e.progressSince,"progress"),review:d(e=>e.reviewSince,"review"),done:d(e=>e.closedAt,"done")},oe=d((e,t)=>`${e} ${t}${e===1?"":"s"}`,"plural");function Ms({checked:e,watcher:t,replyAt:n,threads:s={},working:r={},files:a=[]},u){const i=Number(e?.every)||30,m=[n,t?.since,...Object.values(r),...Object.values(s).flat().filter(c=>(c.who||"claude")==="claude").map(c=>c.ts),...a.flatMap(c=>Object.values(c.agents||{}).flatMap(h=>Object.values(h||{})).map(h=>h?.done||h?.started))].filter(c=>typeof c=="string"&&!Number.isNaN(Date.parse(c))).reduce((c,h)=>c&&Date.parse(c)>Date.parse(h)?c:h,null),f=!!e?.idle;return{at:e?.at||null,every:f?null:i,idle:f,late:!f&&!!e?.at&&u-Date.parse(e.at)>2*i*6e4,claudeAt:m}}d(Ms,"checksOf");function Is(e,t,n){const s=new Set(e.map(i=>i.questions?.topic).filter(Boolean)),r=d(i=>i.find(p=>n.find(m=>m.id===p)?.state==="open")||i[0],"firstOpen"),a=t.filter(i=>i.state!=="resolved"&&i.open>0&&!s.has(i.id)).map(i=>{const p=`questions.html#${r(i.questions)}`,m=/^(?:wave|file):(.+)$/.exec(i.id)?.[1]||null;return{kind:"Question",title:i.title,text:`${i.title} \xB7 ${oe(i.open,"question")}`,url:p,topic:i.id,band:m,questions:{open:i.open,url:p},ready:null,since:n.filter(f=>f.topic===i.id&&f.state==="open").map(f=>f.asked_at).filter(Boolean).sort()[0]||null}}),u=e.map(i=>{const p=i.questions&&i.questions.state!=="resolved"&&i.questions.open>0?{open:i.questions.open,url:`questions.html#${i.questions.first}`}:null,m=i.waitsOn==="ready"?i.pr.url:null,f=!p&&!m&&i.action?{label:i.action.label,url:i.action.url}:null;if(!p&&!m&&!f)return null;const c=`${i.ref}: ${i.title}`,h=[c,p&&oe(p.open,"question"),m&&"Ready on GitHub",f?.label].filter(Boolean).join(" \xB7 ");return{kind:p?"Question":m?"Tap Ready":"Action needed",title:c,text:h,url:`#selectedCard=${encodeURIComponent(i.key)}`,key:i.key,questions:p,ready:m,action:f,since:i.created||null,progress:i.lane==="progress"}}).filter(Boolean);return[...a,...u].sort(qs)}d(Is,"needsOf");const qs=d((e,t)=>!!t.progress-!!e.progress||(e.since&&t.since?e.since.localeCompare(t.since):!!t.since-!!e.since),"oldestFirst");function Cs(e=[]){return e.map(t=>{const n=t.questions?.length?`Question: ${t.questions.map(s=>s.question).join(" \xB7 ")}`:`${t.tool}: ${t.detail||t.action}`;return{kind:"Prompt",title:n,text:n,url:"",prompt:t}})}d(Cs,"promptLines");function Ds(e){const t=e.filter(a=>a.prompt).length,n=e.reduce((a,u)=>a+(u.questions?.open||0),0),s=e.filter(a=>a.ready).length,r=e.filter(a=>a.review).length;return[t&&oe(t,"prompt"),n&&oe(n,"question"),s&&`${oe(s,"draft")} waiting on Ready`,r&&`${oe(r,"review")} held`].filter(Boolean).join(" \xB7 ")}d(Ds,"needsSummary");function Bo(e){const t=d((...n)=>e.items.filter(s=>n.includes(s.lane)).length,"count");return{items:e.needs.map(({kind:n,text:s,url:r})=>({kind:n,text:s,url:r})),working:e.working,summary:e.needsSummary,lanes:{review:t("review"),progress:t("progress"),todo:t("todo")},building:e.items.some(n=>(n.agents||[]).some(s=>!s.done))}}d(Bo,"needsAnswer");const Ns=d(e=>e.buildable&&e.phase==="ready"&&!e.held&&!e.blocked&&!e.recheckAt,"pullableCard"),Ls=10;function Ts(e,{cap:t=Ls,pulled:n=[],hold:s=[],taken:r=0}={}){const a=e.filter(w=>!pe(w)),u=d((w,b)=>w.started&&!w.done&&H(w)&&!(w.kind===ge&&!w.writes)&&!w.pause?.paused&&!(w.waits&&b.some(C=>C.name===w.waits&&!C.done)),"counts"),i=a.filter(w=>!w.stalled).reduce((w,b)=>w+(b.agents||[]).filter((C,l,B)=>u(C,B)).length,0),p=a.filter(w=>Ns(w)&&!w.working).map(w=>w.key),m=a.filter(w=>n.includes(w.key)&&(p.includes(w.key)||w.buildable&&w.working)).map(w=>w.key),f=a.flatMap(w=>(w.agents||[]).filter(b=>b.kind===ge&&!b.started&&!b.done).map(b=>({key:w.key,name:b.name,writes:b.writes}))),c=Math.max(0,t-i-m.length-r),h=f.filter(w=>w.writes),O=[...f.filter(w=>!w.writes),...h.slice(0,c)].map(({key:w,name:b})=>({key:w,name:b})),A=Math.max(0,c-h.length),S=new Set(a.filter(w=>w.bumpedAt).map(w=>w.key)),P=p.filter(w=>!m.includes(w)&&!s.includes(w)),D=P.filter(w=>S.has(w)),U=[...D,...P.filter(w=>!S.has(w)).slice(0,Math.max(0,A-D.length))],N=D.slice(A),j=Ps(a,u).slice(0,N.length).map((w,b)=>({key:w,by:N[b]}));return{idle:!i&&!a.some(w=>w.waitsOn==="checking"),pullable:p,waiting:m,pulls:U,helpers:O,bumpOuts:j,pastCap:N,free:c}}d(Ts,"pullOf");function Ps(e,t){const n=d(s=>{const r=s.agents||[],a=r.filter(p=>t(p,r)),[u]=a;return s.lane==="progress"&&!s.bumpedAt&&!s.stalled&&s.waitsOn!=="checking"&&s.builder?.phase!=="landing"&&!(s.prs||[]).length&&!s.pushed&&!s.mergeReady&&a.length===1&&u.kind!==ge&&u.pid&&u.label!==He&&!u.pause&&!r.some(p=>p.kind===ge&&p.started&&!p.done)?String(u.started):null},"newest");return e.map(s=>({key:s.key,at:n(s)})).filter(s=>s.at).sort((s,r)=>r.at.localeCompare(s.at)).map(s=>s.key)}d(Ps,"bumpOutsOf");function _o(e,t,n){return e.filter(s=>s.kind==="work"&&!pe(s)&&s.waitsOn!=="checking"&&!(s.agents||[]).some(r=>r.started&&!r.done)).flatMap(s=>{const r=(s.prs||[]).find(u=>u.state==="OPEN"&&u.mergeable==="CONFLICTING"&&u.repo===s.repo&&u.headRefOid),a=r&&`${r.headRefOid}@${n}`;return r&&R(t,s.key)!==a?[{key:s.key,pr:r.number,branch:r.headRefName,conflict:a}]:[]})}d(_o,"rebasesDue");const Us=3,Ro=10;function Mo(e,{cap:t=Us}={}){const n=e.filter(i=>!pe(i)),s=n.filter(i=>(i.agents||[]).some(p=>ee(p)&&p.started&&!p.done)).length,r=n.filter(i=>i.buildable&&!["ready","staging"].includes(i.phase)&&!i.unsorted&&!i.held&&!i.working&&!i.grilling&&!i.prototyping&&!i.questions&&!(i.agents||[]).some(p=>ee(p)&&p.kind!==fe)),a=[...r.filter(i=>!i.blocked),...r.filter(i=>i.blocked)],u=a.filter(i=>i.bumpedAt);return[...u,...a.filter(i=>!i.bumpedAt).slice(0,Math.max(0,t-s-u.length))].map(i=>i.key)}d(Mo,"arrivalsOf");function Io(e){return e.filter(t=>t.kind==="work"&&t.lane==="todo"&&t.roundAt&&["grilling","prototyping"].includes(t.phase)&&!(t.agents||[]).some(n=>ee(n)&&n.kind!==fe&&n.started&&(String(n.started)>=String(t.roundAt)||!n.done))).map(t=>({key:t.key,kind:t.phase==="grilling"?"grill":"prototype"}))}d(Io,"roundsDue");const js=d(e=>`${fe}-${e.id}`,"visualAgent");function qo(e,t){const n=[];for(const s of e){const r=s.agents||[];if(r.some(i=>i.kind===fe&&i.started&&!i.done))continue;const a=new Set(r.map(i=>i.name)),u=t.find(i=>i.kind==="visual-request"&&i.data?.key===s.key&&!a.has(js(i)));u&&n.push({key:s.key,request:u})}return n}d(qo,"visualsDue");const Ws=d((e,t)=>e.kind==="ask"?t.get(`ask:${e.id}`)??null:e.kind==="card-added"?e.data?.key??null:null,"intakeKeyOf");function Fs(e,t,n){const s=e.get(n);return s?t.kind==="ask"?!!s.isNew:s.unfiled:!1}d(Fs,"unsorted");const Ks={ask:d((e,t)=>`Sort the new task: item notes, then item set ${e} --phase grilling or prototyping (ready, with --size, for a Quick build); merge it into a card with item relate-ask ${t.id} <card> --summary; or answer it with reply ${e}`,"ask"),"card-added":d(()=>"Link and file the card an agent added: item notes, then item set --relate and --phase","card-added")};function Gs(e,t,n,s){const r=new Map(e.map(u=>[u.key,u])),a=new Set;for(const u of t){const i=Ws(u,s);!i||a.has(i)||!Fs(r,u,i)||(a.add(i),r.get(i).orchestrator={kind:"intake",since:String(u.ts),picked:R(n,u.id)||null,next:Ks[u.kind](i,u)})}}d(Gs,"intakeWaits");function Qs(e,t){for(const n of e){const s=R(t,n.key);s&&(n.orchestrator={kind:"screen",since:s.since,picked:null,next:`Screen the grill's questions ${s.ids.join(", ")}: question settle <id> --note for each the board already answers, then question publish ${n.key}; left 15 minutes unscreened, they reach the owner as they are`})}}d(Qs,"screenWaits");function Hs(e){return e.filter(t=>t.lane==="todo"||t.lane==="review"&&t.stale).map(t=>JSON.stringify([t.key,t.title,t.deps||[]])).sort().join(`
7
+ `)}d(Hs,"todoOf");function Co(e){const t=e.filter(s=>s.kind==="work"&&s.lane!=="done"&&s.questions?.open>0),n=t.map(s=>JSON.stringify([s.key,s.questions.open,s.questions.askedAt||[]])).sort().join(`
8
8
  `);return{waiting:t.map(s=>s.key),seen:`${n}
9
9
  --
10
- ${Hs(e)}`}}d(qo,"triageOf");const Vs=100,Xs=["key","kind","id","ref","title","lane","phase","file","fileTitle","fold","internal","ticket","ticketSource","linear","pr","prs","review","reviewers","risky","sortAt","threadCount","othersAt","hasNotes","questions","prototypes","next","ask","action","actionButton","builder","working","waitsOn","reachedPhase","grilling","refusal","install","parent","children","related","waitsOnIt","blockedBy","ready","finishable","reopened","manualSteps","branch","worktree"],Co=d(e=>Object.fromEntries(Xs.filter(t=>t in e).map(t=>[t,e[t]])),"slimDone");function Do(e,t=Vs){const n=new Set((e.waveBands||[]).flatMap(r=>r.keys)),s=new Set(e.items.filter(r=>r.lane!=="done").flatMap(r=>[...(r.deps||[]).map(a=>String(a).includes("/")?String(a):`${r.file}/${a}`),r.parent?.key,...(r.children||[]).map(a=>a.key),...(r.related||[]).map(a=>a.key)]));return e.items.filter(r=>r.lane==="done"&&r.kind==="work").sort((r,a)=>String(a.closedAt||"").localeCompare(String(r.closedAt||""))).slice(t).filter(r=>r.storedLane==="done"&&!r.reopened&&!n.has(r.key)&&!s.has(r.key)&&(!r.questions||r.questions.state==="resolved"))}d(Do,"archivedOf");const Ys={ready:"draft",ci:"draft",reviewers:"reviewers",merge:"merge"};function zs(e){return e.lane==="todo"?e.unpicked?"prototyping":kt.todo.includes(e.storedPhase)?e.storedPhase:e.storedLane&&e.storedLane!=="todo"?"ready":"new":e.lane==="progress"?e.waitsOn==="checking"?"checking":e.builder&&!e.builder.planner&&e.builder.kind!==ue.install&&e.builder.phase||e.reachedPhase||"reading":e.lane==="review"?e.stale?"stale":e.waitsOn==="owner"&&e.pushed?"merge":Ys[e.waitsOn]??null:e.storedLane==="done"&&kt.done.includes(e.storedPhase)?e.storedPhase:e.prs.some(t=>t.status===V.merged)?"merged":"closed"}d(zs,"phaseOf");function No(e){return e.items.filter(t=>t.kind==="work").flatMap(t=>{const n=t.lane;return n==="done"||t.storedLane==="done"&&!t.reopened?[]:n===t.storedLane&&(n==="todo"||t.phase===t.storedPhase)?[]:[{key:t.key,to:n,phase:t.phase,why:Js(t,n),from:{lane:t.storedLane,phase:t.storedPhase}}]})}d(No,"movesOf");function Js(e,t){return t==="todo"?e.held?"it is held":e.blocked?"it is blocked":"nothing builds it":e.reopened&&e.storedLane==="done"?"a message or question reopened it":e.answered&&t==="review"?"answered, it waits for your Done":t==="progress"?e.waitsOn==="checking"?"its build handed back for a check":e.builder?"a build started":"Claude acts next":e.next||"it waits on review"}d(Js,"moveWhy");const Zs=d(e=>e.startsWith("ask:")?`ask-${e.slice(4)}`:`item-${e}`,"pushTag");function Lo(e){const t=d(a=>e.owner.filter(c=>c.topic===a&&c.state==="open").map(({id:c,question:l})=>({id:c,question:l})),"open"),n=[q.ready,q.merge],s=e.items.filter(a=>a.action).map(a=>{const c=rt.has(a.action.kind)?t(a.questions.topic):[];return{tag:Zs(a.key),kind:a.action.kind,title:`${a.ref}: ${a.action.label}`,body:c[0]?.question||a.title,url:c.length?a.action.url:`#selectedCard=${encodeURIComponent(a.key)}`,now:!n.includes(a.action.kind),questions:c}}),r=e.needs.filter(a=>a.topic).map(a=>{const c=t(a.topic);return{tag:`topic-${a.topic}`,kind:q.questions,title:a.text,body:c[0]?.question||a.title,url:a.url,now:!0,questions:c}});return[...s,...r,...on(e.reviews)]}d(Lo,"ownerActions");function To(e,{pulled:t=[]}={}){return!e.items.filter(r=>!pe(r)).some(r=>r.waitsOn==="checking"||r.grilling||r.prototyping||(r.agents||[]).some(a=>!a.done))&&!e.working&&!Ts(e.items,{pulled:t}).waiting.length}d(To,"strictlyIdle");const eo="item stall it with the reason when it can't go on",Ft="restart it once from its worktree with a thread line; if the restarted run goes stale too, item stall it with the reason",to='Judge the staged card against the whole board: item set --phase ready --text "<what you checked>", or --phase grilling or prototyping for another round; where its notes answer a split question Split as proposed, file the split instead',no='Its block ended: re-read its notes and answered questions against what landed; answer a stale one again with question settle <id> --note, or send the card back with item set --phase grilling when the change is too big; then item set --phase ready --text "<what you re-checked>"';function so(e,t,{answeredAt:n,workingSince:s}){const r=d(f=>e.agents.some(p=>p.kind===f&&p.started&&!p.done),"running"),a=[e.progressSince,...e.agents.map(f=>f.done)].filter(Boolean).map(String).sort().pop()||null,c=t.unasked&&e.storedPhase==={grill:"grilling",prototype:"prototyping"}[t.unasked.kind]&&!e.agents.some(f=>f.kind===t.unasked.kind&&f.started&&(!f.done||String(f.started)>t.unasked.at))?t.unasked:null,l=t.ask?{kind:"ask",since:t.ask.at,next:`Answer the builder's ask: ${t.ask.text}`}:e.staging?{kind:"staging",since:e.stagedAt,next:to}:n?{kind:"answer",since:n,next:"Act on the owner's answer"}:t.replyAt?{kind:"reply",since:t.replyAt,next:"Act on the owner's reply"}:rt.has(e.action?.kind)?null:e.builder?.stopped?{kind:"stale",since:e.builder.silentSince,next:`${e.builder.stopped}: ${Ft}, or drop the card`}:e.builder?.stale?{kind:"stale",since:e.builder.silentSince,next:`Find out why the builder went silent, then finish its run or ${Ft}`}:e.lane==="review"&&e.stale?{kind:"stale",since:new Date(Date.parse(e.inReviewSince)+wt).toISOString(),daily:!0,next:`Find out why the review went stale and fix what you can: rebase, CI, comments; if only a reviewer owes it, a thread line naming who; or ${eo}`}:e.waitsOn==="checking"?t.unverified?{kind:"calls",since:t.handedBackAt,next:t.next||"Verify the builder's judgment calls"}:{kind:"checking",since:t.handedBackAt,next:t.next||"Land the checked build"}:e.grilling&&t.grillAt&&!r("grill")?{kind:"grill",since:t.grillAt,next:"Start the grill asked for"}:e.prototyping&&t.prototypeAt&&!r("prototype")?{kind:"prototype",since:t.prototypeAt,next:"Draw the prototype asked for"}:c?{kind:"unasked",since:c.at,next:`Its ${c.kind} planner ended without its question: read its log, then post the question, start the planner again, or move the card's phase`}:e.recheckAt&&e.lane==="todo"&&e.phase==="ready"&&!e.blocked?{kind:"recheck",since:e.recheckAt,next:no}:e.refusal&&t.refusedAt?{kind:"refused",since:t.refusedAt,next:e.refusal}:e.next===$t&&a?{kind:"unowned",since:a,next:"Start a builder on it, or set its Next"}:t.unblockedAt&&e.lane!=="todo"&&!e.builder&&!e.blocked&&!t.writes.some(f=>f>t.unblockedAt)?{kind:"unblocked",since:t.unblockedAt,next:"Its block ended: act on what it waited for, or set its Next"}:null;if(!l?.since)return null;const u=String(l.since),m=[t.writes.find(f=>f>u),s].filter(Boolean).sort()[0]||null;return{kind:l.kind,since:u,picked:m,next:l.next,...l.daily?{daily:!0}:{}}}d(so,"orchestratorWaitOf");const Po=d((e,t=null)=>({key:e.id,ref:e.data.date,title:`Overnight triage ${e.data.date}`,kind:"triage",since:e.ts,picked:t,next:`Run the overnight triage and reply: board.mjs reply ${e.id}`}),"triageWaitOf");function Uo(e){const t=e.items.filter(n=>n.orchestrator).map(n=>({key:n.key,ref:n.ref,title:n.title,...n.orchestrator,bumpedAt:n.bumpedAt||null}));return[...t.filter(n=>n.bumpedAt).sort((n,s)=>n.bumpedAt.localeCompare(s.bumpedAt)),...t.filter(n=>!n.bumpedAt)]}d(Uo,"orchestratorWaits");const jo=30*6e4,oo=15*6e4,ro=60*6e4,ao=1440*6e4;function Wo(e,t,n){const s=n.toISOString(),r=[],a=[];return{waits:t.map(l=>{const u=e.find(p=>p.key===l.key&&p.kind===l.kind&&p.since===l.since);if(!u)return l.picked||r.push(l.key),{...l,bumped:s};const m=l.picked?[l.picked,u.bumped].filter(Boolean).sort().pop():u.bumped||l.since,f=n-Date.parse(m)>=(l.picked?l.daily?ao:ro:oo);return f&&a.push({key:l.key,ref:l.ref,kind:l.kind,since:l.since,next:l.next}),{...l,bumped:f?s:u.bumped||l.since,...u.pushed?{pushed:!0}:{}}}),added:r,again:a}}d(Wo,"waitsStep");function Fo(e,t){const n=d(s=>{const r=Math.floor((t-Date.parse(s))/6e4),a=Math.floor(r/60),c=r%60;return a?`${a}h${c?` ${c}m`:""}`:`${c}m`},"age");return`still waiting on you: ${e.map(s=>`${s.ref} ${s.kind} for ${n(s.since)}: ${s.next}`).join("; ")}`}d(Fo,"waitsAgainLine");const lo=15;function Ko(e,{idle:t,now:n}){const s=n.toISOString(),r=e.last||s;return t?e.since?e.pushed||n-Date.parse(e.since)<lo*6e4?{state:{...e,last:r},push:!1}:{state:{last:s,since:e.since,pushed:!0},push:!0}:{state:{last:r,since:s},push:!1}:{state:{last:r},push:!1}}d(Ko,"idleStep");function Go(e,t){const n=d(u=>String(u.closedAt||"")>t,"after"),s=e.filter(u=>u.kind==="work"&&u.lane==="done"&&!St(u)&&n(u)),r=new Map;for(const u of e)u.wave&&r.set(u.wave.slug,[...r.get(u.wave.slug)||[],u]);const a=[...r.values()].filter(u=>u.every(m=>m.lane==="done")&&u.some(n)).map(u=>u[0].wave.title),c=s.slice(0,3).map(u=>u.ref).join(", ")+(s.length>3?` and ${s.length-3} more`:"");return[s.length&&`${s.length} ${s.length===1?"card":"cards"} landed: ${c}`,...a.map(u=>`${u} finished`)].filter(Boolean).join(" \xB7 ")||"Nothing closed since the last idle notification."}d(Go,"idleSummary");export{q as ACTION_KIND,It as ACTION_LABELS,fs as ANSWERS_SUBMITTED,De as BUILDER_ASK,ue as BUILDER_KIND,hn as BUILDER_QUIET_AFTER,mn as BUILDER_STALE_AFTER,ht as BUILD_REFUSED,ce as BUTTON,Xe as COMMANDS,ao as DAILY_REWAKE_AFTER,Xs as DONE_FIELDS,Vs as DONE_KEPT,ge as HELPER,lo as IDLE_MINUTES,qt as INTENT_LABELS,kn as ITEM_EVENTS,He as LANDER,So as LANES,Ls as MAX_IMPLEMENTERS,Us as MAX_PLANNERS,$e as MOVE,$t as NO_NEXT,jo as ORCHESTRATOR_PUSH_AFTER,Eo as PAUSE_CLEARS,ns as PAUSE_HARD_STOP,kt as PHASES,ro as PICKED_REWAKE_AFTER,zn as PLANNER_WORD,Gn as PROTOTYPE_AGAIN,xo as PROTOTYPE_WAIT,V as PR_STATE,mt as QUESTION_REOPEN,oo as REWAKE_AFTER,fe as VISUAL,Do as archivedOf,Ro as arrivalsOf,Ao as buildBoard,Q as builds,$o as confirmedDrops,yn as fileFold,st as handedBackAt,Ko as idleStep,Go as idleSummary,Tn as mergeBlockers,as as modelName,No as movesOf,Bo as needsAnswer,Kn as noTakePicked,Jn as orchestratorDid,Uo as orchestratorWaits,vo as owesNext,Lo as ownerActions,ls as ownerQuestion,rs as pauseWork,Fn as picksNoTake,Z as plans,Oo as prototypeWaits,_t as providerOf,Ts as pullOf,Zs as pushTag,_o as rebasesDue,nt as refOf,ke as roundOf,is as roundSettledBy,Mo as roundsDue,Co as slimDone,Ut as stateEntries,At as stoppedWords,To as strictlyIdle,Tt as threadIndex,vs as threadOf,Hs as todoOf,qo as triageOf,Po as triageWaitOf,St as unlanded,js as visualAgent,Io as visualsDue,Fo as waitsAgainLine,Wo as waitsStep,$s as waveItems,As as withHub};
10
+ ${Hs(e)}`}}d(Co,"triageOf");const Vs=100,Xs=["key","kind","id","ref","title","lane","phase","file","fileTitle","fold","internal","ticket","ticketSource","linear","pr","prs","review","reviewers","risky","sortAt","threadCount","othersAt","hasNotes","questions","prototypes","next","ask","action","actionButton","builder","working","waitsOn","reachedPhase","grilling","refusal","install","parent","children","related","waitsOnIt","blockedBy","ready","finishable","reopened","manualSteps","branch","worktree"],Do=d(e=>Object.fromEntries(Xs.filter(t=>t in e).map(t=>[t,e[t]])),"slimDone");function No(e,t=Vs){const n=new Set((e.waveBands||[]).flatMap(r=>r.keys)),s=new Set(e.items.filter(r=>r.lane!=="done").flatMap(r=>[...(r.deps||[]).map(a=>String(a).includes("/")?String(a):`${r.file}/${a}`),r.parent?.key,...(r.children||[]).map(a=>a.key),...(r.related||[]).map(a=>a.key)]));return e.items.filter(r=>r.lane==="done"&&r.kind==="work").sort((r,a)=>String(a.closedAt||"").localeCompare(String(r.closedAt||""))).slice(t).filter(r=>r.storedLane==="done"&&!r.reopened&&!n.has(r.key)&&!s.has(r.key)&&(!r.questions||r.questions.state==="resolved"))}d(No,"archivedOf");const Ys={ready:"draft",ci:"draft",reviewers:"reviewers",merge:"merge"};function zs(e){return e.lane==="todo"?e.unpicked?"prototyping":kt.todo.includes(e.storedPhase)?e.storedPhase:e.storedLane&&e.storedLane!=="todo"?"ready":"new":e.lane==="progress"?e.waitsOn==="checking"?"checking":e.builder&&!e.builder.planner&&e.builder.kind!==ue.install&&e.builder.phase||e.reachedPhase||"reading":e.lane==="review"?e.stale?"stale":e.waitsOn==="owner"&&e.pushed?"merge":Ys[e.waitsOn]??null:e.storedLane==="done"&&kt.done.includes(e.storedPhase)?e.storedPhase:e.prs.some(t=>t.status===X.merged)?"merged":"closed"}d(zs,"phaseOf");function Lo(e){return e.items.filter(t=>t.kind==="work").flatMap(t=>{const n=t.lane;return n==="done"||t.storedLane==="done"&&!t.reopened?[]:n===t.storedLane&&(n==="todo"||t.phase===t.storedPhase)?[]:[{key:t.key,to:n,phase:t.phase,why:Js(t,n),from:{lane:t.storedLane,phase:t.storedPhase}}]})}d(Lo,"movesOf");function Js(e,t){return t==="todo"?e.held?"it is held":e.blocked?"it is blocked":"nothing builds it":e.reopened&&e.storedLane==="done"?"a message or question reopened it":e.answered&&t==="review"?"answered, it waits for your Done":t==="progress"?e.waitsOn==="checking"?"its build handed back for a check":e.builder?"a build started":"Claude acts next":e.next||"it waits on review"}d(Js,"moveWhy");const Zs=d(e=>e.startsWith("ask:")?`ask-${e.slice(4)}`:`item-${e}`,"pushTag");function To(e){const t=d(a=>e.owner.filter(u=>u.topic===a&&u.state==="open").map(({id:u,question:i})=>({id:u,question:i})),"open"),n=[q.ready,q.merge],s=e.items.filter(a=>a.action).map(a=>{const u=rt.has(a.action.kind)?t(a.questions.topic):[];return{tag:Zs(a.key),kind:a.action.kind,title:`${a.ref}: ${a.action.label}`,body:u[0]?.question||a.title,url:u.length?a.action.url:`#selectedCard=${encodeURIComponent(a.key)}`,now:!n.includes(a.action.kind),questions:u}}),r=e.needs.filter(a=>a.topic).map(a=>{const u=t(a.topic);return{tag:`topic-${a.topic}`,kind:q.questions,title:a.text,body:u[0]?.question||a.title,url:a.url,now:!0,questions:u}});return[...s,...r,...on(e.reviews)]}d(To,"ownerActions");function Po(e,{pulled:t=[]}={}){return!e.items.filter(r=>!pe(r)).some(r=>r.waitsOn==="checking"||r.grilling||r.prototyping||(r.agents||[]).some(a=>!a.done))&&!e.working&&!Ts(e.items,{pulled:t}).waiting.length}d(Po,"strictlyIdle");const eo="item stall it with the reason when it can't go on",Ft="restart it once from its worktree with a thread line; if the restarted run goes stale too, item stall it with the reason",to='Judge the staged card against the whole board: item set --phase ready --text "<what you checked>", or --phase grilling or prototyping for another round; where its notes answer a split question Split as proposed, file the split instead',no='Its block ended: re-read its notes and answered questions against what landed; answer a stale one again with question settle <id> --note, or send the card back with item set --phase grilling when the change is too big; then item set --phase ready --text "<what you re-checked>"';function so(e,t,{answeredAt:n,workingSince:s}){const r=d(f=>e.agents.some(c=>c.kind===f&&c.started&&!c.done),"running"),a=[e.progressSince,...e.agents.map(f=>f.done)].filter(Boolean).map(String).sort().pop()||null,u=t.unasked&&e.storedPhase==={grill:"grilling",prototype:"prototyping"}[t.unasked.kind]&&!e.agents.some(f=>f.kind===t.unasked.kind&&f.started&&(!f.done||String(f.started)>t.unasked.at))?t.unasked:null,i=t.ask?{kind:"ask",since:t.ask.at,next:`Answer the builder's ask: ${t.ask.text}`}:e.staging?{kind:"staging",since:e.stagedAt,next:to}:n?{kind:"answer",since:n,next:"Act on the owner's answer"}:t.replyAt?{kind:"reply",since:t.replyAt,next:"Act on the owner's reply"}:rt.has(e.action?.kind)?null:e.builder?.stopped?{kind:"stale",since:e.builder.silentSince,next:`${e.builder.stopped}: ${Ft}, or drop the card`}:e.builder?.stale?{kind:"stale",since:e.builder.silentSince,next:`Find out why the builder went silent, then finish its run or ${Ft}`}:e.lane==="review"&&e.stale?{kind:"stale",since:new Date(Date.parse(e.inReviewSince)+wt).toISOString(),daily:!0,next:`Find out why the review went stale and fix what you can: rebase, CI, comments; if only a reviewer owes it, a thread line naming who; or ${eo}`}:e.waitsOn==="checking"?t.unverified?{kind:"calls",since:t.handedBackAt,next:t.next||"Verify the builder's judgment calls"}:{kind:"checking",since:t.handedBackAt,next:t.next||"Land the checked build"}:e.grilling&&t.grillAt&&!r("grill")?{kind:"grill",since:t.grillAt,next:"Start the grill asked for"}:e.prototyping&&t.prototypeAt&&!r("prototype")?{kind:"prototype",since:t.prototypeAt,next:"Draw the prototype asked for"}:u?{kind:"unasked",since:u.at,next:`Its ${u.kind} planner ended without its question: read its log, then post the question, start the planner again, or move the card's phase`}:e.recheckAt&&e.lane==="todo"&&e.phase==="ready"&&!e.blocked?{kind:"recheck",since:e.recheckAt,next:no}:e.refusal&&t.refusedAt?{kind:"refused",since:t.refusedAt,next:e.refusal}:e.next===$t&&a?{kind:"unowned",since:a,next:"Start a builder on it, or set its Next"}:t.unblockedAt&&e.lane!=="todo"&&!e.builder&&!e.blocked&&!t.writes.some(f=>f>t.unblockedAt)?{kind:"unblocked",since:t.unblockedAt,next:"Its block ended: act on what it waited for, or set its Next"}:null;if(!i?.since)return null;const p=String(i.since),m=[t.writes.find(f=>f>p),s].filter(Boolean).sort()[0]||null;return{kind:i.kind,since:p,picked:m,next:i.next,...i.daily?{daily:!0}:{}}}d(so,"orchestratorWaitOf");const Uo=d((e,t=null)=>({key:e.id,ref:e.data.date,title:`Overnight triage ${e.data.date}`,kind:"triage",since:e.ts,picked:t,next:`Run the overnight triage and reply: board.mjs reply ${e.id}`}),"triageWaitOf");function jo(e){const t=e.items.filter(n=>n.orchestrator).map(n=>({key:n.key,ref:n.ref,title:n.title,...n.orchestrator,bumpedAt:n.bumpedAt||null}));return[...t.filter(n=>n.bumpedAt).sort((n,s)=>n.bumpedAt.localeCompare(s.bumpedAt)),...t.filter(n=>!n.bumpedAt)]}d(jo,"orchestratorWaits");const Wo=30*6e4,oo=15*6e4,ro=60*6e4,ao=1440*6e4;function Fo(e,t,n){const s=n.toISOString(),r=[],a=[];return{waits:t.map(i=>{const p=e.find(c=>c.key===i.key&&c.kind===i.kind&&c.since===i.since);if(!p)return i.picked||r.push(i.key),{...i,bumped:s};const m=i.picked?[i.picked,p.bumped].filter(Boolean).sort().pop():p.bumped||i.since,f=n-Date.parse(m)>=(i.picked?i.daily?ao:ro:oo);return f&&a.push({key:i.key,ref:i.ref,kind:i.kind,since:i.since,next:i.next}),{...i,bumped:f?s:p.bumped||i.since,...p.pushed?{pushed:!0}:{}}}),added:r,again:a}}d(Fo,"waitsStep");function Ko(e,t){const n=d(s=>{const r=Math.floor((t-Date.parse(s))/6e4),a=Math.floor(r/60),u=r%60;return a?`${a}h${u?` ${u}m`:""}`:`${u}m`},"age");return`still waiting on you: ${e.map(s=>`${s.ref} ${s.kind} for ${n(s.since)}: ${s.next}`).join("; ")}`}d(Ko,"waitsAgainLine");const lo=15;function Go(e,{idle:t,now:n}){const s=n.toISOString(),r=e.last||s;return t?e.since?e.pushed||n-Date.parse(e.since)<lo*6e4?{state:{...e,last:r},push:!1}:{state:{last:s,since:e.since,pushed:!0},push:!0}:{state:{last:r,since:s},push:!1}:{state:{last:r},push:!1}}d(Go,"idleStep");function Qo(e,t){const n=d(p=>String(p.closedAt||"")>t,"after"),s=e.filter(p=>p.kind==="work"&&p.lane==="done"&&!St(p)&&n(p)),r=new Map;for(const p of e)p.wave&&r.set(p.wave.slug,[...r.get(p.wave.slug)||[],p]);const a=[...r.values()].filter(p=>p.every(m=>m.lane==="done")&&p.some(n)).map(p=>p[0].wave.title),u=s.slice(0,3).map(p=>p.ref).join(", ")+(s.length>3?` and ${s.length-3} more`:"");return[s.length&&`${s.length} ${s.length===1?"card":"cards"} landed: ${u}`,...a.map(p=>`${p} finished`)].filter(Boolean).join(" \xB7 ")||"Nothing closed since the last idle notification."}d(Qo,"idleSummary");export{q as ACTION_KIND,It as ACTION_LABELS,fs as ANSWERS_SUBMITTED,De as BUILDER_ASK,ue as BUILDER_KIND,hn as BUILDER_QUIET_AFTER,mn as BUILDER_STALE_AFTER,ht as BUILD_REFUSED,ce as BUTTON,Xe as COMMANDS,ao as DAILY_REWAKE_AFTER,Xs as DONE_FIELDS,Vs as DONE_KEPT,ge as HELPER,lo as IDLE_MINUTES,qt as INTENT_LABELS,kn as ITEM_EVENTS,He as LANDER,So as LANES,Ls as MAX_IMPLEMENTERS,Us as MAX_PLANNERS,Ro as MAX_SERVES,$e as MOVE,$t as NO_NEXT,Wo as ORCHESTRATOR_PUSH_AFTER,Eo as PAUSE_CLEARS,ns as PAUSE_HARD_STOP,kt as PHASES,ro as PICKED_REWAKE_AFTER,zn as PLANNER_WORD,Gn as PROTOTYPE_AGAIN,xo as PROTOTYPE_WAIT,X as PR_STATE,mt as QUESTION_REOPEN,oo as REWAKE_AFTER,fe as VISUAL,No as archivedOf,Mo as arrivalsOf,Ao as buildBoard,H as builds,$o as confirmedDrops,yn as fileFold,st as handedBackAt,Go as idleStep,Qo as idleSummary,Tn as mergeBlockers,as as modelName,Lo as movesOf,Bo as needsAnswer,Kn as noTakePicked,Jn as orchestratorDid,jo as orchestratorWaits,vo as owesNext,To as ownerActions,ls as ownerQuestion,rs as pauseWork,Fn as picksNoTake,ee as plans,Oo as prototypeWaits,_t as providerOf,Ts as pullOf,Zs as pushTag,_o as rebasesDue,nt as refOf,ke as roundOf,is as roundSettledBy,Io as roundsDue,Do as slimDone,Ut as stateEntries,At as stoppedWords,Po as strictlyIdle,Tt as threadIndex,vs as threadOf,Hs as todoOf,Co as triageOf,Uo as triageWaitOf,St as unlanded,js as visualAgent,qo as visualsDue,Ko as waitsAgainLine,Fo as waitsStep,$s as waveItems,As as withHub};
@@ -417,28 +417,51 @@ BoardView.part((B) => {
417
417
  }
418
418
  const off = ev.target.closest('a[data-tip]');
419
419
  if (off) { ev.preventDefault(); toast(off.dataset.tip); return; }
420
- const button = ev.target.closest('button[data-try]');
420
+ const kill = ev.target.closest('button[data-try-kill]');
421
+ const button = kill || ev.target.closest('button[data-try]');
421
422
  if (!button) return;
422
423
  ev.preventDefault();
423
424
  if (button.dataset.tip) { toast(button.dataset.tip); return; }
424
- const key = button.dataset.try;
425
+ const key = kill ? kill.dataset.tryKill : button.dataset.try;
425
426
  const it = state.board.items.find((i) => i.key === key);
426
- if (!it || !(await B.confirm(`${tryWords(it.try?.kind).go} ${it.ref}? The hub runs its build script.`)) || !B.alive()) return;
427
- // Serve / Build spins until a board shows the card's build changed: running, or already done
427
+ if (!it) return;
428
+ // A web card's Serve asks the busy host's question in place of its usual confirm, and the hub's own refusal for a
429
+ // busy host or the board's serve cap is a dialog too
430
+ // (docs/adr/2026-11-12-0112-a-card-serve-stays-up-while-its-worktree-does.md).
431
+ const busyAsk = 'The host is busy, start this serve anyway?';
432
+ const web = it.try?.kind === 'web';
433
+ let anyway = !kill && web && it.try?.hostBusy === true;
434
+ const asked = kill ? `Kill ${it.ref}’s serve? Its server stops until you tap Serve.` : anyway ? busyAsk : `${tryWords(it.try?.kind).go} ${it.ref}? The hub runs its build script.`;
435
+ if (!(await B.confirm(asked)) || !B.alive()) return;
436
+ // Serve / Build and Kill spin until a board shows the card's build changed: running, done or stopped
428
437
  // (docs/adr/2026-11-05-0200-a-tap-spins-until-the-board-shows-its-change.md).
429
438
  const before = JSON.stringify(it.try);
430
439
  const id = `${key}:try`;
431
440
  BoardUI.pending.start(id, (board) => JSON.stringify(board.items.find((i) => i.key === key)?.try) !== before, B.stillWaiting);
432
441
  B.render();
442
+ const send = async () => {
443
+ const r = await fetch(B.site.url(kill ? 'api/try/kill' : 'api/try'), { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ key, ...(anyway ? { anyway } : {}) }) });
444
+ return { ok: r.ok, out: await r.json() };
445
+ };
433
446
  try {
434
- const r = await fetch(B.site.url('api/try'), { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ key }) });
435
- const out = await r.json();
436
- if (!r.ok) throw new Error(out.error || 'The hub refused the build.');
447
+ let { ok, out } = await send();
448
+ if (!ok && out.ask === 'busy' && !anyway) {
449
+ if (!(await B.confirm(busyAsk)) || !B.alive()) { BoardUI.pending.drop(id); B.render(); return; }
450
+ anyway = true;
451
+ ({ ok, out } = await send());
452
+ }
453
+ if (!ok && out.ask === 'cap') {
454
+ BoardUI.pending.drop(id);
455
+ B.render();
456
+ await B.notice(out.error);
457
+ return;
458
+ }
459
+ if (!ok) throw new Error(out.error || (kill ? 'The hub refused the Kill.' : 'The hub refused the build.'));
437
460
  BoardUI.pending.answered(id);
438
461
  } catch (error) {
439
462
  BoardUI.pending.drop(id);
440
463
  B.render();
441
- toast(error.message || 'Could not start. Is the host awake?');
464
+ toast(error.message || (kill ? 'Could not kill it. Is the host awake?' : 'Could not start. Is the host awake?'));
442
465
  }
443
466
  B.reload();
444
467
  });
@@ -3,6 +3,7 @@
3
3
  // with board.mjs prototype add (docs/adr/2026-11-01-1448-the-board-serves-every-prototype-from-one-template.md). A take file,
4
4
  // public/prototypes/<topic>/<KEY>.js, is a module:
5
5
  // export function render(stage, kit) { stage.innerHTML = kit.hub(); return () => {…} } // the cleanup is optional
6
+ // A take draws any board class at any width: prototype.html undoes the board's width-gated hides in the stage (.needs-deck, .needs-pane).
6
7
  // render runs on every switch to that take with a fresh, empty stage; it may be async. The stage contains its layout
7
8
  // and paint, so nothing in it, position: fixed included, can cover the viewer's bar, its ✕ or the app shell.
8
9
  // kit.sidebar(data?, {here, switching, device, head, height}) ui-sidebar.js sidebar() as an open drawer card in the
@@ -49,6 +49,12 @@
49
49
  width: min(320px, 100%); height: 640px; margin: 0 auto; padding: 0 8px; border: 1px solid var(--line); border-radius: 14px; }
50
50
  #stage .kit-sidebar.sidebar:not(.kit-head) > header.top { display: none; }
51
51
  @media (min-width: 900px) { #stage .kit-sidebar.sidebar { width: 272px; } }
52
+ /* Everything a take draws shows at every width: the board's width-gated hides are undone here (style.css hides
53
+ .needs-deck from 900px and .needs-pane below it, .key-hint below 600px). render-check.mjs refuses a take whose box is
54
+ still hidden at one of its two widths, so a gate missed here fails the round instead of drawing blank. */
55
+ #stage .needs-deck { display: block; }
56
+ #stage .needs-pane { display: block; position: static; height: auto; overflow: visible; border-left: 0; padding: 0; }
57
+ #stage .key-hint { display: inline; }
52
58
  #stage .kit-sheet-strip { padding: 32px 0 0; border-radius: 12px; background: rgba(0,0,0,.35); overflow: hidden; }
53
59
  #stage .kit-sheet { max-width: 560px; margin: 0 auto; border: 1px solid var(--line); border-bottom: 0; border-radius: 16px 16px 0 0; background: var(--panel); color: var(--text); box-shadow: 0 -8px 30px rgba(0,0,0,.18); }
54
60
  #stage .kit-sheet .sheet-head { padding: 10px 16px 0; }
@@ -468,7 +468,7 @@ button.attach[aria-pressed="true"] { color: var(--accent); background: var(--chi
468
468
  .view-only-strip b { color: var(--accent); }
469
469
  /* A board out of reach (docs/adr/2026-11-06-0135-a-board-out-of-reach-keeps-its-copy-or-shows-a-landing.md): it reads as before,
470
470
  undimmed, and only the buttons that send, ui.js SENDS, are held, the labelled ones saying why; app-header.js stops their taps. */
471
- .offline :is(form button[type="submit"], .actions button, button[data-confirm], button[data-deck-pick], button[data-review-answer], button[data-prompt-send], button[data-prompt-choice], button[data-quick], button[data-card-bump], button[data-bump], button[data-withdraw], button[data-grill], button[data-proto], button[data-visual], button[data-plan], button[data-main-pull], button[data-arch-run], button[data-try], button[data-reopen]) { opacity: .55; cursor: not-allowed; }
471
+ .offline :is(form button[type="submit"], .actions button, button[data-confirm], button[data-deck-pick], button[data-review-answer], button[data-prompt-send], button[data-prompt-choice], button[data-quick], button[data-card-bump], button[data-bump], button[data-withdraw], button[data-grill], button[data-proto], button[data-visual], button[data-plan], button[data-main-pull], button[data-arch-run], button[data-try], button[data-try-kill], button[data-reopen]) { opacity: .55; cursor: not-allowed; }
472
472
  /* With no copy to show, the landing takes the board's place: the kickflip mark and two lines, as hub/front-door.mjs's page has them. */
473
473
  .down-landing { min-height: 60vh; display: grid; place-content: center; justify-items: center; gap: 10px; padding: 32px 24px; text-align: center; }
474
474
  .down-landing-mark { display: block; margin-bottom: 8px; width: 70px; height: 60px; object-fit: contain; }
@@ -866,7 +866,7 @@ button[aria-busy="true"]:is([data-card-bump]) svg { fill: currentColor; }
866
866
  .tb-main-text { flex: 1 1 0; min-width: 0; overflow-wrap: anywhere; }
867
867
  .tb-main .meta { justify-content: flex-end; margin-left: auto; }
868
868
  /* #60: Main's refresh icon right of its timestamp; a failed pull's reason shows on hover, or on a tap on touch */
869
- .tb-main .main-pull { position: relative; display: inline-flex; align-items: center; vertical-align: middle; margin-left: 6px; }
869
+ .tb-main .main-pull { position: relative; display: inline-flex; align-items: center; vertical-align: text-bottom; margin: -7px 0 -7px 6px; }
870
870
  .tb-main .gh-refresh { display: inline-flex; align-items: center; justify-content: center; font-size: 15px; line-height: 1; padding: 2px 4px; color: var(--muted); min-width: 28px; min-height: 28px; }
871
871
  .tb-main .gh-refresh[aria-disabled="true"] { opacity: .45; cursor: default; }
872
872
  .tb-main .gh-refresh.failed { color: var(--color-danger); }
@@ -125,9 +125,14 @@
125
125
  return `<${tag} class="install tone-${s.tone}"${attrs}>${s.html}</${tag}>`;
126
126
  }
127
127
  // A card's Serve / Build, only while the board's Auto-build is off and its branch has changes
128
- // (docs/adr/2026-11-02-1806-only-the-owner-or-auto-build-starts-a-card-run.md).
128
+ // (docs/adr/2026-11-02-1806-only-the-owner-or-auto-build-starts-a-card-run.md). A website card's serve that is running
129
+ // or up has Kill in its place, Auto serve on or off
130
+ // (docs/adr/2026-11-12-0112-a-card-serve-stays-up-while-its-worktree-does.md).
129
131
  function tryButton(it) {
130
132
  const t = tryOf(it);
133
+ if (it.kind === 'work' && t?.kind === 'web' && ['running', 'ready'].includes(t.state)) {
134
+ return `<button type="button" data-try-kill="${esc(it.key)}"${BoardUI.pending.has(`${it.key}:try`) ? BoardUI.BUSY : ''}>Kill</button>`;
135
+ }
131
136
  if (it.kind !== 'work' || !t?.script || t.autoBuild || !t.changes) return '';
132
137
  const words = tryWords(t.kind);
133
138
  const reason = t.state === 'running' ? words.busy : t.state === 'ready' && !t.stale ? words.current : '';
@@ -410,7 +410,7 @@
410
410
  const copyNote = (what, savedAt) => `<p class="note copy-note">Copy of ${esc(what)} as of ${since(savedAt)}. Read only.</p>`;
411
411
  // The buttons that send, which a board out of reach holds (style.css .offline): every one greyed and stopped, keeping its
412
412
  // label and size. Reading controls, folds, links, Edit and drafts stay live.
413
- const SENDS = `form button[type="submit"], .actions button, button[data-confirm], button[data-deck-pick], button[data-review-answer], button[data-prompt-send], button[data-prompt-choice], button[data-quick], button[data-card-bump], button[data-bump], button[data-withdraw], button[data-grill], button[data-proto], button[data-visual], button[data-plan], button[data-main-pull], button[data-arch-run], button[data-try], button[data-reopen]`;
413
+ const SENDS = `form button[type="submit"], .actions button, button[data-confirm], button[data-deck-pick], button[data-review-answer], button[data-prompt-send], button[data-prompt-choice], button[data-quick], button[data-card-bump], button[data-bump], button[data-withdraw], button[data-grill], button[data-proto], button[data-visual], button[data-plan], button[data-main-pull], button[data-arch-run], button[data-try], button[data-try-kill], button[data-reopen]`;
414
414
  // While held(), a tap on one of root's sending buttons, and any form's submit there, Enter's included, goes nowhere, and onHeld
415
415
  // tells the tap it is waiting. on is the view's B.on.
416
416
  function holdSends(on, root, held, onHeld) {
@@ -51,6 +51,8 @@
51
51
  let topics = [];
52
52
  let topicId = null;
53
53
  let soloId = null;
54
+ // Counts the owner's own topic picks (a pill, or the shell opening a topic), which a Send in flight compares against.
55
+ let picks = 0;
54
56
  let shown = '';
55
57
  let sentLine = '';
56
58
  // The shown topic's questions this page has drawn unsent, which stay in place once sent or resolved until you leave
@@ -157,15 +159,17 @@
157
159
  // The topic a tap on this page acts for, the pills before it and its name, which moveOn reads once it is recorded.
158
160
  function leaving() {
159
161
  const from = soloId ? topicOf(soloId)?.id : topicId;
160
- return { from, pills: topicRow(topics, from).pills, name: toastName(topics.find((t) => t.id === from) || { title: '' }) };
162
+ return { from, picks, pills: topicRow(topics, from).pills, name: toastName(topics.find((t) => t.id === from) || { title: '' }) };
161
163
  }
162
164
  // After a recorded tap: a load that started before it went out would read its question as still open.
163
165
  // `footer` is a line or, for a Send, a function of the reloaded board giving the whole line.
164
166
  // The tap's POST answers with the board as it left it, which lands here in place of a reload.
165
- async function moveOnOnceLoaded({ from, pills }, what, out, footer = what) {
167
+ async function moveOnOnceLoaded({ from, pills, picks: at }, what, out, footer = what) {
166
168
  await loading;
167
169
  if (!B.alive()) return;
168
170
  if (!land(out.board, false)) await load(false);
171
+ // Tapped to another topic meanwhile: the page stays there and only the toast says what was recorded.
172
+ if (picks !== at) { B.toast(what); return; }
169
173
  moveOn(from, pills, what, typeof footer === 'function' ? footer() : footer, typeof footer !== 'function');
170
174
  }
171
175
 
@@ -386,7 +390,7 @@
386
390
  const b = ev.target.closest && ev.target.closest('[data-topic]');
387
391
  if (b) {
388
392
  // Embedded in the desktop pane, one click also focuses the topic's card on the board; a phone only picks the topic.
389
- soloId = null; setTopic(b.dataset.topic); sentLine = ''; shown = '';
393
+ picks++; soloId = null; setTopic(b.dataset.topic); sentLine = ''; shown = '';
390
394
  const first = topics.find((t) => t.id === topicId)?.questions[0];
391
395
  if (first) setHash(first);
392
396
  ctx.onTopic?.(topicId);
@@ -468,7 +472,7 @@
468
472
  if (ctx.embedded) B.openTopic = (id) => {
469
473
  if (!loaded) return false;
470
474
  const topic = topics.find((t) => t.id === id);
471
- if (topic) { soloId = null; setTopic(topic.id); } else pick(id || '');
475
+ if (topic) { picks++; soloId = null; setTopic(topic.id); } else pick(id || '');
472
476
  ctx.onTopic?.(topicId);
473
477
  sentLine = '';
474
478
  shown = '';