@xauyxau/hnch 1.6.0 → 1.6.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/dist/index.js +4 -4
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
3
|
-
`,"utf-8");}async function
|
|
4
|
-
`,"utf-8");}var Do={"unknown-room":4001,"wrong-password":4002,"invalid-invite":4003,"expired-invite":4004,"invite-already-redeemed":4005,"room-full":4006,"malformed-credentials":4010},on=new Map(Object.entries(Do).map(([e,t])=>[t,e]));function he(e){return e.role??"voter"}function G(e){let{revealedAt:t,skippedAt:n}=e;return t===void 0&&n===void 0?null:t===void 0?"skipped":n===void 0||t<=n?"revealed":"skipped"}var ze={id:"fibonacci",cards:["0","1","2","3","5","8","13","21"]},rn={id:"t-shirt",cards:["XS","S","M","L","XL"]},sn={id:"powers-of-2",cards:["1","2","4","8","16","32","64"]},an={id:"linear",cards:["1","2","3","4","5","6","7","8","9","10"]},cn={id:"risk",cards:["Low","Medium","High","Critical"]},te={[ze.id]:ze,[rn.id]:rn,[sn.id]:sn,[an.id]:an,[cn.id]:cn},dn={fibonacci:"Fibonacci","t-shirt":"T-shirt","powers-of-2":"Powers of 2",linear:"Linear (1\u201310)",risk:"Risk"};function Se(e){return e in dn?dn[e]:e.length===0?e:(e.startsWith("custom-")?e.slice(7):e).split("-").map(n=>n.length===0?n:n.charAt(0).toUpperCase()+n.slice(1)).join(" ")}var Mo="meta",ke="settings",ko="participants",$o="rounds",Bo=["roomUuid","createdAt","createdBy","sessionEndedAt"],Ho=["name","defaultScale","hostParticipantId"],Uo=["displayName","joinedAt","role","leftAt","removedBy"],jo=["id","title","description","ticketId","parentRoundId","scale","timerStartedAt","revealedAt","skippedAt","createdBy","createdAt"];function Qe(e){return e.getMap(Mo)}function Ke(e){return e.getMap(ke)}function Xe(e){return e.getMap(ko)}function Tt(e){return e.getArray($o)}function Ze(e,t){let n={};for(let o of t){let r=e.get(o);r!==void 0&&(n[o]=r);}return n}function Lo(e){return Ze(Qe(e),Bo)}function _o(e){return Ze(Ke(e),Ho)}function Fo(e){let t=Xe(e),n=[];return t.forEach((o,r)=>{let s=Ze(o,Uo);n.push({id:r,...s});}),n.sort((o,r)=>o.joinedAt-r.joinedAt||o.id.localeCompare(r.id)),n}function Vo(e){let t={};return e.forEach((n,o)=>{t[o]=n;}),t}function Yo(e){let t=Ze(e,jo),n=e.get("votes"),o=n?Vo(n):{};return {...t,votes:o}}function Jo(e){let t=Tt(e),n=[];return t.forEach(o=>{n.push(Yo(o));}),n}function z(e){return {meta:Lo(e),settings:_o(e),participants:Fo(e),rounds:Jo(e)}}function ln(e,t){if(Qe(e).has("roomUuid"))throw new Error("initDoc: doc already seeded");e.transact(()=>{let n=Qe(e);n.set("roomUuid",t.roomUuid),n.set("createdAt",t.createdAt),n.set("createdBy",t.participantId);let o=Ke(e);t.sessionName!==void 0&&o.set("name",t.sessionName),o.set("defaultScale",t.defaultScale),o.set("hostParticipantId",t.participantId);let r=new Ie.Map;r.set("displayName",t.displayName),r.set("joinedAt",t.createdAt),Xe(e).set(t.participantId,r);});}function et(e,t){e.transact(()=>{let n=Xe(e),o=n.get(t.participantId),r=o??new Ie.Map;r.set("displayName",t.displayName),o===void 0&&r.set("joinedAt",Date.now()),r.delete("leftAt"),r.delete("removedBy"),o===void 0&&n.set(t.participantId,r);});}function Te(e,t){e.transact(()=>{let n=Ke(e);for(let[o,r]of Object.entries(t))r===void 0?n.delete(o):n.set(o,r);});}function Et(e,t,n){e.transact(()=>{let o=Xe(e),r=o.get(t),s=r??new Ie.Map;s.set("displayName",n.displayName),s.set("joinedAt",n.joinedAt),n.role!==void 0&&s.set("role",n.role),r===void 0&&o.set(t,s);});}function At(e,t){let n=t.scale??Ke(e).get("defaultScale");if(!n)throw new Error("appendRound: no scale (settings.defaultScale missing)");return e.transact(()=>{let o=new Ie.Map;o.set("id",t.id),o.set("createdAt",t.createdAt),o.set("createdBy",t.createdBy),o.set("scale",n),t.title!==void 0&&o.set("title",t.title),t.description!==void 0&&o.set("description",t.description),t.ticketId!==void 0&&o.set("ticketId",t.ticketId),t.parentRoundId!==void 0&&o.set("parentRoundId",t.parentRoundId),o.set("timerStartedAt",t.createdAt),o.set("votes",new Ie.Map),Tt(e).push([o]);}),t.id}function un(e,t,n){let o=$e(e,t);if(!o)throw new Error(`patchRoundDescriptor: round ${t} not found`);e.transact(()=>{n.title!==void 0&&o.set("title",n.title),n.description!==void 0&&o.set("description",n.description);});}function $e(e,t){let n=Tt(e);for(let o=0;o<n.length;o+=1){let r=n.get(o);if(r.get("id")===t)return r}}function pn(e,t,n,o){let r=$e(e,t);if(!r)throw new Error(`castVote: round ${t} not found`);r.get("revealedAt")===void 0&&e.transact(()=>{r.get("votes").set(n,o);});}function fn(e,t,n){let o=$e(e,t);if(!o)throw new Error(`clearVote: round ${t} not found`);o.get("revealedAt")===void 0&&e.transact(()=>{o.get("votes").delete(n);});}function Nt(e,t,n){let o=$e(e,t);if(!o)throw new Error(`revealRound: round ${t} not found`);if(o.get("skippedAt")!==void 0)throw new Error(`revealRound: round ${t} already skipped`);e.transact(()=>{o.set("revealedAt",n);});}function mn(e,t,n){let o=$e(e,t);if(!o)throw new Error(`skipRound: round ${t} not found`);if(o.get("revealedAt")!==void 0)throw new Error(`skipRound: round ${t} already revealed`);if(o.get("skippedAt")!==void 0)throw new Error(`skipRound: round ${t} already skipped`);e.transact(()=>{o.set("skippedAt",n);});}function gn(e){e.transact(()=>{Qe(e).set("sessionEndedAt",Date.now());});}function X(e){let{rounds:t}=e;if(t.length===0)return null;for(let n=t.length-1;n>=0;n-=1){let o=t[n];if(o.revealedAt===void 0)return o}return t.at(-1)}function hn(e){let t=Object.values(e.votes);return t.length<2?false:t.every(n=>n===t[0])}var Sn="PBKDF2",Wo="SHA-256",xn=new TextEncoder;function qo(e){return Array.from(new Uint8Array(e)).map(t=>t.toString(16).padStart(2,"0")).join("")}async function xe(e,t){let n=await crypto.subtle.importKey("raw",xn.encode(e),Sn,false,["deriveBits"]),o=await crypto.subtle.deriveBits({name:Sn,salt:xn.encode(t),iterations:1e5,hash:Wo},n,256);return qo(o)}var wn="ABCDEFGHJKMNPQRSTUVWXYZ23456789";if(wn.length!==31)throw new Error(`Room code alphabet must be 31 characters, got ${wn.length}`);function Pt(e){return e.trim().replaceAll(/-/g,"").toUpperCase()}function yn(e,t=true){let n=Pt(e);return !t||n.length!==6?n:`${n.slice(0,3)}-${n.slice(3)}`}function tt(e,t,n){if(e.startsWith("custom-")){let r=(n??[]).find(s=>s.id===e);return r?{id:r.id,cards:r.cards}:void 0}let o=te[e];if(o){let r=(t??{})[e]??[];if(r.length===0)return o;let s=o.cards.filter((p,a)=>!r.includes(a));return s.length===0?void 0:{id:e,cards:s}}}function nt(e,t){let n=Object.keys(te).map(r=>tt(r,e,t)).filter(r=>r!==void 0),o=(t??[]).map(r=>({id:r.id,cards:r.cards}));return [...n,...o]}async function vn(e,t){let n=await fetch(`${e}/rooms`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({})});if(!n.ok)throw new Error(`POST /rooms failed: ${n.status}`);return await n.json()}async function Be(e,t){let n=await fetch(`${e}/rooms/${encodeURIComponent(t)}`);if(n.status===404){let o=await n.json().catch(()=>null);throw new Error(`Room not found: ${o?.reason??"unknown-room"}`)}if(!n.ok)throw new Error(`GET /rooms/:code failed: ${n.status}`);return await n.json()}async function Ot(e,t,n){let o=await fetch(`${e}/rooms/${t}/access`,{method:"PATCH",headers:{"content-type":"application/json"},body:JSON.stringify({passwordHash:n})});if(!o.ok)throw new Error(`PATCH /rooms/:uuid/access failed: ${o.status}`)}function Cn(e){let n=`${e.wsBaseUrl.replace(/^http/,"ws")}/ws`,o={did:e.deviceId};e.passwordHash!==void 0&&(o.pw=e.passwordHash);let r=new WebsocketProvider(n,e.roomUuid,e.doc,{params:o,connect:true,...e.awareness!==void 0&&{awareness:e.awareness},WebSocketPolyfill:zo}),s=u=>{e.onStatusChange?.(u.status);},p=u=>{if(u===null)return;let i=on.get(u.code);i!==void 0&&(e.onRejection?.(i),r.shouldConnect=false,r.disconnect());};r.on("status",s),r.on("connection-close",p);let a=false,l=e.passwordHash;return {doc:e.doc,awareness:r.awareness,updateCredentials(u){if(u.passwordHash===l)return;l=u.passwordHash??void 0,r.shouldConnect=false,r.disconnect();let c={did:r.params.did??""};l!==void 0&&(c.pw=l),r.params=c,r.connect();},disconnect(){a||(a=true,r.off("status",s),r.off("connection-close",p),r.shouldConnect=false,r.disconnect(),r.destroy());}}}var Qo=5e3;function bn(e,t,n){let o=null,r=null;function s(){let i=new Set;return t.getStates().forEach(c=>{let h=c.participantId;typeof h=="string"&&i.add(h);}),i}function p(){return e.getMap(ke).get("hostParticipantId")}function a(){r!==null&&(clearTimeout(r),r=null),o=null;}function l(){let i=p(),c=o;if(o=null,!c||i!==c)return;let h=s();if(h.has(i))return;let E=[...h].toSorted();if(E.length===0)return;let P=E[0];P===n&&Te(e,{hostParticipantId:P});}function u(){let i=p();if(!i)return;if(s().has(i)){a();return}o!==i&&(a(),o=i,r=setTimeout(()=>{r=null,l();},Qo));}return t.on("change",u),e.getMap(ke).observe(u),u(),{stop(){a(),t.off("change",u),e.getMap(ke).unobserve(u);}}}async function Tn(e){let t,n=await vn(e.relayUrl);e.password&&(t=await xe(e.password,n.uuid),await Ot(e.relayUrl,n.uuid,t));let o=new Ie.Doc,r=new Awareness(o),s=Date.now();return ln(o,{roomUuid:n.uuid,createdAt:s,participantId:e.participantId,displayName:e.displayName,defaultScale:e.defaultScale??ze,...e.sessionName!==void 0&&{sessionName:e.sessionName}}),r.setLocalStateField("participantId",e.participantId),ot({doc:o,awareness:r,roomCode:n.code,roomUuid:n.uuid,participantId:e.participantId,relayUrl:e.relayUrl,passwordHash:t})}async function En(e){let t=await Be(e.relayUrl,e.roomCode),n;if(t.accessMode==="password"){if(!e.password)throw new Error("Room requires a password");n=await xe(e.password,t.uuid);}let o=new Ie.Doc,r=new Awareness(o);return et(o,{participantId:e.participantId,displayName:e.displayName}),r.setLocalStateField("participantId",e.participantId),ot({doc:o,awareness:r,roomCode:e.roomCode,roomUuid:t.uuid,participantId:e.participantId,relayUrl:e.relayUrl,passwordHash:n,onRejection:e.onRejection})}function ot(e){let{doc:t,awareness:n,participantId:o,relayUrl:r,roomUuid:s}=e,p=new Set,a=new Set,l=new Set,u=Cn({wsBaseUrl:e.relayUrl,roomUuid:e.roomUuid,doc:t,deviceId:o,passwordHash:e.passwordHash,awareness:n,onStatusChange(d){p.forEach(m=>m(d));},onRejection(d){console.error(`Connection rejected: ${d}`),e.onRejection?.(d);}}),i=bn(t,n,o),c=()=>{a.forEach(d=>d());};t.getMap("meta").observeDeep(c),t.getMap("settings").observeDeep(c),t.getMap("participants").observeDeep(c),t.getArray("rounds").observeDeep(c);function h(){let d=new Set;return n.getStates().forEach(m=>{let v=m.participantId;typeof v=="string"&&d.add(v);}),d}let E=new Set,P=setInterval(()=>{let d=z(t),m=X(d);if(!m||m.revealedAt!==void 0||E.has(m.id))return;let v=h();if(v.size===0)return;let w=d.participants.filter(f=>v.has(f.id)&&he(f)==="voter");if(w.length===0)return;w.filter(f=>f.id in m.votes).length>=w.length&&(E.add(m.id),Nt(t,m.id,Date.now()));},250),T=t.getMap("settings").get("hostParticipantId"),I=()=>{let d=t.getMap("settings").get("hostParticipantId");d!==T&&d!==void 0&&l.forEach(m=>m(d)),T=d;};t.getMap("settings").observe(I);let H=false;return {doc:t,awareness:n,roomCode:e.roomCode,roomUuid:e.roomUuid,projection(){return z(t)},current(){return X(z(t))},isHost(){return z(t).settings.hostParticipantId===o},onlineIds:h,vote(d){let m=X(z(t));m&&pn(t,m.id,o,d);},unvote(){let d=X(z(t));d&&fn(t,d.id,o);},reveal(){let d=X(z(t));d&&Nt(t,d.id,Date.now());},skip(){let d=X(z(t));d&&mn(t,d.id,Date.now());},newRound(d){let m=randomUUID();return At(t,{id:m,createdAt:Date.now(),createdBy:o,...d!==void 0&&{title:d}}),m},revote(){let d=X(z(t)),m=randomUUID();return At(t,{id:m,createdAt:Date.now(),createdBy:o,...d?.id!==void 0&&{parentRoundId:d.id},...d?.title!==void 0&&{title:d.title},...d?.description!==void 0&&{description:d.description},...d?.ticketId!==void 0&&{ticketId:d.ticketId}}),m},updateRoundDescriptor(d,m){un(t,d,m);},updateName(d){Te(t,{name:d});},updateScale(d){Te(t,{defaultScale:d});},updateDisplayName(d){let w=z(t).participants.find(M=>M.id===o)?.joinedAt??Date.now();Et(t,o,{displayName:d,joinedAt:w});},async updateAccess(d,m){let v=null;if(d==="password"){if(!m)throw new Error("Password required for password mode");v=await xe(m,s);}await Ot(r,s,v),u.updateCredentials({passwordHash:v});},handoffHost(d){if(!this.isHost())throw new Error("Only the current host can hand off");if(!h().has(d))throw new Error("Target participant is not online");if(!z(t).participants.some(M=>M.id===d))throw new Error("Target participant does not exist");Te(t,{hostParticipantId:d});},toggleRole(){let m=z(t).participants.find(f=>f.id===o),w=he(m??{})==="viewer"?"voter":"viewer",M=m?.joinedAt??Date.now();Et(t,o,{displayName:m?.displayName??"",joinedAt:M,role:w});},endSessionForEveryone(){if(!this.isHost())throw new Error("Only the current host can end the session");gn(t);},onChange(d){return a.add(d),()=>a.delete(d)},onStatusChange(d){return p.add(d),()=>p.delete(d)},onHostChange(d){return l.add(d),()=>l.delete(d)},disconnect(){H||(H=true,clearInterval(P),i.stop(),t.getMap("meta").unobserveDeep(c),t.getMap("settings").unobserveDeep(c),t.getMap("settings").unobserve(I),t.getMap("participants").unobserveDeep(c),t.getArray("rounds").unobserveDeep(c),u.disconnect());}}}function An(e){let t=Pt(e);return t===""?null:t}var or=1440*60*1e3;function Mt(){return join(Oe(),"last-session.json")}function rr(e){if(typeof e!="object"||e===null)return false;let t=e;return typeof t.roomCode=="string"&&typeof t.roomUuid=="string"&&typeof t.lastSeenAt=="number"&&(t.passwordHash===void 0||typeof t.passwordHash=="string")}function Pn(){let e=Mt();if(!existsSync(e))return null;try{let t=JSON.parse(readFileSync(e,"utf-8"));return !rr(t)||Date.now()-t.lastSeenAt>or?(F(),null):t}catch{return F(),null}}function rt(e){let t=Oe();mkdirSync(t,{recursive:true,mode:448}),chmodSync(t,448);let n=Mt();writeFileSync(n,JSON.stringify(e,null,2)+`
|
|
5
|
-
`,{encoding:"utf-8",mode:384}),chmodSync(n,384);}function F(){let e=Mt();try{unlinkSync(e);}catch(t){if(t.code!=="ENOENT")throw t}}async function st(e){let t=true;try{await e.clearLastSession();}catch(n){t=false,e.logger.error(`Failed to clear cached session: ${n instanceof Error?n.message:String(n)}`);}return t?e.logger.error("Session expired (room no longer exists). Cached session cleared."):e.logger.error("Session expired. Could not delete ~/.config/hnch/last-session.json \u2014 please remove it manually."),e.exit(1)}function it(e){let t=e.doc.getMap("meta"),n=null,o=false;function r(){if(o)return;let s=t.get("sessionEndedAt");if(s==null||s===n)return;n=s;let p=t.get("createdBy"),a=p===e.localParticipantId,l=e.doc.getMap("participants"),u=e.doc.getMap("settings").get("hostParticipantId")??p,i="host";if(u){let h=l.get(u)?.get("displayName");typeof h=="string"&&h.length>0&&(i=h);}setImmediate(()=>{o||e.onEnded({isLocal:a,hostName:i,timestamp:s});});}return t.observe(r),r(),()=>{o=true,t.unobserve(r);}}var sr="@xauyxau/hnch";function at(e){return yn(e)}function ct(e){return `${process.env.HNCH_SHARE_ORIGIN??"https://hnch.dev"}/join?code=${e}`}function dt(e){return `npx ${sr} join ${e}`}var ir={connecting:"yellow",connected:"green",disconnected:"red"};function Mn({roomCode:e,sessionName:t,status:n,isHost:o}){let r=t?`${e} \xB7 ${t}`:e;return jsxs(Box,{borderStyle:"single",borderColor:"gray",paddingX:1,justifyContent:"space-between",children:[jsxs(Text,{bold:true,children:["hnch \xB7 ",r]}),jsxs(Box,{gap:2,children:[o&&jsx(Text,{color:"yellow",children:"[host]"}),jsx(Text,{color:ir[n],children:n})]})]})}function ce({items:e,onSelect:t,onClose:n,title:o}){if(e.length===0)return jsxs(Box,{flexDirection:"column",gap:1,children:[o&&jsx(Text,{bold:true,children:o}),jsx(Text,{dimColor:true,children:"(no items)"})]});let r=e.findIndex(a=>!a.disabled),[s,p]=useState(Math.max(r,0));return useInput((a,l)=>{if(l.upArrow)p(u=>{let i=(u-1+e.length)%e.length,c=0;for(;e[i]?.disabled&&c<e.length;)i=(i-1+e.length)%e.length,c++;return i});else if(l.downArrow)p(u=>{let i=(u+1)%e.length,c=0;for(;e[i]?.disabled&&c<e.length;)i=(i+1)%e.length,c++;return i});else if(l.return){let u=e[s];u&&!u.disabled&&t(u.value);}else l.escape&&n?.();}),jsxs(Box,{flexDirection:"column",gap:1,children:[o&&jsx(Text,{bold:true,children:o}),jsx(Box,{flexDirection:"column",children:e.map((a,l)=>{let u=l===s,i=a.disabled??false;return jsxs(Box,{gap:1,children:[jsx(Text,{...u&&{color:"cyan"},dimColor:i,children:u?">":" "}),jsx(Text,{bold:u,dimColor:i,children:a.label}),a.detail&&jsx(Text,{dimColor:true,children:a.detail})]},a.value)})}),jsx(Text,{dimColor:true,children:"\u2191\u2193 select \u23CE confirm esc close"})]})}function W({value:e,onChange:t,onSubmit:n,onCancel:o,placeholder:r,isActive:s=true,mask:p=false}){useInput((i,c)=>{c.escape?o?.():c.return?n(e):c.backspace||c.delete?t(e.slice(0,-1)):i&&!c.ctrl&&!c.meta&&t(e+i);},{isActive:s});let a=p&&e.length>0?"*".repeat(e.length):e,l=a.length>0?a:r??"",u=e.length===0&&r!==void 0;return jsxs(Text,{children:[jsx(Text,{dimColor:u,children:l}),jsx(Text,{inverse:true,children:" "})]})}function Hn({mode:e,password:t,onModeChange:n,onPasswordChange:o,onClose:r}){let[s,p]=useState(e==="open"?0:1),[a,l]=useState(false);return useEffect(()=>{p(e==="open"?0:1);},[e]),useInput((u,i)=>{if(i.leftArrow)p(c=>Math.max(0,c-1));else if(i.rightArrow)p(c=>Math.min(1,c+1));else if(i.return){let c=s===0?"open":"password";c!==e&&n(c),c==="password"&&l(true);}else i.escape&&r?.();},{isActive:!a}),jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Box,{gap:1,children:["open","password"].map((u,i)=>{let c=i===s,h=u===e,E=u==="open"?"Open":"Password";return jsx(Text,{inverse:c,bold:h,...h&&{color:"cyan"},children:h?`[${E}]`:` ${E} `},u)})}),e==="password"&&jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{children:"Password: "}),jsx(W,{value:t,onChange:o,onSubmit:()=>l(false),onCancel:()=>l(false),placeholder:"(required to join)",isActive:a,mask:true}),!a&&jsx(Text,{dimColor:true,children:"\u23CE edit \u2190 \u2192 switch mode esc close"}),a&&jsx(Text,{dimColor:true,children:"\u23CE done esc cancel"})]}),e==="open"&&jsx(Text,{dimColor:true,children:"\u2190\u2192 toggle \u23CE confirm esc close"})]})}function Ln({scaleId:e,cards:t,disabledIndices:n,onOverrideChange:o,onClose:r}){let s=useMemo(()=>new Set(n),[n]),[p,a]=useState(0),l=u=>{let i=new Set(s);if(i.has(u))i.delete(u);else {if(i.size>=t.length-1)return;i.add(u);}let c=Array.from(i).toSorted((h,E)=>h-E);o(e,c);};return useInput((u,i)=>{i.leftArrow?a(c=>Math.max(0,c-1)):i.rightArrow?a(c=>Math.min(t.length-1,c+1)):u===" "?l(p):(i.return||i.escape)&&r();}),jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Box,{gap:1,children:t.map((u,i)=>{let c=i===p,h=s.has(i);return jsx(Text,{inverse:c,bold:!h,...!h&&{color:"cyan"},strikethrough:h,dimColor:h,children:u},u)})}),jsx(Text,{dimColor:true,children:" \u2190\u2192 navigate space toggle \u23CE/esc done"})]})}function _t({initialName:e="",initialCards:t=[],onSave:n,onCancel:o}){let r=e.length>0,[s,p]=useState(e),[a,l]=useState(t.length>0?t:[""]),[u,i]=useState(0),[c,h]=useState("editing-name"),[E,P]=useState(""),T=()=>{let d=s.trim();if(d.length===0||d.length>40){P("Name must be 1-40 characters");return}p(d),P(""),h("editing-cards");},I=()=>{let d=s.trim();if(d.length===0||d.length>40){P("Name must be 1-40 characters");return}let m=a.map(w=>w.trim()).filter(w=>w.length>0),v=Array.from(new Set(m));if(v.length<2){P("Need at least 2 unique non-empty cards");return}P(""),n(d,v);};return useInput((d,m)=>{if(c==="editing-cards"){if(m.leftArrow)i(v=>Math.max(0,v-1));else if(m.rightArrow)i(v=>{let w=v+1;return w>=a.length&&a.length<25?(l(M=>[...M,""]),w):Math.min(w,a.length-1)});else if(m.return)I();else if(m.escape)o();else if(m.tab)h("editing-name");else if(m.backspace){let v=a[u]||"";if(v.length===0){if(a.length>2){let w=a.filter((M,f)=>f!==u);l(w),i(Math.max(0,u-1));}}else {let w=[...a];w[u]=v.slice(0,-1),l(w);}}else if(d&&!m.ctrl&&!m.meta){let v=a[u]||"";if(v.length<10){let w=[...a];w[u]=v+d,l(w);}}}},{isActive:c==="editing-cards"}),jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:r?"\u2500\u2500 Edit Custom Scale \u2500\u2500":"\u2500\u2500 Create Custom Scale \u2500\u2500"}),jsxs(Box,{flexDirection:"column",gap:0,children:[jsx(Text,{children:"Name:"}),jsx(W,{value:s,onChange:p,onSubmit:T,onCancel:o,placeholder:"Scale name",isActive:c==="editing-name"})]}),c==="editing-cards"&&jsxs(Box,{flexDirection:"column",gap:0,children:[jsx(Text,{children:"Cards:"}),jsx(Box,{gap:1,flexWrap:"wrap",children:a.map((d,m)=>{let v=m===u;return jsx(Text,{inverse:v,...!v&&{color:"cyan"},children:d.length>0?d:"\xB7"},m)})})]}),E&&jsx(Text,{color:"red",children:E}),jsx(Text,{dimColor:true,children:c==="editing-name"?"\u23CE next esc cancel":"\u2190\u2192 navigate typing adds card bsp delete \u23CE save tab name esc cancel"})]})}function pt({scales:e,selectedScaleId:t,presetOverrides:n,customScales:o=[],onSelect:r,onOverrideChange:s,onCustomScaleCreate:p,onCustomScaleEdit:a,onCustomScaleDelete:l,onClose:u,showNoneOption:i=false}){let[c,h]=useState(0),[E,P]=useState(null),[T,I]=useState("browsing"),[H,d]=useState(null),[m,v]=useState(null),w=p?[...e,{id:"__create-custom__",cards:[]}]:e,M=w.length;if(useInput((f,k)=>{if(T==="browsing"){if(k.escape){u();return}if(M===0)return;if(k.upArrow)h(b=>(b-1+M)%M);else if(k.downArrow)h(b=>(b+1)%M);else if(k.return){let b=w[c];if(!b)return;if(b.id==="__create-custom__"){I("creating");return}b.id in te?P(E===b.id?null:b.id):r(b.id);}else if(f===" "){let b=w[c];b&&b.id!=="__create-custom__"&&r(b.id);}else if(f==="e"||f==="E"){let b=w[c];a&&b?.id.startsWith("custom-")&&(d(b.id),I("editing"));}else if(f==="d"||f==="D"){let b=w[c];l&&b?.id.startsWith("custom-")&&(v(b.id),I("confirming-delete"));}}else T==="confirming-delete"&&(f==="y"||f==="Y"?(m&&l&&l(m),v(null),I("browsing")):(f==="n"||f==="N"||k.escape)&&(v(null),I("browsing")));},{isActive:T==="browsing"&&E===null||T==="confirming-delete"}),T==="creating")return jsx(_t,{onSave:(f,k)=>{p&&p(f,k),I("browsing");},onCancel:()=>I("browsing")});if(T==="editing"&&H){let f=o?.find(b=>b.id===H);if(e.find(b=>b.id===H)&&f)return jsx(_t,{initialName:f.label,initialCards:f.cards,onSave:(b,L)=>{a&&a(H,b,L),d(null),I("browsing");},onCancel:()=>{d(null),I("browsing");}})}if(T==="confirming-delete"&&m){let f=w.find(b=>b.id===m),k=f?Se(f.id):"Unknown";return jsxs(Box,{flexDirection:"column",gap:1,children:[jsxs(Text,{children:["Delete ",k,"? "]}),jsx(Text,{children:"[y] yes [n] no esc cancel"})]})}return w.length===0?jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{dimColor:true,children:"(no scales available)"}),jsx(Text,{dimColor:true,children:"esc back"})]}):jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Box,{flexDirection:"column",children:w.map((f,k)=>{let b=k===c,L=f.id===t,oe=E===f.id,Z=f.id in te,ee=f.id==="__create-custom__",re=f.cards.join(", "),g=i&&f.id==="",C=g?"(none)":ee?"[+ Create custom scale]":Se(f.id);return jsxs(Box,{flexDirection:"column",children:[jsxs(Box,{gap:1,children:[jsx(Text,{...b&&{color:"cyan"},children:b?">":" "}),jsx(Text,{bold:b,children:C}),!g&&!ee&&jsx(Text,{dimColor:true,children:re}),L&&jsx(Text,{color:"cyan",children:"[current]"})]}),oe&&!g&&!ee&&jsx(Box,{paddingLeft:2,children:Z?jsx(Ln,{scaleId:f.id,cards:te[f.id]?.cards??[],disabledIndices:n?.[f.id]??[],onOverrideChange:s,onClose:()=>P(null)}):null})]},f.id||"none")})}),jsx(Text,{dimColor:true,children:(()=>{let f=c<w.length?w[c]:null;return f?.id.startsWith("custom-")?`\u2191\u2193 navigate space select${a?" [e] edit":""}${l?" [d] delete":""} esc back`:f&&f.id in te?"\u2191\u2193 navigate \u23CE customize space select esc back":"\u2191\u2193 navigate \u23CE/space select esc back"})()})]})}function yr(e,t){return t?[...e]:e.filter(n=>n.value==="display-name")}function _n({sessionName:e,displayName:t,currentScale:n,accessMode:o,presetOverrides:r,customScales:s,isHost:p=true,onChangeName:a,onChangeScale:l,onChangeDisplayName:u,onChangeAccess:i,onClose:c}){let[h,E]=useState(r??{}),P=useMemo(()=>nt(h,s),[h,s]),[T,I]=useState("main-menu"),[H,d]=useState(e??""),[m,v]=useState(t),[w,M]=useState(o==="password"?"password":"open"),[f,k]=useState(""),[b,L]=useState(false),oe=[{label:"Room name",value:"room-name",detail:`[${e??"(none)"}]`},{label:"Scale",value:"scale",detail:`[${Se(n.id)}]`},{label:"Access",value:"access",detail:`[${o==="open"?"Open":"Password"}]`},{label:"Display name",value:"display-name",detail:`[${t}]`}],Z=yr(oe,p);function ee(O){!p&&O!=="display-name"||(O==="room-name"?(d(e??""),I("editing-room-name")):O==="scale"?I("picking-scale"):O==="access"?I("picking-access"):O==="display-name"&&(v(t),I("editing-display-name")));}function re(O){let D=O.trim()===""?void 0:O.trim();a(D),I("main-menu");}function g(O){let D=O.trim();D.length>0&&(u(D),process.nextTick(()=>Y({displayName:D}))),I("main-menu");}function C(O){let D=P.find(_=>_.id===O);D&&l(D),I("main-menu");}function A(O,D){let _={...h};D.length===0?delete _[O]:_[O]=D,E(_),Y({presetOverrides:Object.keys(_).length>0?_:void 0});}async function B(O,D){L(true);try{await i(O,D),M(O),k(O==="password"?D??"":"");}catch(_){console.error(`Failed to update access: ${_}`);}finally{L(false),I("main-menu");}}return T==="main-menu"?jsx(ce,{title:"\u2500\u2500 Settings \u2500\u2500",items:Z,onSelect:ee,onClose:c}):T==="editing-room-name"?jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:"\u2500\u2500 Settings \u2500\u2500"}),jsx(Text,{children:"Room name: "}),jsx(W,{value:H,onChange:d,onSubmit:re,onCancel:()=>I("main-menu"),placeholder:"(leave empty to remove)",isActive:true}),jsx(Text,{dimColor:true,children:"\u23CE confirm esc back"})]}):T==="editing-display-name"?jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:"\u2500\u2500 Settings \u2500\u2500"}),jsx(Text,{children:"Display name: "}),jsx(W,{value:m,onChange:v,onSubmit:g,onCancel:()=>I("main-menu"),placeholder:"(required)",isActive:true}),jsx(Text,{dimColor:true,children:"\u23CE confirm esc back"})]}):T==="picking-scale"?jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:"\u2500\u2500 Settings \u2500\u2500"}),jsx(pt,{scales:P,selectedScaleId:n.id,presetOverrides:h,onSelect:C,onOverrideChange:A,onClose:()=>I("main-menu")})]}):T==="picking-access"?jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:"\u2500\u2500 Settings \u2500\u2500"}),b&&jsx(Text,{dimColor:true,children:"Updating access..."}),!b&&jsx(Hn,{mode:w,password:f,onModeChange:O=>{O==="open"&&B("open");},onPasswordChange:k,onClose:()=>I("main-menu")})]}):jsx(Box,{})}function gt({participants:e,onlineIds:t,hostId:n,votes:o,revealed:r,participantId:s}){return jsxs(Box,{flexDirection:"column",children:[jsxs(Text,{bold:true,children:["Participants (",e.length,")"]}),e.map(p=>{let a=t.has(p.id),l=a?"\u25CF":"\u25CB",u=a?"green":"gray",i=p.id===n,c=he(p)==="viewer",h=p.id===s,E=p.id in o,P=c?" (viewer)":r?o[p.id]?` \u2192 ${o[p.id]}`:"":E?" \u2713":" \xB7",T=h&&!c&&!(r&&o[p.id]);return jsxs(Box,{gap:1,children:[jsx(Text,{color:u,children:l}),jsxs(Text,{children:[p.displayName,i?" (host)":""]}),T?jsx(Text,{color:"cyan",children:"(voter)"}):jsx(Text,{color:c?"gray":E?"green":"gray",children:P})]},p.id)})]})}var vr=[{label:"End session for everyone",value:"end-session",detail:"Disconnects all participants. Cannot be undone."}];function Vn({onPick:e,onClose:t}){return jsx(ce,{title:"Host Actions",items:vr,onSelect:e,onClose:t})}function ht({roomCode:e,onPick:t,onClose:n}){let o=[{label:"Copy code",value:"code",detail:at(e)},{label:"Copy share link",value:"url",detail:ct(e)},{label:"Copy npx command",value:"npx",detail:dt(e)}];return jsx(ce,{title:"Share",items:o,onSelect:r=>t(r),onClose:n})}function Yn(e){let t=Object.values(e).map(i=>parseFloat(i)).filter(i=>!isNaN(i)).toSorted((i,c)=>i-c);if(t.length===0)return {average:"-",median:"-",agreement:"-"};let o=t.reduce((i,c)=>i+c,0)/t.length,r=Math.floor(t.length/2),s=t.length%2===0?(t[r-1]+t[r])/2:t[r],p=Object.values(e),a=new Map;for(let i of p)a.set(i,(a.get(i)??0)+1);let l=Math.max(...a.values()),u=p.length>0?Math.round(l/p.length*100):0;return {average:o.toFixed(1),median:s.toFixed(1),agreement:`${u}%`}}function Vt(e,t){let n=Math.floor((t-e)/1e3),o=Math.floor(n/60),r=n%60;return `${o}:${r.toString().padStart(2,"0")}`}function Jn(e,t){if(G(e)==="skipped"){let a=e.title?` ${e.title}`:"",l=e.ticketId?` [${e.ticketId}]`:"";return `Round ${t}:${a}${l} \u2014 Skipped`}let n=new Map;for(let a of Object.values(e.votes))n.set(a,(n.get(a)??0)+1);let o=[...n.entries()].toSorted((a,l)=>l[1]-a[1]||a[0].localeCompare(l[0])).map(([a,l])=>`${a}\xD7${l}`).join(", "),r=e.title?` ${e.title}`:"",s=e.ticketId?` [${e.ticketId}]`:"",p=e.timerStartedAt!==void 0&&e.revealedAt!==void 0?` \u2014 ${Vt(e.timerStartedAt,e.revealedAt)}`:"";return `Round ${t}:${r}${s} \u2014 ${o||"(no votes)"}${p}`}function Wn({projection:e,onlineIds:t,isHost:n,participantId:o,rounds:r,currentRound:s,settingsOpen:p,onStartRound:a,onNewRound:l,onRevote:u,onOpenSettings:i,onHandoffHost:c,onEndSession:h,onShare:E,shareOpen:P,onSetShareOpen:T,roomCode:I,confirmingQuit:H,myRole:d,onToggleRole:m,roundTitlePromptActive:v,roundTitleInput:w,onRoundTitleChange:M,onRoundTitleSubmit:f,onRoundTitleCancel:k,descriptorPromptActive:b,descriptorInput:L,onDescriptorChange:oe,onDescriptorSubmit:Z,onDescriptorCancel:ee,onEditDescriptor:re}){let[g,C]=St.useState("idle"),[A,B]=St.useState(null),[O,D]=St.useState(false),[_,me]=St.useState(false);useEffect(()=>{n||(C("idle"),B(null),D(false),me(false));},[n]),useInput(S=>{if(S==="c"||S==="C"){T(true);return}if(S==="w"||S==="W"){m();return}n&&((S==="e"||S==="E")&&i(),(S==="s"||S==="S")&&a(),(S==="h"||S==="H")&&C("picking"),(S==="m"||S==="M")&&D(true),s!==null&&G(s)!==null&&((S==="n"||S==="N")&&l(),(S==="v"||S==="V")&&G(s)!=="skipped"&&u(),(S==="t"||S==="T")&&re()));},{isActive:!p&&!H&&g==="idle"&&!v&&!O&&!_&&!P&&!b}),useInput((S,K)=>{S==="y"||S==="Y"?(h(),me(false)):(S==="n"||S==="N"||K.escape)&&me(false);},{isActive:_});let se=e.participants.filter(S=>S.id!==o&&t.has(S.id));useInput(S=>{if(g==="picking"){let K=parseInt(S,10);!isNaN(K)&&K>=1&&K<=se.length?(B(se[K-1].id),C("confirming")):(S==="Escape"||S==="q"||S==="Q")&&C("idle");}},{isActive:g==="picking"}),useInput(S=>{g==="confirming"&&A&&(S==="y"||S==="Y"?(c(A),C("idle"),B(null)):(S==="n"||S==="N"||S==="Escape")&&(C("idle"),B(null)));},{isActive:g==="confirming"});let Ye=r.length>0,Pe=r.filter(S=>G(S)!==null),Je=s!==null?Pe.slice(0,-1):Pe;return O?jsx(Vn,{onPick:S=>{D(false),S==="end-session"&&me(true);},onClose:()=>D(false)}):P?jsx(ht,{roomCode:I,onPick:S=>{T(false),E(S);},onClose:()=>T(false)}):jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:Ye?"Session":"Waiting for participants..."}),!Ye&&jsx(Text,{dimColor:true,children:"No rounds started yet."}),jsx(gt,{participants:e.participants,onlineIds:t,hostId:e.settings.hostParticipantId,votes:{},revealed:false,participantId:o}),s!==null&&G(s)!==null&&jsx(Ir,{projection:e,round:s,rounds:r}),Je.length>0&&jsxs(Box,{flexDirection:"column",children:[jsx(Text,{dimColor:true,children:"\u2500\u2500 History \u2500\u2500"}),Je.map(S=>{let K=r.indexOf(S)+1;return jsx(Text,{dimColor:true,children:Jn(S,K)},S.id)})]}),g==="picking"&&se.length>0&&jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{children:"Hand off to:"}),se.map((S,K)=>jsxs(Text,{children:["[",K+1,"] ",S.displayName]},S.id)),jsx(Text,{dimColor:true,children:"[q] Cancel"})]}),g==="picking"&&se.length===0&&jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{dimColor:true,children:"No other online participants"}),jsx(Text,{dimColor:true,children:"[q] Cancel"})]}),g==="confirming"&&A&&jsx(Box,{flexDirection:"column",children:jsxs(Text,{children:["Hand off to ",e.participants.find(S=>S.id===A)?.displayName,"? [y/n]"]})}),v&&jsxs(Box,{flexDirection:"column",children:[jsxs(Box,{gap:1,children:[jsx(Text,{children:"Round title (optional, Enter to skip):"}),jsx(W,{value:w,onChange:M,onSubmit:f,onCancel:k,placeholder:"Enter to skip",isActive:v})]}),jsx(Text,{dimColor:true,children:"[Esc] Cancel"})]}),b&&jsxs(Box,{flexDirection:"column",children:[jsxs(Box,{gap:1,children:[jsx(Text,{children:"Edit round title:"}),jsx(W,{value:L,onChange:oe,onSubmit:Z,onCancel:ee,placeholder:"Enter to save",isActive:b})]}),jsx(Text,{dimColor:true,children:"[Esc] Cancel \xB7 empty submit cancels"})]}),_&&jsxs(Box,{children:[jsx(Text,{color:"red",children:"End session for everyone? This cannot be undone. "}),jsx(Text,{dimColor:true,children:"y/n [Esc] Cancel"})]}),g==="idle"&&!v&&!b&&!_&&jsxs(Box,{gap:2,children:[n?jsx(Fragment,{children:s!==null&&G(s)!==null?jsxs(Fragment,{children:[G(s)!=="skipped"&&jsx(Text,{color:"yellow",children:"[v] Revote"}),jsx(Text,{color:"yellow",children:"[n] Next"}),jsx(Text,{color:"yellow",children:"[t] Edit title"}),jsx(Text,{color:"yellow",children:"[h] Handoff"}),jsx(Text,{color:"yellow",children:"[e] Settings"}),jsx(Text,{color:"yellow",children:"[c] Share"}),jsxs(Text,{color:"yellow",children:["[w] ",d==="viewer"?"Vote":"Observe"]}),jsx(Text,{color:"yellow",children:"[m] Menu"})]}):jsxs(Fragment,{children:[jsx(Text,{color:"yellow",children:"[s] Start round"}),jsx(Text,{color:"yellow",children:"[h] Handoff"}),jsx(Text,{color:"yellow",children:"[e] Settings"}),jsx(Text,{color:"yellow",children:"[c] Share"}),jsxs(Text,{color:"yellow",children:["[w] ",d==="viewer"?"Vote":"Observe"]}),jsx(Text,{color:"yellow",children:"[m] Menu"})]})}):jsxs(Fragment,{children:[jsx(Text,{dimColor:true,children:"Waiting for host to start a round..."}),jsx(Text,{children:"[c] Share"}),jsxs(Text,{color:"yellow",children:["[w] ",d==="viewer"?"Vote":"Observe"]})]}),jsx(Text,{children:"[q] Quit"})]})]})}function Ir({projection:e,round:t,rounds:n}){let o=n.indexOf(t)+1,r=t.title?` \u2014 ${t.title}`:"",s=t.ticketId?` [${t.ticketId}]`:"";if(G(t)==="skipped")return jsx(Box,{flexDirection:"column",gap:1,children:jsxs(Text,{bold:true,children:["Round ",o,r,s," \u2014 Skipped"]})});let p=Yn(t.votes),a=new Map(e.participants.map(i=>[i.id,i.displayName])),l=t.timerStartedAt!==void 0&&t.revealedAt!==void 0?Vt(t.timerStartedAt,t.revealedAt):null,u=Object.values(t.votes)[0];return jsxs(Box,{flexDirection:"column",gap:1,children:[jsxs(Text,{bold:true,children:["Round ",o,r,s," \u2014 Results"]}),hn(t)&&jsxs(Text,{bold:true,color:"green",children:["Consensus! Everyone voted ",u]}),jsxs(Box,{flexDirection:"column",children:[jsxs(Box,{gap:2,children:[jsx(Text,{bold:true,underline:true,children:"Name".padEnd(20)}),jsx(Text,{bold:true,underline:true,children:"Estimate".padEnd(10)})]}),Object.entries(t.votes).map(([i,c])=>jsxs(Box,{gap:2,children:[jsx(Text,{children:(a.get(i)??i).padEnd(20)}),jsx(Text,{color:"cyan",bold:true,children:c.padEnd(10)})]},i)),e.participants.filter(i=>!(i.id in t.votes)).map(i=>jsxs(Box,{gap:2,children:[jsx(Text,{dimColor:true,children:i.displayName.padEnd(20)}),jsx(Text,{dimColor:true,children:"(no vote)".padEnd(10)})]},i.id))]}),jsxs(Box,{gap:2,children:[jsxs(Text,{children:["Avg: ",jsx(Text,{bold:true,children:p.average})]}),jsxs(Text,{children:["Med: ",jsx(Text,{bold:true,children:p.median})]}),jsxs(Text,{children:["Agreement: ",jsx(Text,{bold:true,children:p.agreement})]}),l!==null&&jsxs(Text,{dimColor:true,children:["\u23F1 ",l]})]})]})}function zn({scale:e,selected:t,onSelect:n,onClear:o,disabled:r}){let[s,p]=useState(0);return useInput((a,l)=>{if(!r)if(l.leftArrow)p(u=>Math.max(0,u-1));else if(l.rightArrow)p(u=>Math.min(e.cards.length-1,u+1));else if(l.return){let u=e.cards[s];u===t?o():n(u);}else (l.backspace||l.delete)&&o();}),jsxs(Box,{flexDirection:"column",gap:0,children:[jsx(Box,{gap:1,children:e.cards.map((a,l)=>{let u=l===s,i=a===t;return jsx(Text,{inverse:u,bold:i,...i&&{color:"cyan"},children:i?`[${a}]`:` ${a} `},a)})}),jsx(Text,{dimColor:true,children:" \u2190\u2192 navigate \u23CE select \u232B clear"})]})}function Pr(e){let t=Math.floor(e/1e3),n=Math.floor(t/60),o=t%60;return `${n}:${o.toString().padStart(2,"0")}`}function Qn({projection:e,round:t,onlineIds:n,isHost:o,myId:r,myRole:s,elapsedMs:p,roomCode:a,shareOpen:l,settingsOpen:u,onVote:i,onClearVote:c,onReveal:h,onSkip:E,onToggleRole:P,onShare:T,onSetShareOpen:I,onOpenSettings:H,confirmingQuit:d}){if(useInput(f=>{if(f==="e"||f==="E"){H();return}if(f==="c"||f==="C"){I(true);return}o&&(f==="r"||f==="R")&&h(),o&&(f==="s"||f==="S")&&E(),(f==="w"||f==="W")&&P();},{isActive:!d&&!u&&!l}),l)return jsx(ht,{roomCode:a,onPick:f=>{I(false),T(f);},onClose:()=>I(false)});let m=t.votes[r],v=e.rounds.indexOf(t)+1,w=t.title?` \u2014 ${t.title}`:"",M=t.ticketId?` [${t.ticketId}]`:"";return jsxs(Box,{flexDirection:"column",gap:1,children:[jsxs(Box,{gap:2,children:[jsxs(Text,{bold:true,children:["Round ",v,w,M]}),p!==void 0&&jsxs(Text,{dimColor:true,children:["\u23F1 ",Pr(p)]})]}),jsxs(Box,{gap:4,children:[jsx(gt,{participants:e.participants,onlineIds:n,hostId:e.settings.hostParticipantId,votes:t.votes,revealed:false}),s==="viewer"?jsx(Box,{flexDirection:"column",children:jsx(Text,{dimColor:true,children:"You're observing"})}):jsx(zn,{scale:t.scale,selected:m,onSelect:i,onClear:c,disabled:d})]}),jsxs(Box,{gap:2,children:[o&&jsx(Text,{color:"yellow",children:"[r] Reveal"}),o&&jsx(Text,{color:"yellow",children:"[s] Skip"}),jsxs(Text,{color:"yellow",children:["[w] ",s==="viewer"?"Vote":"Observe"]}),jsx(Text,{children:"[e] Settings"}),jsx(Text,{children:"[c] Share"}),jsx(Text,{children:"[q] Quit"})]})]})}var kr=3e3;function Wt(e=kr){let[t,n]=useState(null),o=useRef(null),r=useCallback(()=>{o.current!==null&&(clearTimeout(o.current),o.current=null);},[]),s=useCallback(a=>{r(),n(a),o.current=setTimeout(()=>{n(null),o.current=null;},e);},[r,e]),p=useCallback(()=>{r(),n(null);},[r]);return useEffect(()=>()=>{r();},[r]),{notice:t,show:s,dismiss:p}}function Ur(e){let t=X(e);return !t||G(t)!==null?"lobby":"voting"}function vt({session:e,participantId:t,displayName:n,accessMode:o,presetOverrides:r,customScales:s}){let{exit:p}=useApp(),[a,l]=useState(e.projection()),[u,i]=useState("connecting"),[c,h]=useState(e.onlineIds()),[E,P]=useState(),[T,I]=useState(false),[H,d]=useState(o==="password"?"password":"open"),[m,v]=useState(n),[w,M]=useState(false),[f,k]=useState(false),[b,L]=useState(""),[oe,Z]=useState(false),[ee,re]=useState(""),[g,C]=useState(null),A=Wt(3e3),[B,O]=useState(false),D=Wt(3e3);useEffect(()=>{let R=e.onChange(()=>{l(e.projection()),h(e.onlineIds());}),V=e.onStatusChange(i),We=e.onHostChange(qe=>{let vo=e.projection().participants.find(bo=>bo.id===qe),Co=qe===t?"You are now the host":`${vo?.displayName||"Unknown"} is now the host`;A.show(Co);}),yo=setInterval(()=>{h(e.onlineIds());let qe=e.projection(),Ge=X(qe);Ge?.timerStartedAt!==void 0&&Ge.revealedAt===void 0?P(Date.now()-Ge.timerStartedAt):P(void 0);},1e3);return ()=>{R(),V(),We(),clearInterval(yo);}},[e,t,A.show]),useInput(R=>{(R==="q"||R==="Q")&&M(true);},{isActive:!T&&!w&&!f&&!oe&&!B}),useInput((R,V)=>{R==="y"||R==="Y"?(e.disconnect(),p()):(R==="n"||R==="N"||V.escape)&&M(false);},{isActive:w});let _=Ur(a),me=X(a),se=a.settings.hostParticipantId===t,Ye=a.participants.find(R=>R.id===t),Pe=he(Ye??{}),Je=useCallback(()=>{L(""),k(true);},[]),S=useCallback(R=>{k(false),L(""),e.newRound(R.trim()||void 0);},[e]),K=useCallback(()=>{k(false),L("");},[]),no=useCallback(()=>{let R=e.current();!R||R.revealedAt===void 0||(C(R.id),re(R.title??""),Z(true));},[e]),oo=useCallback(R=>{let V=g;if(Z(false),C(null),!V)return;let We=R.trim();We!==""&&e.updateRoundDescriptor(V,{title:We});},[e,g]),ro=useCallback(()=>{Z(false),C(null);},[]),so=useCallback(R=>{e.vote(R);},[e]),io=useCallback(()=>{e.unvote();},[e]),ao=useCallback(()=>{e.reveal();},[e]),co=useCallback(()=>{e.skip();},[e]),lo=useCallback(()=>{L(""),k(true);},[]),uo=useCallback(()=>{e.revote();},[e]),Qt=useCallback(()=>{e.toggleRole();},[e]),po=useCallback(R=>{e.updateName(R);},[e]),fo=useCallback(R=>{e.updateScale(R);},[e]),mo=useCallback(R=>{e.updateDisplayName(R),v(R);},[e]),go=useCallback(async(R,V)=>{await e.updateAccess(R,V),d(R);},[e]),ho=useCallback(R=>{try{e.handoffHost(R);}catch(V){console.error(`Handoff failed: ${V instanceof Error?V.message:String(V)}`);}},[e]),Kt=useCallback(()=>{I(true);},[]),So=useCallback(()=>{I(false);},[]),xo=useCallback(()=>{e.endSessionForEveryone();},[e]),Xt=useCallback(async R=>{let V=R==="code"?at(e.roomCode):R==="url"?ct(e.roomCode):dt(e.roomCode);try{await Hr.write(V),D.show("Copied!");}catch{D.show(`Copy failed \u2014 ${V}`);}},[e.roomCode,D]),Zt=a.rounds.filter(R=>G(R)!==null),wo=Zt.length>0?Zt.at(-1):null;return jsxs(Box,{flexDirection:"column",children:[jsx(Mn,{roomCode:e.roomCode,sessionName:a.settings.name,status:u,isHost:se}),A.notice&&jsx(Box,{paddingX:1,children:jsx(Text,{color:"yellow",children:A.notice})}),D.notice&&jsx(Box,{paddingX:1,children:jsx(Text,{color:D.notice.startsWith("Copy failed")?"red":"green",children:D.notice})}),T?jsx(_n,{sessionName:a.settings.name,displayName:m,currentScale:a.settings.defaultScale,accessMode:H,presetOverrides:r,customScales:s,isHost:se,onChangeName:po,onChangeScale:fo,onChangeDisplayName:mo,onChangeAccess:go,onClose:So}):_==="lobby"?jsx(Wn,{projection:a,onlineIds:c,isHost:se,participantId:t,rounds:a.rounds,currentRound:wo,settingsOpen:T,onStartRound:Je,onNewRound:lo,roundTitlePromptActive:f,roundTitleInput:b,onRoundTitleChange:L,onRoundTitleSubmit:S,onRoundTitleCancel:K,onRevote:uo,onOpenSettings:Kt,onHandoffHost:ho,onEndSession:xo,onShare:Xt,shareOpen:B,onSetShareOpen:O,roomCode:e.roomCode,confirmingQuit:w,myRole:Pe,onToggleRole:Qt,descriptorPromptActive:oe,descriptorInput:ee,onDescriptorChange:re,onDescriptorSubmit:oo,onDescriptorCancel:ro,onEditDescriptor:no}):_==="voting"&&me?jsx(Qn,{projection:a,round:me,onlineIds:c,isHost:se,myId:t,myRole:Pe,elapsedMs:E,onVote:so,onClearVote:io,onReveal:ao,onSkip:co,onToggleRole:Qt,onShare:Xt,onSetShareOpen:O,shareOpen:B,settingsOpen:T,onOpenSettings:Kt,roomCode:e.roomCode,confirmingQuit:w}):null,w&&jsx(Box,{children:jsx(Text,{dimColor:true,children:"Leave session? y/n"})})]})}function Xn({presetOverrides:e,customScales:t}){let{exit:n}=useApp(),[o,r]=useState("main-menu"),[s,p]=useState(ge()),[a,l]=useState(s.displayName??""),[u,i]=useState(s.relayUrl??""),[c,h]=useState(s.defaultSessionName??""),[E,P]=useState(t??[]),[T,I]=useState(e??{});useInput(g=>{(g==="q"||g==="Q")&&n();},{isActive:o==="main-menu"});let H=useMemo(()=>nt(T,E),[T,E]),d=s.defaultScale,m=d?H.find(g=>g.id===d):void 0,v=[{label:"Display name",value:"display-name",detail:`[${s.displayName??"(none)"}]`},{label:"Relay URL",value:"relay-url",detail:`[${s.relayUrl??"(none)"}]`},{label:"Default scale",value:"default-scale",detail:`[${m?Se(m.id):"(none)"}]`},{label:"Default session name",value:"default-session-name",detail:`[${s.defaultSessionName??"(none)"}]`},{label:"Default password",value:"default-password",detail:`[${s.defaultPassword?"on":"off"}]`}];function w(g){g==="display-name"?(l(s.displayName??""),r("editing-display-name")):g==="relay-url"?(i(s.relayUrl??""),r("editing-relay-url")):g==="default-scale"?r("picking-scale"):g==="default-session-name"?(h(s.defaultSessionName??""),r("editing-session-name")):g==="default-password"&&r("picking-default-password");}function M(g){let C=g.trim();if(C.length>0){let A={...s,displayName:C};p(A),Y({displayName:C});}r("main-menu");}function f(g){let C=g.trim();if(C.length>0){let A={...s,relayUrl:C};p(A),Y({relayUrl:C});}r("main-menu");}function k(g){let C=g.trim(),A=C.length>0?{...s,defaultSessionName:C}:{...s};C.length===0?(delete A.defaultSessionName,p(A),Me({defaultSessionName:""})):(p(A),Y({defaultSessionName:C})),r("main-menu");}function b(g){let C=g.length>0?{...s,defaultScale:g}:{...s};g.length===0?(delete C.defaultScale,p(C),Me({defaultScale:""})):(p(C),Y({defaultScale:g})),r("main-menu");}function L(g,C){let A={...T};C.length===0?delete A[g]:A[g]=C,I(A),Y({presetOverrides:Object.keys(A).length>0?A:void 0});}function oe(g){let C=g==="on",A={...s,defaultPassword:C};p(A),Y({defaultPassword:C}),r("main-menu");}function Z(g,C){let A=`custom-${randomUUID()}`,B=[...E,{id:A,label:g,cards:C}];P(B),p({...s,customScales:B}),Y({customScales:B});}function ee(g,C,A){let B=E.map(O=>O.id===g?{id:g,label:C,cards:A}:O);P(B),p({...s,customScales:B}),Y({customScales:B});}function re(g){let C=E.filter(B=>B.id!==g);P(C);let A={...s,customScales:C};s.defaultScale===g?(A.defaultScale="fibonacci",Y({customScales:C,defaultScale:"fibonacci"})):Y({customScales:C}),p(A);}if(o==="main-menu")return jsx(ce,{title:"\u2500\u2500 Config Settings \u2500\u2500",items:v,onSelect:w,onClose:()=>n()});if(o==="editing-display-name")return jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:"\u2500\u2500 Config Settings \u2500\u2500"}),jsx(Text,{children:"Display name: "}),jsx(W,{value:a,onChange:l,onSubmit:M,onCancel:()=>r("main-menu"),placeholder:"(required)",isActive:true}),jsx(Text,{dimColor:true,children:"\u23CE confirm esc back"})]});if(o==="editing-relay-url")return jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:"\u2500\u2500 Config Settings \u2500\u2500"}),jsx(Text,{children:"Relay URL: "}),jsx(W,{value:u,onChange:i,onSubmit:f,onCancel:()=>r("main-menu"),placeholder:"(required)",isActive:true}),jsx(Text,{dimColor:true,children:"\u23CE confirm esc back"})]});if(o==="editing-session-name")return jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:"\u2500\u2500 Config Settings \u2500\u2500"}),jsx(Text,{children:"Default session name: "}),jsx(W,{value:c,onChange:h,onSubmit:k,onCancel:()=>r("main-menu"),placeholder:"(leave empty to clear)",isActive:true}),jsx(Text,{dimColor:true,children:"\u23CE confirm esc back"})]});if(o==="picking-scale"){let g=[{id:"",cards:[]},...H];return jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:"\u2500\u2500 Config Settings \u2500\u2500"}),jsx(pt,{scales:g,selectedScaleId:d??"",presetOverrides:T,customScales:E,onSelect:b,onOverrideChange:L,onCustomScaleCreate:Z,onCustomScaleEdit:ee,onCustomScaleDelete:re,onClose:()=>r("main-menu"),showNoneOption:true})]})}if(o==="picking-default-password"){let g=[{label:"On",value:"on",detail:s.defaultPassword?"[current]":void 0},{label:"Off",value:"off",detail:s.defaultPassword?void 0:"[current]"}];return jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:"\u2500\u2500 Config Settings \u2500\u2500"}),jsx(ce,{title:"Default password:",items:g,onSelect:oe,onClose:()=>r("main-menu")})]})}return jsx(Box,{})}async function zt(){return new Promise(e=>{let t=createInterface({input:process.stdin,output:process.stderr});process.stderr.write("Password: "),t.question("",n=>{t.close(),e(n);});})}async function qr(e){let t=await De(),n=e.scale??t.defaultScale??"fibonacci",o=e.name??t.defaultSessionName,r=e.password??t.defaultPassword??false,s;r&&(s=await zt());let p=tt(n,t.presetOverrides,t.customScales);if(!p){let c=Object.keys(te).join(", ");console.error(`Unknown or fully-disabled scale: "${n}". Builtin IDs: ${c}`),process.exit(1);}let a=await Tn({participantId:t.participantId,displayName:t.displayName,relayUrl:t.relayUrl,sessionName:o,password:s,defaultScale:p}),l;s&&(l=await xe(s,a.roomUuid)),rt({roomCode:a.roomCode,roomUuid:a.roomUuid,passwordHash:l,lastSeenAt:Date.now()}),console.error(`Room created: ${a.roomCode}`),console.error(`Share: hnch join ${a.roomCode}`),console.error("");let{waitUntilExit:u,unmount:i}=render(St.createElement(vt,{session:a,participantId:t.participantId,displayName:t.displayName,accessMode:s?"password":"open",presetOverrides:t.presetOverrides,customScales:t.customScales}));it({doc:a.doc,localParticipantId:t.participantId,onEnded:async c=>{console.error(c.isLocal?"Session ended.":`Session ended by ${c.hostName}.`),i();try{await F();}catch(h){console.error(`Failed to clear cached session: ${h instanceof Error?h.message:String(h)}`);}a.disconnect(),process.exit(0);}}),await u(),a.disconnect(),F(),process.exit(0);}async function Gr(e){let t=await De(),n=An(e.code);n===null&&(console.error("invalid room code"),process.exit(1));let o;e.password&&(o=await zt());let r=await Be(t.relayUrl,n),s;r.accessMode==="password"&&!e.password?s=await zt():e.password&&(s=o);let p,a=false,l=await En({participantId:t.participantId,displayName:t.displayName,relayUrl:t.relayUrl,roomCode:n,password:s,onRejection(c){if(c==="unknown-room"){st({clearLastSession:F,logger:console,exit:process.exit});return}c==="room-full"&&(a=true,p?.());}}),u;s&&(u=await xe(s,l.roomUuid)),rt({roomCode:l.roomCode,roomUuid:l.roomUuid,passwordHash:u,lastSeenAt:Date.now()}),console.error(`Joined room: ${l.roomCode}`),console.error("");let i=render(St.createElement(vt,{session:l,participantId:t.participantId,displayName:t.displayName,accessMode:r.accessMode,presetOverrides:t.presetOverrides,customScales:t.customScales}));p=i.unmount,it({doc:l.doc,localParticipantId:t.participantId,onEnded:async c=>{console.error(c.isLocal?"Session ended.":`Session ended by ${c.hostName}.`),p?.();try{await F();}catch(h){console.error(`Failed to clear cached session: ${h instanceof Error?h.message:String(h)}`);}l.disconnect(),process.exit(0);}}),await i.waitUntilExit(),a&&(console.error("This room is full (max 20 participants)."),l.disconnect(),F(),process.exit(1)),l.disconnect(),F(),process.exit(0);}async function zr(){let e=await De(),t=Pn();t||(console.error("No recent session to resume."),process.exit(0));let n;try{n=await Be(e.relayUrl,t.roomCode);}catch(i){if((i instanceof Error?i.message:String(i)).includes("Room not found"))return await st({clearLastSession:F,logger:console,exit:process.exit});console.error("Could not reach relay \u2014 try again later."),process.exit(0);}n.accessMode==="password"&&!t.passwordHash&&(console.error(`Password changed \u2014 rejoin with: hnch join ${t.roomCode} --password`),F(),process.exit(0)),console.error(`Rejoining room ${t.roomCode}\u2026`),console.error("");let o=new Ie.Doc,r=new Awareness(o);et(o,{participantId:e.participantId,displayName:e.displayName}),r.setLocalStateField("participantId",e.participantId);let s=false,p=false,a,l=ot({doc:o,awareness:r,roomCode:t.roomCode,roomUuid:t.roomUuid,participantId:e.participantId,relayUrl:e.relayUrl,passwordHash:t.passwordHash,onRejection(i){if(i==="unknown-room"){st({clearLastSession:F,logger:console,exit:process.exit});return}i==="wrong-password"&&(s=true,a?.()),i==="room-full"&&(p=true,a?.());}});rt({roomCode:l.roomCode,roomUuid:l.roomUuid,passwordHash:t.passwordHash,lastSeenAt:Date.now()});let u=render(St.createElement(vt,{session:l,participantId:e.participantId,displayName:e.displayName,accessMode:n.accessMode,presetOverrides:e.presetOverrides,customScales:e.customScales}));a=u.unmount,it({doc:l.doc,localParticipantId:e.participantId,onEnded:async i=>{console.error(i.isLocal?"Session ended.":`Session ended by ${i.hostName}.`),a?.();try{await F();}catch(c){console.error(`Failed to clear cached session: ${c instanceof Error?c.message:String(c)}`);}l.disconnect(),process.exit(0);}}),await u.waitUntilExit(),p&&(console.error("This room is full (max 20 participants)."),F(),process.exit(1)),s&&(console.error(`Password changed \u2014 rejoin with: hnch join ${t.roomCode} --password`),F(),process.exit(0)),l.disconnect(),F(),process.exit(0);}async function Qr(e){if(!(e.name!==void 0||e.relayUrl!==void 0||e.scale!==void 0||e.sessionName!==void 0||e.password!==void 0)){let o=await De(),{waitUntilExit:r}=render(St.createElement(Xn,{presetOverrides:o.presetOverrides,customScales:o.customScales}));await r(),process.exit(0);}let n={};if(e.name!==void 0&&(n.displayName=e.name),e.relayUrl!==void 0&&(n.relayUrl=e.relayUrl),e.scale!==void 0){if(e.scale!==""){let o=ge();if(!tt(e.scale,o.presetOverrides,o.customScales)){console.error(`Unknown scale: "${e.scale}". Builtin IDs: ${Object.keys(te).join(", ")}`);return}}n.defaultScale=e.scale;}e.sessionName!==void 0&&(n.defaultSessionName=e.sessionName),e.password!==void 0&&(n.defaultPassword=e.password),Me(n),console.log("Config updated.");}var to=Yr(hideBin(process.argv)).scriptName("hnch").usage("$0 <command> \u2014 terminal pointing poker").command("create","Create a new room",e=>e.option("name",{type:"string",describe:"Session name"}).option("password",{type:"boolean",describe:"Protect room with a password"}).option("scale",{type:"string",describe:"Estimation scale ID (fibonacci, t-shirt, powers-of-2, linear, risk, or custom-<id>)"}),e=>{qr(e).catch(t=>{console.error(t instanceof Error?t.message:t),process.exit(1);});}).command("join <code>","Join an existing room",e=>e.positional("code",{type:"string",demandOption:true,describe:"6-character room code"}).option("password",{type:"boolean",describe:"Room requires a password",default:false}),e=>{Gr(e).catch(t=>{console.error(t instanceof Error?t.message:t),process.exit(1);});}).command("resume","Rejoin your last session",e=>e,()=>{zr().catch(e=>{console.error(e instanceof Error?e.message:e),process.exit(1);});}).command("config","View or update CLI config",e=>e.option("name",{type:"string",describe:"Set display name"}).option("relay-url",{type:"string",describe:"Set relay URL"}).option("scale",{type:"string",describe:"Set default estimation scale ID (empty string clears)"}).option("session-name",{type:"string",describe:"Set default session name (empty string clears)"}).option("password",{type:"boolean",describe:"Set default password prompt on (--password) or off (--no-password)"}),e=>{Qr(e).catch(t=>{console.error(t instanceof Error?t.message:t),process.exit(1);});}).demandCommand(1,"Specify a command: create, join, resume, or config").strict().help(),Gt=hideBin(process.argv);(Gt.length===0||Gt.length===1&&Gt[0]==="--")&&(to.showHelp(),process.exit(0));to.parse();
|
|
2
|
+
import Jr from'yargs';import {hideBin}from'yargs/helpers';import St,{useState,useEffect,useCallback,useMemo,useRef}from'react';import {render,useApp,useInput,Box,Text}from'ink';import*as Ie from'yjs';import {Awareness}from'y-protocols/awareness';import {mkdirSync,chmodSync,writeFileSync,unlinkSync,existsSync,readFileSync}from'fs';import {homedir}from'os';import {join}from'path';import {randomUUID}from'crypto';import {createInterface}from'readline';import {WebsocketProvider}from'y-websocket';import Qo from'ws';import Ur from'clipboardy';import {jsxs,jsx,Fragment}from'react/jsx-runtime';var Po="https://hnch.dev";function Oe(){let t=process.env.XDG_CONFIG_HOME||join(homedir(),".config");return join(t,"hnch")}function It(){return join(Oe(),"config.json")}function nn(){let e=Oe();existsSync(e)||mkdirSync(e,{recursive:true});}function ge(){let e=It();if(!existsSync(e))return {};try{return JSON.parse(readFileSync(e,"utf-8"))}catch{return {}}}function Y(e){nn();let n={...ge(),...e};writeFileSync(It(),JSON.stringify(n,null,2)+`
|
|
3
|
+
`,"utf-8");}async function Oo(e){let t=createInterface({input:process.stdin,output:process.stderr});return new Promise(n=>{t.question(e,o=>{t.close(),n(o.trim());});})}function Do(e){return e.startsWith("wss://")?"https://"+e.slice(6):e.startsWith("ws://")?"http://"+e.slice(5):e}async function De(){let e=ge(),t=false;if(e.participantId||(e.participantId=randomUUID(),t=true),!e.displayName){let o=await Oo("Enter your display name: ");e.displayName=o||`user-${e.participantId.slice(0,6)}`,t=true;}e.relayUrl||(e.relayUrl=Po,t=true),t&&Y(e);let n=process.env.HNCH_RELAY_URL||e.relayUrl;return n=Do(n),{participantId:e.participantId,displayName:e.displayName,relayUrl:n,defaultScale:e.defaultScale,defaultSessionName:e.defaultSessionName,defaultPassword:e.defaultPassword,presetOverrides:e.presetOverrides,customScales:e.customScales}}function Me(e){nn();let n={...ge()};for(let[o,r]of Object.entries(e))r===""?delete n[o]:r!==void 0&&(n[o]=r);writeFileSync(It(),JSON.stringify(n,null,2)+`
|
|
4
|
+
`,"utf-8");}var Mo={"unknown-room":4001,"wrong-password":4002,"invalid-invite":4003,"expired-invite":4004,"invite-already-redeemed":4005,"room-full":4006,"malformed-credentials":4010},on=new Map(Object.entries(Mo).map(([e,t])=>[t,e]));function he(e){return e.role??"voter"}function G(e){let{revealedAt:t,skippedAt:n}=e;return t===void 0&&n===void 0?null:t===void 0?"skipped":n===void 0||t<=n?"revealed":"skipped"}var ze={id:"fibonacci",cards:["0","1","2","3","5","8","13","21"]},rn={id:"t-shirt",cards:["XS","S","M","L","XL"]},sn={id:"powers-of-2",cards:["1","2","4","8","16","32","64"]},an={id:"linear",cards:["1","2","3","4","5","6","7","8","9","10"]},cn={id:"risk",cards:["Low","Medium","High","Critical"]},te={[ze.id]:ze,[rn.id]:rn,[sn.id]:sn,[an.id]:an,[cn.id]:cn},dn={fibonacci:"Fibonacci","t-shirt":"T-shirt","powers-of-2":"Powers of 2",linear:"Linear (1\u201310)",risk:"Risk"};function ln(e){return e in dn?dn[e]:e.length===0?e:(e.startsWith("custom-")?e.slice(7):e).split("-").map(n=>n.length===0?n:n.charAt(0).toUpperCase()+n.slice(1)).join(" ")}var ko="meta",ke="settings",$o="participants",Bo="rounds",Ho=["roomUuid","createdAt","createdBy","sessionEndedAt"],Uo=["name","defaultScale","hostParticipantId"],jo=["displayName","joinedAt","role","leftAt","removedBy"],Lo=["id","title","description","ticketId","parentRoundId","scale","timerStartedAt","revealedAt","skippedAt","createdBy","createdAt"];function Qe(e){return e.getMap(ko)}function Ke(e){return e.getMap(ke)}function Xe(e){return e.getMap($o)}function Tt(e){return e.getArray(Bo)}function Ze(e,t){let n={};for(let o of t){let r=e.get(o);r!==void 0&&(n[o]=r);}return n}function _o(e){return Ze(Qe(e),Ho)}function Fo(e){return Ze(Ke(e),Uo)}function Vo(e){let t=Xe(e),n=[];return t.forEach((o,r)=>{let s=Ze(o,jo);n.push({id:r,...s});}),n.sort((o,r)=>o.joinedAt-r.joinedAt||o.id.localeCompare(r.id)),n}function Yo(e){let t={};return e.forEach((n,o)=>{t[o]=n;}),t}function Jo(e){let t=Ze(e,Lo),n=e.get("votes"),o=n?Yo(n):{};return {...t,votes:o}}function Wo(e){let t=Tt(e),n=[];return t.forEach(o=>{n.push(Jo(o));}),n}function z(e){return {meta:_o(e),settings:Fo(e),participants:Vo(e),rounds:Wo(e)}}function un(e,t){if(Qe(e).has("roomUuid"))throw new Error("initDoc: doc already seeded");e.transact(()=>{let n=Qe(e);n.set("roomUuid",t.roomUuid),n.set("createdAt",t.createdAt),n.set("createdBy",t.participantId);let o=Ke(e);t.sessionName!==void 0&&o.set("name",t.sessionName),o.set("defaultScale",t.defaultScale),o.set("hostParticipantId",t.participantId);let r=new Ie.Map;r.set("displayName",t.displayName),r.set("joinedAt",t.createdAt),Xe(e).set(t.participantId,r);});}function et(e,t){e.transact(()=>{let n=Xe(e),o=n.get(t.participantId),r=o??new Ie.Map;r.set("displayName",t.displayName),o===void 0&&r.set("joinedAt",Date.now()),r.delete("leftAt"),r.delete("removedBy"),o===void 0&&n.set(t.participantId,r);});}function Te(e,t){e.transact(()=>{let n=Ke(e);for(let[o,r]of Object.entries(t))r===void 0?n.delete(o):n.set(o,r);});}function Et(e,t,n){e.transact(()=>{let o=Xe(e),r=o.get(t),s=r??new Ie.Map;s.set("displayName",n.displayName),s.set("joinedAt",n.joinedAt),n.role!==void 0&&s.set("role",n.role),r===void 0&&o.set(t,s);});}function At(e,t){let n=t.scale??Ke(e).get("defaultScale");if(!n)throw new Error("appendRound: no scale (settings.defaultScale missing)");return e.transact(()=>{let o=new Ie.Map;o.set("id",t.id),o.set("createdAt",t.createdAt),o.set("createdBy",t.createdBy),o.set("scale",n),t.title!==void 0&&o.set("title",t.title),t.description!==void 0&&o.set("description",t.description),t.ticketId!==void 0&&o.set("ticketId",t.ticketId),t.parentRoundId!==void 0&&o.set("parentRoundId",t.parentRoundId),o.set("timerStartedAt",t.createdAt),o.set("votes",new Ie.Map),Tt(e).push([o]);}),t.id}function pn(e,t,n){let o=$e(e,t);if(!o)throw new Error(`patchRoundDescriptor: round ${t} not found`);e.transact(()=>{n.title!==void 0&&o.set("title",n.title),n.description!==void 0&&o.set("description",n.description);});}function $e(e,t){let n=Tt(e);for(let o=0;o<n.length;o+=1){let r=n.get(o);if(r.get("id")===t)return r}}function fn(e,t,n,o){let r=$e(e,t);if(!r)throw new Error(`castVote: round ${t} not found`);r.get("revealedAt")===void 0&&e.transact(()=>{r.get("votes").set(n,o);});}function mn(e,t,n){let o=$e(e,t);if(!o)throw new Error(`clearVote: round ${t} not found`);o.get("revealedAt")===void 0&&e.transact(()=>{o.get("votes").delete(n);});}function Nt(e,t,n){let o=$e(e,t);if(!o)throw new Error(`revealRound: round ${t} not found`);if(o.get("skippedAt")!==void 0)throw new Error(`revealRound: round ${t} already skipped`);e.transact(()=>{o.set("revealedAt",n);});}function gn(e,t,n){let o=$e(e,t);if(!o)throw new Error(`skipRound: round ${t} not found`);if(o.get("revealedAt")!==void 0)throw new Error(`skipRound: round ${t} already revealed`);if(o.get("skippedAt")!==void 0)throw new Error(`skipRound: round ${t} already skipped`);e.transact(()=>{o.set("skippedAt",n);});}function hn(e){e.transact(()=>{Qe(e).set("sessionEndedAt",Date.now());});}function X(e){let{rounds:t}=e;if(t.length===0)return null;for(let n=t.length-1;n>=0;n-=1){let o=t[n];if(o.revealedAt===void 0)return o}return t.at(-1)}function Sn(e){let t=Object.values(e.votes);return t.length<2?false:t.every(n=>n===t[0])}var xn="PBKDF2",qo="SHA-256",wn=new TextEncoder;function Go(e){return Array.from(new Uint8Array(e)).map(t=>t.toString(16).padStart(2,"0")).join("")}async function Se(e,t){let n=await crypto.subtle.importKey("raw",wn.encode(e),xn,false,["deriveBits"]),o=await crypto.subtle.deriveBits({name:xn,salt:wn.encode(t),iterations:1e5,hash:qo},n,256);return Go(o)}var yn="ABCDEFGHJKMNPQRSTUVWXYZ23456789";if(yn.length!==31)throw new Error(`Room code alphabet must be 31 characters, got ${yn.length}`);function Pt(e){return e.trim().replaceAll(/-/g,"").toUpperCase()}function vn(e,t=true){let n=Pt(e);return !t||n.length!==6?n:`${n.slice(0,3)}-${n.slice(3)}`}function tt(e,t,n){if(e.startsWith("custom-")){let r=(n??[]).find(s=>s.id===e);return r?{id:r.id,cards:r.cards}:void 0}let o=te[e];if(o){let r=(t??{})[e]??[];if(r.length===0)return o;let s=o.cards.filter((p,a)=>!r.includes(a));return s.length===0?void 0:{id:e,cards:s}}}function nt(e,t){let n=Object.keys(te).map(r=>tt(r,e,t)).filter(r=>r!==void 0),o=(t??[]).map(r=>({id:r.id,cards:r.cards}));return [...n,...o]}function xe(e,t){if(e.startsWith("custom-")){let n=(t??[]).find(o=>o.id===e);if(n&&n.label.trim().length>0)return n.label}return ln(e)}async function Cn(e,t){let n=await fetch(`${e}/rooms`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({})});if(!n.ok)throw new Error(`POST /rooms failed: ${n.status}`);return await n.json()}async function Be(e,t){let n=await fetch(`${e}/rooms/${encodeURIComponent(t)}`);if(n.status===404){let o=await n.json().catch(()=>null);throw new Error(`Room not found: ${o?.reason??"unknown-room"}`)}if(!n.ok)throw new Error(`GET /rooms/:code failed: ${n.status}`);return await n.json()}async function Ot(e,t,n){let o=await fetch(`${e}/rooms/${t}/access`,{method:"PATCH",headers:{"content-type":"application/json"},body:JSON.stringify({passwordHash:n})});if(!o.ok)throw new Error(`PATCH /rooms/:uuid/access failed: ${o.status}`)}function bn(e){let n=`${e.wsBaseUrl.replace(/^http/,"ws")}/ws`,o={did:e.deviceId};e.passwordHash!==void 0&&(o.pw=e.passwordHash);let r=new WebsocketProvider(n,e.roomUuid,e.doc,{params:o,connect:true,...e.awareness!==void 0&&{awareness:e.awareness},WebSocketPolyfill:Qo}),s=u=>{e.onStatusChange?.(u.status);},p=u=>{if(u===null)return;let i=on.get(u.code);i!==void 0&&(e.onRejection?.(i),r.shouldConnect=false,r.disconnect());};r.on("status",s),r.on("connection-close",p);let a=false,l=e.passwordHash;return {doc:e.doc,awareness:r.awareness,updateCredentials(u){if(u.passwordHash===l)return;l=u.passwordHash??void 0,r.shouldConnect=false,r.disconnect();let c={did:r.params.did??""};l!==void 0&&(c.pw=l),r.params=c,r.connect();},disconnect(){a||(a=true,r.off("status",s),r.off("connection-close",p),r.shouldConnect=false,r.disconnect(),r.destroy());}}}var Ko=5e3;function Rn(e,t,n){let o=null,r=null;function s(){let i=new Set;return t.getStates().forEach(c=>{let h=c.participantId;typeof h=="string"&&i.add(h);}),i}function p(){return e.getMap(ke).get("hostParticipantId")}function a(){r!==null&&(clearTimeout(r),r=null),o=null;}function l(){let i=p(),c=o;if(o=null,!c||i!==c)return;let h=s();if(h.has(i))return;let T=[...h].toSorted();if(T.length===0)return;let P=T[0];P===n&&Te(e,{hostParticipantId:P});}function u(){let i=p();if(!i)return;if(s().has(i)){a();return}o!==i&&(a(),o=i,r=setTimeout(()=>{r=null,l();},Ko));}return t.on("change",u),e.getMap(ke).observe(u),u(),{stop(){a(),t.off("change",u),e.getMap(ke).unobserve(u);}}}async function En(e){let t,n=await Cn(e.relayUrl);e.password&&(t=await Se(e.password,n.uuid),await Ot(e.relayUrl,n.uuid,t));let o=new Ie.Doc,r=new Awareness(o),s=Date.now();return un(o,{roomUuid:n.uuid,createdAt:s,participantId:e.participantId,displayName:e.displayName,defaultScale:e.defaultScale??ze,...e.sessionName!==void 0&&{sessionName:e.sessionName}}),r.setLocalStateField("participantId",e.participantId),ot({doc:o,awareness:r,roomCode:n.code,roomUuid:n.uuid,participantId:e.participantId,relayUrl:e.relayUrl,passwordHash:t})}async function An(e){let t=await Be(e.relayUrl,e.roomCode),n;if(t.accessMode==="password"){if(!e.password)throw new Error("Room requires a password");n=await Se(e.password,t.uuid);}let o=new Ie.Doc,r=new Awareness(o);return et(o,{participantId:e.participantId,displayName:e.displayName}),r.setLocalStateField("participantId",e.participantId),ot({doc:o,awareness:r,roomCode:e.roomCode,roomUuid:t.uuid,participantId:e.participantId,relayUrl:e.relayUrl,passwordHash:n,onRejection:e.onRejection})}function ot(e){let{doc:t,awareness:n,participantId:o,relayUrl:r,roomUuid:s}=e,p=new Set,a=new Set,l=new Set,u=bn({wsBaseUrl:e.relayUrl,roomUuid:e.roomUuid,doc:t,deviceId:o,passwordHash:e.passwordHash,awareness:n,onStatusChange(d){p.forEach(m=>m(d));},onRejection(d){console.error(`Connection rejected: ${d}`),e.onRejection?.(d);}}),i=Rn(t,n,o),c=()=>{a.forEach(d=>d());};t.getMap("meta").observeDeep(c),t.getMap("settings").observeDeep(c),t.getMap("participants").observeDeep(c),t.getArray("rounds").observeDeep(c);function h(){let d=new Set;return n.getStates().forEach(m=>{let v=m.participantId;typeof v=="string"&&d.add(v);}),d}let T=new Set,P=setInterval(()=>{let d=z(t),m=X(d);if(!m||m.revealedAt!==void 0||T.has(m.id))return;let v=h();if(v.size===0)return;let w=d.participants.filter(f=>v.has(f.id)&&he(f)==="voter");if(w.length===0)return;w.filter(f=>f.id in m.votes).length>=w.length&&(T.add(m.id),Nt(t,m.id,Date.now()));},250),E=t.getMap("settings").get("hostParticipantId"),I=()=>{let d=t.getMap("settings").get("hostParticipantId");d!==E&&d!==void 0&&l.forEach(m=>m(d)),E=d;};t.getMap("settings").observe(I);let H=false;return {doc:t,awareness:n,roomCode:e.roomCode,roomUuid:e.roomUuid,projection(){return z(t)},current(){return X(z(t))},isHost(){return z(t).settings.hostParticipantId===o},onlineIds:h,vote(d){let m=X(z(t));m&&fn(t,m.id,o,d);},unvote(){let d=X(z(t));d&&mn(t,d.id,o);},reveal(){let d=X(z(t));d&&Nt(t,d.id,Date.now());},skip(){let d=X(z(t));d&&gn(t,d.id,Date.now());},newRound(d){let m=randomUUID();return At(t,{id:m,createdAt:Date.now(),createdBy:o,...d!==void 0&&{title:d}}),m},revote(){let d=X(z(t)),m=randomUUID();return At(t,{id:m,createdAt:Date.now(),createdBy:o,...d?.id!==void 0&&{parentRoundId:d.id},...d?.title!==void 0&&{title:d.title},...d?.description!==void 0&&{description:d.description},...d?.ticketId!==void 0&&{ticketId:d.ticketId}}),m},updateRoundDescriptor(d,m){pn(t,d,m);},updateName(d){Te(t,{name:d});},updateScale(d){Te(t,{defaultScale:d});},updateDisplayName(d){let w=z(t).participants.find(M=>M.id===o)?.joinedAt??Date.now();Et(t,o,{displayName:d,joinedAt:w});},async updateAccess(d,m){let v=null;if(d==="password"){if(!m)throw new Error("Password required for password mode");v=await Se(m,s);}await Ot(r,s,v),u.updateCredentials({passwordHash:v});},handoffHost(d){if(!this.isHost())throw new Error("Only the current host can hand off");if(!h().has(d))throw new Error("Target participant is not online");if(!z(t).participants.some(M=>M.id===d))throw new Error("Target participant does not exist");Te(t,{hostParticipantId:d});},toggleRole(){let m=z(t).participants.find(f=>f.id===o),w=he(m??{})==="viewer"?"voter":"viewer",M=m?.joinedAt??Date.now();Et(t,o,{displayName:m?.displayName??"",joinedAt:M,role:w});},endSessionForEveryone(){if(!this.isHost())throw new Error("Only the current host can end the session");hn(t);},onChange(d){return a.add(d),()=>a.delete(d)},onStatusChange(d){return p.add(d),()=>p.delete(d)},onHostChange(d){return l.add(d),()=>l.delete(d)},disconnect(){H||(H=true,clearInterval(P),i.stop(),t.getMap("meta").unobserveDeep(c),t.getMap("settings").unobserveDeep(c),t.getMap("settings").unobserve(I),t.getMap("participants").unobserveDeep(c),t.getArray("rounds").unobserveDeep(c),u.disconnect());}}}function Nn(e){let t=Pt(e);return t===""?null:t}var rr=1440*60*1e3;function Mt(){return join(Oe(),"last-session.json")}function sr(e){if(typeof e!="object"||e===null)return false;let t=e;return typeof t.roomCode=="string"&&typeof t.roomUuid=="string"&&typeof t.lastSeenAt=="number"&&(t.passwordHash===void 0||typeof t.passwordHash=="string")}function On(){let e=Mt();if(!existsSync(e))return null;try{let t=JSON.parse(readFileSync(e,"utf-8"));return !sr(t)||Date.now()-t.lastSeenAt>rr?(F(),null):t}catch{return F(),null}}function rt(e){let t=Oe();mkdirSync(t,{recursive:true,mode:448}),chmodSync(t,448);let n=Mt();writeFileSync(n,JSON.stringify(e,null,2)+`
|
|
5
|
+
`,{encoding:"utf-8",mode:384}),chmodSync(n,384);}function F(){let e=Mt();try{unlinkSync(e);}catch(t){if(t.code!=="ENOENT")throw t}}async function st(e){let t=true;try{await e.clearLastSession();}catch(n){t=false,e.logger.error(`Failed to clear cached session: ${n instanceof Error?n.message:String(n)}`);}return t?e.logger.error("Session expired (room no longer exists). Cached session cleared."):e.logger.error("Session expired. Could not delete ~/.config/hnch/last-session.json \u2014 please remove it manually."),e.exit(1)}function it(e){let t=e.doc.getMap("meta"),n=null,o=false;function r(){if(o)return;let s=t.get("sessionEndedAt");if(s==null||s===n)return;n=s;let p=t.get("createdBy"),a=p===e.localParticipantId,l=e.doc.getMap("participants"),u=e.doc.getMap("settings").get("hostParticipantId")??p,i="host";if(u){let h=l.get(u)?.get("displayName");typeof h=="string"&&h.length>0&&(i=h);}setImmediate(()=>{o||e.onEnded({isLocal:a,hostName:i,timestamp:s});});}return t.observe(r),r(),()=>{o=true,t.unobserve(r);}}var ir="@xauyxau/hnch";function at(e){return vn(e)}function ct(e){return `${process.env.HNCH_SHARE_ORIGIN??"https://hnch.dev"}/join?code=${e}`}function dt(e){return `npx ${ir} join ${e}`}var ar={connecting:"yellow",connected:"green",disconnected:"red"};function kn({roomCode:e,sessionName:t,status:n,isHost:o}){let r=t?`${e} \xB7 ${t}`:e;return jsxs(Box,{borderStyle:"single",borderColor:"gray",paddingX:1,justifyContent:"space-between",children:[jsxs(Text,{bold:true,children:["hnch \xB7 ",r]}),jsxs(Box,{gap:2,children:[o&&jsx(Text,{color:"yellow",children:"[host]"}),jsx(Text,{color:ar[n],children:n})]})]})}function ce({items:e,onSelect:t,onClose:n,title:o}){if(e.length===0)return jsxs(Box,{flexDirection:"column",gap:1,children:[o&&jsx(Text,{bold:true,children:o}),jsx(Text,{dimColor:true,children:"(no items)"})]});let r=e.findIndex(a=>!a.disabled),[s,p]=useState(Math.max(r,0));return useInput((a,l)=>{if(l.upArrow)p(u=>{let i=(u-1+e.length)%e.length,c=0;for(;e[i]?.disabled&&c<e.length;)i=(i-1+e.length)%e.length,c++;return i});else if(l.downArrow)p(u=>{let i=(u+1)%e.length,c=0;for(;e[i]?.disabled&&c<e.length;)i=(i+1)%e.length,c++;return i});else if(l.return){let u=e[s];u&&!u.disabled&&t(u.value);}else l.escape&&n?.();}),jsxs(Box,{flexDirection:"column",gap:1,children:[o&&jsx(Text,{bold:true,children:o}),jsx(Box,{flexDirection:"column",children:e.map((a,l)=>{let u=l===s,i=a.disabled??false;return jsxs(Box,{gap:1,children:[jsx(Text,{...u&&{color:"cyan"},dimColor:i,children:u?">":" "}),jsx(Text,{bold:u,dimColor:i,children:a.label}),a.detail&&jsx(Text,{dimColor:true,children:a.detail})]},a.value)})}),jsx(Text,{dimColor:true,children:"\u2191\u2193 select \u23CE confirm esc close"})]})}function W({value:e,onChange:t,onSubmit:n,onCancel:o,placeholder:r,isActive:s=true,mask:p=false}){useInput((i,c)=>{c.escape?o?.():c.return?n(e):c.backspace||c.delete?t(e.slice(0,-1)):i&&!c.ctrl&&!c.meta&&t(e+i);},{isActive:s});let a=p&&e.length>0?"*".repeat(e.length):e,l=a.length>0?a:r??"",u=e.length===0&&r!==void 0;return jsxs(Text,{children:[jsx(Text,{dimColor:u,children:l}),jsx(Text,{inverse:true,children:" "})]})}function Un({mode:e,password:t,onModeChange:n,onPasswordChange:o,onClose:r}){let[s,p]=useState(e==="open"?0:1),[a,l]=useState(false);return useEffect(()=>{p(e==="open"?0:1);},[e]),useInput((u,i)=>{if(i.leftArrow)p(c=>Math.max(0,c-1));else if(i.rightArrow)p(c=>Math.min(1,c+1));else if(i.return){let c=s===0?"open":"password";c!==e&&n(c),c==="password"&&l(true);}else i.escape&&r?.();},{isActive:!a}),jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Box,{gap:1,children:["open","password"].map((u,i)=>{let c=i===s,h=u===e,T=u==="open"?"Open":"Password";return jsx(Text,{inverse:c,bold:h,...h&&{color:"cyan"},children:h?`[${T}]`:` ${T} `},u)})}),e==="password"&&jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{children:"Password: "}),jsx(W,{value:t,onChange:o,onSubmit:()=>l(false),onCancel:()=>l(false),placeholder:"(required to join)",isActive:a,mask:true}),!a&&jsx(Text,{dimColor:true,children:"\u23CE edit \u2190 \u2192 switch mode esc close"}),a&&jsx(Text,{dimColor:true,children:"\u23CE done esc cancel"})]}),e==="open"&&jsx(Text,{dimColor:true,children:"\u2190\u2192 toggle \u23CE confirm esc close"})]})}function _n({scaleId:e,cards:t,disabledIndices:n,onOverrideChange:o,onClose:r}){let s=useMemo(()=>new Set(n),[n]),[p,a]=useState(0),l=u=>{let i=new Set(s);if(i.has(u))i.delete(u);else {if(i.size>=t.length-1)return;i.add(u);}let c=Array.from(i).toSorted((h,T)=>h-T);o(e,c);};return useInput((u,i)=>{i.leftArrow?a(c=>Math.max(0,c-1)):i.rightArrow?a(c=>Math.min(t.length-1,c+1)):u===" "?l(p):(i.return||i.escape)&&r();}),jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Box,{gap:1,children:t.map((u,i)=>{let c=i===p,h=s.has(i);return jsx(Text,{inverse:c,bold:!h,...!h&&{color:"cyan"},strikethrough:h,dimColor:h,children:u},u)})}),jsx(Text,{dimColor:true,children:" \u2190\u2192 navigate space toggle \u23CE/esc done"})]})}function _t({initialName:e="",initialCards:t=[],onSave:n,onCancel:o}){let r=e.length>0,[s,p]=useState(e),[a,l]=useState(t.length>0?t:[""]),[u,i]=useState(0),[c,h]=useState("editing-name"),[T,P]=useState(""),E=()=>{let d=s.trim();if(d.length===0||d.length>40){P("Name must be 1-40 characters");return}p(d),P(""),h("editing-cards");},I=()=>{let d=s.trim();if(d.length===0||d.length>40){P("Name must be 1-40 characters");return}let m=a.map(w=>w.trim()).filter(w=>w.length>0),v=Array.from(new Set(m));if(v.length<2){P("Need at least 2 unique non-empty cards");return}P(""),n(d,v);};return useInput((d,m)=>{if(c==="editing-cards"){if(m.leftArrow)i(v=>Math.max(0,v-1));else if(m.rightArrow)i(v=>{let w=v+1;return w>=a.length&&a.length<25?(l(M=>[...M,""]),w):Math.min(w,a.length-1)});else if(m.return)I();else if(m.escape)o();else if(m.tab)h("editing-name");else if(m.backspace){let v=a[u]||"";if(v.length===0){if(a.length>2){let w=a.filter((M,f)=>f!==u);l(w),i(Math.max(0,u-1));}}else {let w=[...a];w[u]=v.slice(0,-1),l(w);}}else if(d&&!m.ctrl&&!m.meta){let v=a[u]||"";if(v.length<10){let w=[...a];w[u]=v+d,l(w);}}}},{isActive:c==="editing-cards"}),jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:r?"\u2500\u2500 Edit Custom Scale \u2500\u2500":"\u2500\u2500 Create Custom Scale \u2500\u2500"}),jsxs(Box,{flexDirection:"column",gap:0,children:[jsx(Text,{children:"Name:"}),jsx(W,{value:s,onChange:p,onSubmit:E,onCancel:o,placeholder:"Scale name",isActive:c==="editing-name"})]}),c==="editing-cards"&&jsxs(Box,{flexDirection:"column",gap:0,children:[jsx(Text,{children:"Cards:"}),jsx(Box,{gap:1,flexWrap:"wrap",children:a.map((d,m)=>{let v=m===u;return jsx(Text,{inverse:v,...!v&&{color:"cyan"},children:d.length>0?d:"\xB7"},m)})})]}),T&&jsx(Text,{color:"red",children:T}),jsx(Text,{dimColor:true,children:c==="editing-name"?"\u23CE next esc cancel":"\u2190\u2192 navigate typing adds card bsp delete \u23CE save tab name esc cancel"})]})}function pt({scales:e,selectedScaleId:t,presetOverrides:n,customScales:o=[],onSelect:r,onOverrideChange:s,onCustomScaleCreate:p,onCustomScaleEdit:a,onCustomScaleDelete:l,onClose:u,showNoneOption:i=false}){let[c,h]=useState(0),[T,P]=useState(null),[E,I]=useState("browsing"),[H,d]=useState(null),[m,v]=useState(null),w=p?[...e,{id:"__create-custom__",cards:[]}]:e,M=w.length;if(useInput((f,k)=>{if(E==="browsing"){if(k.escape){u();return}if(M===0)return;if(k.upArrow)h(b=>(b-1+M)%M);else if(k.downArrow)h(b=>(b+1)%M);else if(k.return){let b=w[c];if(!b)return;if(b.id==="__create-custom__"){I("creating");return}b.id in te?P(T===b.id?null:b.id):r(b.id);}else if(f===" "){let b=w[c];b&&b.id!=="__create-custom__"&&r(b.id);}else if(f==="e"||f==="E"){let b=w[c];a&&b?.id.startsWith("custom-")&&(d(b.id),I("editing"));}else if(f==="d"||f==="D"){let b=w[c];l&&b?.id.startsWith("custom-")&&(v(b.id),I("confirming-delete"));}}else E==="confirming-delete"&&(f==="y"||f==="Y"?(m&&l&&l(m),v(null),I("browsing")):(f==="n"||f==="N"||k.escape)&&(v(null),I("browsing")));},{isActive:E==="browsing"&&T===null||E==="confirming-delete"}),E==="creating")return jsx(_t,{onSave:(f,k)=>{p&&p(f,k),I("browsing");},onCancel:()=>I("browsing")});if(E==="editing"&&H){let f=o?.find(b=>b.id===H);if(e.find(b=>b.id===H)&&f)return jsx(_t,{initialName:f.label,initialCards:f.cards,onSave:(b,L)=>{a&&a(H,b,L),d(null),I("browsing");},onCancel:()=>{d(null),I("browsing");}})}if(E==="confirming-delete"&&m){let f=w.find(b=>b.id===m),k=f?xe(f.id,o):"Unknown";return jsxs(Box,{flexDirection:"column",gap:1,children:[jsxs(Text,{children:["Delete ",k,"? "]}),jsx(Text,{children:"[y] yes [n] no esc cancel"})]})}return w.length===0?jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{dimColor:true,children:"(no scales available)"}),jsx(Text,{dimColor:true,children:"esc back"})]}):jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Box,{flexDirection:"column",children:w.map((f,k)=>{let b=k===c,L=f.id===t,oe=T===f.id,Z=f.id in te,ee=f.id==="__create-custom__",re=f.cards.join(", "),g=i&&f.id==="",C=g?"(none)":ee?"[+ Create custom scale]":xe(f.id,o);return jsxs(Box,{flexDirection:"column",children:[jsxs(Box,{gap:1,children:[jsx(Text,{...b&&{color:"cyan"},children:b?">":" "}),jsx(Text,{bold:b,children:C}),!g&&!ee&&jsx(Text,{dimColor:true,children:re}),L&&jsx(Text,{color:"cyan",children:"[current]"})]}),oe&&!g&&!ee&&jsx(Box,{paddingLeft:2,children:Z?jsx(_n,{scaleId:f.id,cards:te[f.id]?.cards??[],disabledIndices:n?.[f.id]??[],onOverrideChange:s,onClose:()=>P(null)}):null})]},f.id||"none")})}),jsx(Text,{dimColor:true,children:(()=>{let f=c<w.length?w[c]:null;return f?.id.startsWith("custom-")?`\u2191\u2193 navigate space select${a?" [e] edit":""}${l?" [d] delete":""} esc back`:f&&f.id in te?"\u2191\u2193 navigate \u23CE customize space select esc back":"\u2191\u2193 navigate \u23CE/space select esc back"})()})]})}function vr(e,t){return t?[...e]:e.filter(n=>n.value==="display-name")}function Fn({sessionName:e,displayName:t,currentScale:n,accessMode:o,presetOverrides:r,customScales:s,isHost:p=true,onChangeName:a,onChangeScale:l,onChangeDisplayName:u,onChangeAccess:i,onClose:c}){let[h,T]=useState(r??{}),P=useMemo(()=>nt(h,s),[h,s]),[E,I]=useState("main-menu"),[H,d]=useState(e??""),[m,v]=useState(t),[w,M]=useState(o==="password"?"password":"open"),[f,k]=useState(""),[b,L]=useState(false),oe=[{label:"Room name",value:"room-name",detail:`[${e??"(none)"}]`},{label:"Scale",value:"scale",detail:`[${xe(n.id,s)}]`},{label:"Access",value:"access",detail:`[${o==="open"?"Open":"Password"}]`},{label:"Display name",value:"display-name",detail:`[${t}]`}],Z=vr(oe,p);function ee(O){!p&&O!=="display-name"||(O==="room-name"?(d(e??""),I("editing-room-name")):O==="scale"?I("picking-scale"):O==="access"?I("picking-access"):O==="display-name"&&(v(t),I("editing-display-name")));}function re(O){let D=O.trim()===""?void 0:O.trim();a(D),I("main-menu");}function g(O){let D=O.trim();D.length>0&&(u(D),process.nextTick(()=>Y({displayName:D}))),I("main-menu");}function C(O){let D=P.find(_=>_.id===O);D&&l(D),I("main-menu");}function A(O,D){let _={...h};D.length===0?delete _[O]:_[O]=D,T(_),Y({presetOverrides:Object.keys(_).length>0?_:void 0});}async function B(O,D){L(true);try{await i(O,D),M(O),k(O==="password"?D??"":"");}catch(_){console.error(`Failed to update access: ${_}`);}finally{L(false),I("main-menu");}}return E==="main-menu"?jsx(ce,{title:"\u2500\u2500 Settings \u2500\u2500",items:Z,onSelect:ee,onClose:c}):E==="editing-room-name"?jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:"\u2500\u2500 Settings \u2500\u2500"}),jsx(Text,{children:"Room name: "}),jsx(W,{value:H,onChange:d,onSubmit:re,onCancel:()=>I("main-menu"),placeholder:"(leave empty to remove)",isActive:true}),jsx(Text,{dimColor:true,children:"\u23CE confirm esc back"})]}):E==="editing-display-name"?jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:"\u2500\u2500 Settings \u2500\u2500"}),jsx(Text,{children:"Display name: "}),jsx(W,{value:m,onChange:v,onSubmit:g,onCancel:()=>I("main-menu"),placeholder:"(required)",isActive:true}),jsx(Text,{dimColor:true,children:"\u23CE confirm esc back"})]}):E==="picking-scale"?jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:"\u2500\u2500 Settings \u2500\u2500"}),jsx(pt,{scales:P,selectedScaleId:n.id,presetOverrides:h,onSelect:C,onOverrideChange:A,onClose:()=>I("main-menu")})]}):E==="picking-access"?jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:"\u2500\u2500 Settings \u2500\u2500"}),b&&jsx(Text,{dimColor:true,children:"Updating access..."}),!b&&jsx(Un,{mode:w,password:f,onModeChange:O=>{O==="open"&&B("open");},onPasswordChange:k,onClose:()=>I("main-menu")})]}):jsx(Box,{})}function gt({participants:e,onlineIds:t,hostId:n,votes:o,revealed:r,participantId:s}){return jsxs(Box,{flexDirection:"column",children:[jsxs(Text,{bold:true,children:["Participants (",e.length,")"]}),e.map(p=>{let a=t.has(p.id),l=a?"\u25CF":"\u25CB",u=a?"green":"gray",i=p.id===n,c=he(p)==="viewer",h=p.id===s,T=p.id in o,P=c?" (viewer)":r?o[p.id]?` \u2192 ${o[p.id]}`:"":T?" \u2713":" \xB7",E=h&&!c&&!(r&&o[p.id]);return jsxs(Box,{gap:1,children:[jsx(Text,{color:u,children:l}),jsxs(Text,{children:[p.displayName,i?" (host)":""]}),E?jsx(Text,{color:"cyan",children:"(voter)"}):jsx(Text,{color:c?"gray":T?"green":"gray",children:P})]},p.id)})]})}var Cr=[{label:"End session for everyone",value:"end-session",detail:"Disconnects all participants. Cannot be undone."}];function Yn({onPick:e,onClose:t}){return jsx(ce,{title:"Host Actions",items:Cr,onSelect:e,onClose:t})}function ht({roomCode:e,onPick:t,onClose:n}){let o=[{label:"Copy code",value:"code",detail:at(e)},{label:"Copy share link",value:"url",detail:ct(e)},{label:"Copy npx command",value:"npx",detail:dt(e)}];return jsx(ce,{title:"Share",items:o,onSelect:r=>t(r),onClose:n})}function Jn(e){let t=Object.values(e).map(i=>parseFloat(i)).filter(i=>!isNaN(i)).toSorted((i,c)=>i-c);if(t.length===0)return {average:"-",median:"-",agreement:"-"};let o=t.reduce((i,c)=>i+c,0)/t.length,r=Math.floor(t.length/2),s=t.length%2===0?(t[r-1]+t[r])/2:t[r],p=Object.values(e),a=new Map;for(let i of p)a.set(i,(a.get(i)??0)+1);let l=Math.max(...a.values()),u=p.length>0?Math.round(l/p.length*100):0;return {average:o.toFixed(1),median:s.toFixed(1),agreement:`${u}%`}}function Vt(e,t){let n=Math.floor((t-e)/1e3),o=Math.floor(n/60),r=n%60;return `${o}:${r.toString().padStart(2,"0")}`}function Wn(e,t){if(G(e)==="skipped"){let a=e.title?` ${e.title}`:"",l=e.ticketId?` [${e.ticketId}]`:"";return `Round ${t}:${a}${l} \u2014 Skipped`}let n=new Map;for(let a of Object.values(e.votes))n.set(a,(n.get(a)??0)+1);let o=[...n.entries()].toSorted((a,l)=>l[1]-a[1]||a[0].localeCompare(l[0])).map(([a,l])=>`${a}\xD7${l}`).join(", "),r=e.title?` ${e.title}`:"",s=e.ticketId?` [${e.ticketId}]`:"",p=e.timerStartedAt!==void 0&&e.revealedAt!==void 0?` \u2014 ${Vt(e.timerStartedAt,e.revealedAt)}`:"";return `Round ${t}:${r}${s} \u2014 ${o||"(no votes)"}${p}`}function qn({projection:e,onlineIds:t,isHost:n,participantId:o,rounds:r,currentRound:s,settingsOpen:p,onStartRound:a,onNewRound:l,onRevote:u,onOpenSettings:i,onHandoffHost:c,onEndSession:h,onShare:T,shareOpen:P,onSetShareOpen:E,roomCode:I,confirmingQuit:H,myRole:d,onToggleRole:m,roundTitlePromptActive:v,roundTitleInput:w,onRoundTitleChange:M,onRoundTitleSubmit:f,onRoundTitleCancel:k,descriptorPromptActive:b,descriptorInput:L,onDescriptorChange:oe,onDescriptorSubmit:Z,onDescriptorCancel:ee,onEditDescriptor:re}){let[g,C]=St.useState("idle"),[A,B]=St.useState(null),[O,D]=St.useState(false),[_,me]=St.useState(false);useEffect(()=>{n||(C("idle"),B(null),D(false),me(false));},[n]),useInput(S=>{if(S==="c"||S==="C"){E(true);return}if(S==="w"||S==="W"){m();return}n&&((S==="e"||S==="E")&&i(),(S==="s"||S==="S")&&a(),(S==="h"||S==="H")&&C("picking"),(S==="m"||S==="M")&&D(true),s!==null&&G(s)!==null&&((S==="n"||S==="N")&&l(),(S==="v"||S==="V")&&G(s)!=="skipped"&&u(),(S==="t"||S==="T")&&re()));},{isActive:!p&&!H&&g==="idle"&&!v&&!O&&!_&&!P&&!b}),useInput((S,K)=>{S==="y"||S==="Y"?(h(),me(false)):(S==="n"||S==="N"||K.escape)&&me(false);},{isActive:_});let se=e.participants.filter(S=>S.id!==o&&t.has(S.id));useInput(S=>{if(g==="picking"){let K=parseInt(S,10);!isNaN(K)&&K>=1&&K<=se.length?(B(se[K-1].id),C("confirming")):(S==="Escape"||S==="q"||S==="Q")&&C("idle");}},{isActive:g==="picking"}),useInput(S=>{g==="confirming"&&A&&(S==="y"||S==="Y"?(c(A),C("idle"),B(null)):(S==="n"||S==="N"||S==="Escape")&&(C("idle"),B(null)));},{isActive:g==="confirming"});let Ye=r.length>0,Pe=r.filter(S=>G(S)!==null),Je=s!==null?Pe.slice(0,-1):Pe;return O?jsx(Yn,{onPick:S=>{D(false),S==="end-session"&&me(true);},onClose:()=>D(false)}):P?jsx(ht,{roomCode:I,onPick:S=>{E(false),T(S);},onClose:()=>E(false)}):jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:Ye?"Session":"Waiting for participants..."}),!Ye&&jsx(Text,{dimColor:true,children:"No rounds started yet."}),jsx(gt,{participants:e.participants,onlineIds:t,hostId:e.settings.hostParticipantId,votes:{},revealed:false,participantId:o}),s!==null&&G(s)!==null&&jsx(Tr,{projection:e,round:s,rounds:r}),Je.length>0&&jsxs(Box,{flexDirection:"column",children:[jsx(Text,{dimColor:true,children:"\u2500\u2500 History \u2500\u2500"}),Je.map(S=>{let K=r.indexOf(S)+1;return jsx(Text,{dimColor:true,children:Wn(S,K)},S.id)})]}),g==="picking"&&se.length>0&&jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{children:"Hand off to:"}),se.map((S,K)=>jsxs(Text,{children:["[",K+1,"] ",S.displayName]},S.id)),jsx(Text,{dimColor:true,children:"[q] Cancel"})]}),g==="picking"&&se.length===0&&jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{dimColor:true,children:"No other online participants"}),jsx(Text,{dimColor:true,children:"[q] Cancel"})]}),g==="confirming"&&A&&jsx(Box,{flexDirection:"column",children:jsxs(Text,{children:["Hand off to ",e.participants.find(S=>S.id===A)?.displayName,"? [y/n]"]})}),v&&jsxs(Box,{flexDirection:"column",children:[jsxs(Box,{gap:1,children:[jsx(Text,{children:"Round title (optional, Enter to skip):"}),jsx(W,{value:w,onChange:M,onSubmit:f,onCancel:k,placeholder:"Enter to skip",isActive:v})]}),jsx(Text,{dimColor:true,children:"[Esc] Cancel"})]}),b&&jsxs(Box,{flexDirection:"column",children:[jsxs(Box,{gap:1,children:[jsx(Text,{children:"Edit round title:"}),jsx(W,{value:L,onChange:oe,onSubmit:Z,onCancel:ee,placeholder:"Enter to save",isActive:b})]}),jsx(Text,{dimColor:true,children:"[Esc] Cancel \xB7 empty submit cancels"})]}),_&&jsxs(Box,{children:[jsx(Text,{color:"red",children:"End session for everyone? This cannot be undone. "}),jsx(Text,{dimColor:true,children:"y/n [Esc] Cancel"})]}),g==="idle"&&!v&&!b&&!_&&jsxs(Box,{gap:2,children:[n?jsx(Fragment,{children:s!==null&&G(s)!==null?jsxs(Fragment,{children:[G(s)!=="skipped"&&jsx(Text,{color:"yellow",children:"[v] Revote"}),jsx(Text,{color:"yellow",children:"[n] Next"}),jsx(Text,{color:"yellow",children:"[t] Edit title"}),jsx(Text,{color:"yellow",children:"[h] Handoff"}),jsx(Text,{color:"yellow",children:"[e] Settings"}),jsx(Text,{color:"yellow",children:"[c] Share"}),jsxs(Text,{color:"yellow",children:["[w] ",d==="viewer"?"Vote":"Observe"]}),jsx(Text,{color:"yellow",children:"[m] Menu"})]}):jsxs(Fragment,{children:[jsx(Text,{color:"yellow",children:"[s] Start round"}),jsx(Text,{color:"yellow",children:"[h] Handoff"}),jsx(Text,{color:"yellow",children:"[e] Settings"}),jsx(Text,{color:"yellow",children:"[c] Share"}),jsxs(Text,{color:"yellow",children:["[w] ",d==="viewer"?"Vote":"Observe"]}),jsx(Text,{color:"yellow",children:"[m] Menu"})]})}):jsxs(Fragment,{children:[jsx(Text,{dimColor:true,children:"Waiting for host to start a round..."}),jsx(Text,{children:"[c] Share"}),jsxs(Text,{color:"yellow",children:["[w] ",d==="viewer"?"Vote":"Observe"]})]}),jsx(Text,{children:"[q] Quit"})]})]})}function Tr({projection:e,round:t,rounds:n}){let o=n.indexOf(t)+1,r=t.title?` \u2014 ${t.title}`:"",s=t.ticketId?` [${t.ticketId}]`:"";if(G(t)==="skipped")return jsx(Box,{flexDirection:"column",gap:1,children:jsxs(Text,{bold:true,children:["Round ",o,r,s," \u2014 Skipped"]})});let p=Jn(t.votes),a=new Map(e.participants.map(i=>[i.id,i.displayName])),l=t.timerStartedAt!==void 0&&t.revealedAt!==void 0?Vt(t.timerStartedAt,t.revealedAt):null,u=Object.values(t.votes)[0];return jsxs(Box,{flexDirection:"column",gap:1,children:[jsxs(Text,{bold:true,children:["Round ",o,r,s," \u2014 Results"]}),Sn(t)&&jsxs(Text,{bold:true,color:"green",children:["Consensus! Everyone voted ",u]}),jsxs(Box,{flexDirection:"column",children:[jsxs(Box,{gap:2,children:[jsx(Text,{bold:true,underline:true,children:"Name".padEnd(20)}),jsx(Text,{bold:true,underline:true,children:"Estimate".padEnd(10)})]}),Object.entries(t.votes).map(([i,c])=>jsxs(Box,{gap:2,children:[jsx(Text,{children:(a.get(i)??i).padEnd(20)}),jsx(Text,{color:"cyan",bold:true,children:c.padEnd(10)})]},i)),e.participants.filter(i=>!(i.id in t.votes)).map(i=>jsxs(Box,{gap:2,children:[jsx(Text,{dimColor:true,children:i.displayName.padEnd(20)}),jsx(Text,{dimColor:true,children:"(no vote)".padEnd(10)})]},i.id))]}),jsxs(Box,{gap:2,children:[jsxs(Text,{children:["Avg: ",jsx(Text,{bold:true,children:p.average})]}),jsxs(Text,{children:["Med: ",jsx(Text,{bold:true,children:p.median})]}),jsxs(Text,{children:["Agreement: ",jsx(Text,{bold:true,children:p.agreement})]}),l!==null&&jsxs(Text,{dimColor:true,children:["\u23F1 ",l]})]})]})}function Qn({scale:e,selected:t,onSelect:n,onClear:o,disabled:r}){let[s,p]=useState(0);return useInput((a,l)=>{if(!r)if(l.leftArrow)p(u=>Math.max(0,u-1));else if(l.rightArrow)p(u=>Math.min(e.cards.length-1,u+1));else if(l.return){let u=e.cards[s];u===t?o():n(u);}else (l.backspace||l.delete)&&o();}),jsxs(Box,{flexDirection:"column",gap:0,children:[jsx(Box,{gap:1,children:e.cards.map((a,l)=>{let u=l===s,i=a===t;return jsx(Text,{inverse:u,bold:i,...i&&{color:"cyan"},children:i?`[${a}]`:` ${a} `},a)})}),jsx(Text,{dimColor:true,children:" \u2190\u2192 navigate \u23CE select \u232B clear"})]})}function Or(e){let t=Math.floor(e/1e3),n=Math.floor(t/60),o=t%60;return `${n}:${o.toString().padStart(2,"0")}`}function Kn({projection:e,round:t,onlineIds:n,isHost:o,myId:r,myRole:s,elapsedMs:p,roomCode:a,shareOpen:l,settingsOpen:u,onVote:i,onClearVote:c,onReveal:h,onSkip:T,onToggleRole:P,onShare:E,onSetShareOpen:I,onOpenSettings:H,confirmingQuit:d}){if(useInput(f=>{if(f==="e"||f==="E"){H();return}if(f==="c"||f==="C"){I(true);return}o&&(f==="r"||f==="R")&&h(),o&&(f==="s"||f==="S")&&T(),(f==="w"||f==="W")&&P();},{isActive:!d&&!u&&!l}),l)return jsx(ht,{roomCode:a,onPick:f=>{I(false),E(f);},onClose:()=>I(false)});let m=t.votes[r],v=e.rounds.indexOf(t)+1,w=t.title?` \u2014 ${t.title}`:"",M=t.ticketId?` [${t.ticketId}]`:"";return jsxs(Box,{flexDirection:"column",gap:1,children:[jsxs(Box,{gap:2,children:[jsxs(Text,{bold:true,children:["Round ",v,w,M]}),p!==void 0&&jsxs(Text,{dimColor:true,children:["\u23F1 ",Or(p)]})]}),jsxs(Box,{gap:4,children:[jsx(gt,{participants:e.participants,onlineIds:n,hostId:e.settings.hostParticipantId,votes:t.votes,revealed:false}),s==="viewer"?jsx(Box,{flexDirection:"column",children:jsx(Text,{dimColor:true,children:"You're observing"})}):jsx(Qn,{scale:t.scale,selected:m,onSelect:i,onClear:c,disabled:d})]}),jsxs(Box,{gap:2,children:[o&&jsx(Text,{color:"yellow",children:"[r] Reveal"}),o&&jsx(Text,{color:"yellow",children:"[s] Skip"}),jsxs(Text,{color:"yellow",children:["[w] ",s==="viewer"?"Vote":"Observe"]}),jsx(Text,{children:"[e] Settings"}),jsx(Text,{children:"[c] Share"}),jsx(Text,{children:"[q] Quit"})]})]})}var $r=3e3;function Wt(e=$r){let[t,n]=useState(null),o=useRef(null),r=useCallback(()=>{o.current!==null&&(clearTimeout(o.current),o.current=null);},[]),s=useCallback(a=>{r(),n(a),o.current=setTimeout(()=>{n(null),o.current=null;},e);},[r,e]),p=useCallback(()=>{r(),n(null);},[r]);return useEffect(()=>()=>{r();},[r]),{notice:t,show:s,dismiss:p}}function jr(e){let t=X(e);return !t||G(t)!==null?"lobby":"voting"}function vt({session:e,participantId:t,displayName:n,accessMode:o,presetOverrides:r,customScales:s}){let{exit:p}=useApp(),[a,l]=useState(e.projection()),[u,i]=useState("connecting"),[c,h]=useState(e.onlineIds()),[T,P]=useState(),[E,I]=useState(false),[H,d]=useState(o==="password"?"password":"open"),[m,v]=useState(n),[w,M]=useState(false),[f,k]=useState(false),[b,L]=useState(""),[oe,Z]=useState(false),[ee,re]=useState(""),[g,C]=useState(null),A=Wt(3e3),[B,O]=useState(false),D=Wt(3e3);useEffect(()=>{let R=e.onChange(()=>{l(e.projection()),h(e.onlineIds());}),V=e.onStatusChange(i),We=e.onHostChange(qe=>{let Co=e.projection().participants.find(Ro=>Ro.id===qe),bo=qe===t?"You are now the host":`${Co?.displayName||"Unknown"} is now the host`;A.show(bo);}),vo=setInterval(()=>{h(e.onlineIds());let qe=e.projection(),Ge=X(qe);Ge?.timerStartedAt!==void 0&&Ge.revealedAt===void 0?P(Date.now()-Ge.timerStartedAt):P(void 0);},1e3);return ()=>{R(),V(),We(),clearInterval(vo);}},[e,t,A.show]),useInput(R=>{(R==="q"||R==="Q")&&M(true);},{isActive:!E&&!w&&!f&&!oe&&!B}),useInput((R,V)=>{R==="y"||R==="Y"?(e.disconnect(),p()):(R==="n"||R==="N"||V.escape)&&M(false);},{isActive:w});let _=jr(a),me=X(a),se=a.settings.hostParticipantId===t,Ye=a.participants.find(R=>R.id===t),Pe=he(Ye??{}),Je=useCallback(()=>{L(""),k(true);},[]),S=useCallback(R=>{k(false),L(""),e.newRound(R.trim()||void 0);},[e]),K=useCallback(()=>{k(false),L("");},[]),oo=useCallback(()=>{let R=e.current();!R||R.revealedAt===void 0||(C(R.id),re(R.title??""),Z(true));},[e]),ro=useCallback(R=>{let V=g;if(Z(false),C(null),!V)return;let We=R.trim();We!==""&&e.updateRoundDescriptor(V,{title:We});},[e,g]),so=useCallback(()=>{Z(false),C(null);},[]),io=useCallback(R=>{e.vote(R);},[e]),ao=useCallback(()=>{e.unvote();},[e]),co=useCallback(()=>{e.reveal();},[e]),lo=useCallback(()=>{e.skip();},[e]),uo=useCallback(()=>{L(""),k(true);},[]),po=useCallback(()=>{e.revote();},[e]),Qt=useCallback(()=>{e.toggleRole();},[e]),fo=useCallback(R=>{e.updateName(R);},[e]),mo=useCallback(R=>{e.updateScale(R);},[e]),go=useCallback(R=>{e.updateDisplayName(R),v(R);},[e]),ho=useCallback(async(R,V)=>{await e.updateAccess(R,V),d(R);},[e]),So=useCallback(R=>{try{e.handoffHost(R);}catch(V){console.error(`Handoff failed: ${V instanceof Error?V.message:String(V)}`);}},[e]),Kt=useCallback(()=>{I(true);},[]),xo=useCallback(()=>{I(false);},[]),wo=useCallback(()=>{e.endSessionForEveryone();},[e]),Xt=useCallback(async R=>{let V=R==="code"?at(e.roomCode):R==="url"?ct(e.roomCode):dt(e.roomCode);try{await Ur.write(V),D.show("Copied!");}catch{D.show(`Copy failed \u2014 ${V}`);}},[e.roomCode,D]),Zt=a.rounds.filter(R=>G(R)!==null),yo=Zt.length>0?Zt.at(-1):null;return jsxs(Box,{flexDirection:"column",children:[jsx(kn,{roomCode:e.roomCode,sessionName:a.settings.name,status:u,isHost:se}),A.notice&&jsx(Box,{paddingX:1,children:jsx(Text,{color:"yellow",children:A.notice})}),D.notice&&jsx(Box,{paddingX:1,children:jsx(Text,{color:D.notice.startsWith("Copy failed")?"red":"green",children:D.notice})}),E?jsx(Fn,{sessionName:a.settings.name,displayName:m,currentScale:a.settings.defaultScale,accessMode:H,presetOverrides:r,customScales:s,isHost:se,onChangeName:fo,onChangeScale:mo,onChangeDisplayName:go,onChangeAccess:ho,onClose:xo}):_==="lobby"?jsx(qn,{projection:a,onlineIds:c,isHost:se,participantId:t,rounds:a.rounds,currentRound:yo,settingsOpen:E,onStartRound:Je,onNewRound:uo,roundTitlePromptActive:f,roundTitleInput:b,onRoundTitleChange:L,onRoundTitleSubmit:S,onRoundTitleCancel:K,onRevote:po,onOpenSettings:Kt,onHandoffHost:So,onEndSession:wo,onShare:Xt,shareOpen:B,onSetShareOpen:O,roomCode:e.roomCode,confirmingQuit:w,myRole:Pe,onToggleRole:Qt,descriptorPromptActive:oe,descriptorInput:ee,onDescriptorChange:re,onDescriptorSubmit:ro,onDescriptorCancel:so,onEditDescriptor:oo}):_==="voting"&&me?jsx(Kn,{projection:a,round:me,onlineIds:c,isHost:se,myId:t,myRole:Pe,elapsedMs:T,onVote:io,onClearVote:ao,onReveal:co,onSkip:lo,onToggleRole:Qt,onShare:Xt,onSetShareOpen:O,shareOpen:B,settingsOpen:E,onOpenSettings:Kt,roomCode:e.roomCode,confirmingQuit:w}):null,w&&jsx(Box,{children:jsx(Text,{dimColor:true,children:"Leave session? y/n"})})]})}function Zn({presetOverrides:e,customScales:t}){let{exit:n}=useApp(),[o,r]=useState("main-menu"),[s,p]=useState(ge()),[a,l]=useState(s.displayName??""),[u,i]=useState(s.relayUrl??""),[c,h]=useState(s.defaultSessionName??""),[T,P]=useState(t??[]),[E,I]=useState(e??{});useInput(g=>{(g==="q"||g==="Q")&&n();},{isActive:o==="main-menu"});let H=useMemo(()=>nt(E,T),[E,T]),d=s.defaultScale,m=d?H.find(g=>g.id===d):void 0,v=[{label:"Display name",value:"display-name",detail:`[${s.displayName??"(none)"}]`},{label:"Relay URL",value:"relay-url",detail:`[${s.relayUrl??"(none)"}]`},{label:"Default scale",value:"default-scale",detail:`[${m?xe(m.id,T):"(none)"}]`},{label:"Default session name",value:"default-session-name",detail:`[${s.defaultSessionName??"(none)"}]`},{label:"Default password",value:"default-password",detail:`[${s.defaultPassword?"on":"off"}]`}];function w(g){g==="display-name"?(l(s.displayName??""),r("editing-display-name")):g==="relay-url"?(i(s.relayUrl??""),r("editing-relay-url")):g==="default-scale"?r("picking-scale"):g==="default-session-name"?(h(s.defaultSessionName??""),r("editing-session-name")):g==="default-password"&&r("picking-default-password");}function M(g){let C=g.trim();if(C.length>0){let A={...s,displayName:C};p(A),Y({displayName:C});}r("main-menu");}function f(g){let C=g.trim();if(C.length>0){let A={...s,relayUrl:C};p(A),Y({relayUrl:C});}r("main-menu");}function k(g){let C=g.trim(),A=C.length>0?{...s,defaultSessionName:C}:{...s};C.length===0?(delete A.defaultSessionName,p(A),Me({defaultSessionName:""})):(p(A),Y({defaultSessionName:C})),r("main-menu");}function b(g){let C=g.length>0?{...s,defaultScale:g}:{...s};g.length===0?(delete C.defaultScale,p(C),Me({defaultScale:""})):(p(C),Y({defaultScale:g})),r("main-menu");}function L(g,C){let A={...E};C.length===0?delete A[g]:A[g]=C,I(A),Y({presetOverrides:Object.keys(A).length>0?A:void 0});}function oe(g){let C=g==="on",A={...s,defaultPassword:C};p(A),Y({defaultPassword:C}),r("main-menu");}function Z(g,C){let A=`custom-${randomUUID()}`,B=[...T,{id:A,label:g,cards:C}];P(B),p({...s,customScales:B}),Y({customScales:B});}function ee(g,C,A){let B=T.map(O=>O.id===g?{id:g,label:C,cards:A}:O);P(B),p({...s,customScales:B}),Y({customScales:B});}function re(g){let C=T.filter(B=>B.id!==g);P(C);let A={...s,customScales:C};s.defaultScale===g?(A.defaultScale="fibonacci",Y({customScales:C,defaultScale:"fibonacci"})):Y({customScales:C}),p(A);}if(o==="main-menu")return jsx(ce,{title:"\u2500\u2500 Config Settings \u2500\u2500",items:v,onSelect:w,onClose:()=>n()});if(o==="editing-display-name")return jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:"\u2500\u2500 Config Settings \u2500\u2500"}),jsx(Text,{children:"Display name: "}),jsx(W,{value:a,onChange:l,onSubmit:M,onCancel:()=>r("main-menu"),placeholder:"(required)",isActive:true}),jsx(Text,{dimColor:true,children:"\u23CE confirm esc back"})]});if(o==="editing-relay-url")return jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:"\u2500\u2500 Config Settings \u2500\u2500"}),jsx(Text,{children:"Relay URL: "}),jsx(W,{value:u,onChange:i,onSubmit:f,onCancel:()=>r("main-menu"),placeholder:"(required)",isActive:true}),jsx(Text,{dimColor:true,children:"\u23CE confirm esc back"})]});if(o==="editing-session-name")return jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:"\u2500\u2500 Config Settings \u2500\u2500"}),jsx(Text,{children:"Default session name: "}),jsx(W,{value:c,onChange:h,onSubmit:k,onCancel:()=>r("main-menu"),placeholder:"(leave empty to clear)",isActive:true}),jsx(Text,{dimColor:true,children:"\u23CE confirm esc back"})]});if(o==="picking-scale"){let g=[{id:"",cards:[]},...H];return jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:"\u2500\u2500 Config Settings \u2500\u2500"}),jsx(pt,{scales:g,selectedScaleId:d??"",presetOverrides:E,customScales:T,onSelect:b,onOverrideChange:L,onCustomScaleCreate:Z,onCustomScaleEdit:ee,onCustomScaleDelete:re,onClose:()=>r("main-menu"),showNoneOption:true})]})}if(o==="picking-default-password"){let g=[{label:"On",value:"on",detail:s.defaultPassword?"[current]":void 0},{label:"Off",value:"off",detail:s.defaultPassword?void 0:"[current]"}];return jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:"\u2500\u2500 Config Settings \u2500\u2500"}),jsx(ce,{title:"Default password:",items:g,onSelect:oe,onClose:()=>r("main-menu")})]})}return jsx(Box,{})}async function zt(){return new Promise(e=>{let t=createInterface({input:process.stdin,output:process.stderr});process.stderr.write("Password: "),t.question("",n=>{t.close(),e(n);});})}async function Gr(e){let t=await De(),n=e.scale??t.defaultScale??"fibonacci",o=e.name??t.defaultSessionName,r=e.password??t.defaultPassword??false,s;r&&(s=await zt());let p=tt(n,t.presetOverrides,t.customScales);if(!p){let c=Object.keys(te).join(", ");console.error(`Unknown or fully-disabled scale: "${n}". Builtin IDs: ${c}`),process.exit(1);}let a=await En({participantId:t.participantId,displayName:t.displayName,relayUrl:t.relayUrl,sessionName:o,password:s,defaultScale:p}),l;s&&(l=await Se(s,a.roomUuid)),rt({roomCode:a.roomCode,roomUuid:a.roomUuid,passwordHash:l,lastSeenAt:Date.now()}),console.error(`Room created: ${a.roomCode}`),console.error(`Share: hnch join ${a.roomCode}`),console.error("");let{waitUntilExit:u,unmount:i}=render(St.createElement(vt,{session:a,participantId:t.participantId,displayName:t.displayName,accessMode:s?"password":"open",presetOverrides:t.presetOverrides,customScales:t.customScales}));it({doc:a.doc,localParticipantId:t.participantId,onEnded:async c=>{console.error(c.isLocal?"Session ended.":`Session ended by ${c.hostName}.`),i();try{await F();}catch(h){console.error(`Failed to clear cached session: ${h instanceof Error?h.message:String(h)}`);}a.disconnect(),process.exit(0);}}),await u(),a.disconnect(),F(),process.exit(0);}async function zr(e){let t=await De(),n=Nn(e.code);n===null&&(console.error("invalid room code"),process.exit(1));let o;e.password&&(o=await zt());let r=await Be(t.relayUrl,n),s;r.accessMode==="password"&&!e.password?s=await zt():e.password&&(s=o);let p,a=false,l=await An({participantId:t.participantId,displayName:t.displayName,relayUrl:t.relayUrl,roomCode:n,password:s,onRejection(c){if(c==="unknown-room"){st({clearLastSession:F,logger:console,exit:process.exit});return}c==="room-full"&&(a=true,p?.());}}),u;s&&(u=await Se(s,l.roomUuid)),rt({roomCode:l.roomCode,roomUuid:l.roomUuid,passwordHash:u,lastSeenAt:Date.now()}),console.error(`Joined room: ${l.roomCode}`),console.error("");let i=render(St.createElement(vt,{session:l,participantId:t.participantId,displayName:t.displayName,accessMode:r.accessMode,presetOverrides:t.presetOverrides,customScales:t.customScales}));p=i.unmount,it({doc:l.doc,localParticipantId:t.participantId,onEnded:async c=>{console.error(c.isLocal?"Session ended.":`Session ended by ${c.hostName}.`),p?.();try{await F();}catch(h){console.error(`Failed to clear cached session: ${h instanceof Error?h.message:String(h)}`);}l.disconnect(),process.exit(0);}}),await i.waitUntilExit(),a&&(console.error("This room is full (max 20 participants)."),l.disconnect(),F(),process.exit(1)),l.disconnect(),F(),process.exit(0);}async function Qr(){let e=await De(),t=On();t||(console.error("No recent session to resume."),process.exit(0));let n;try{n=await Be(e.relayUrl,t.roomCode);}catch(i){if((i instanceof Error?i.message:String(i)).includes("Room not found"))return await st({clearLastSession:F,logger:console,exit:process.exit});console.error("Could not reach relay \u2014 try again later."),process.exit(0);}n.accessMode==="password"&&!t.passwordHash&&(console.error(`Password changed \u2014 rejoin with: hnch join ${t.roomCode} --password`),F(),process.exit(0)),console.error(`Rejoining room ${t.roomCode}\u2026`),console.error("");let o=new Ie.Doc,r=new Awareness(o);et(o,{participantId:e.participantId,displayName:e.displayName}),r.setLocalStateField("participantId",e.participantId);let s=false,p=false,a,l=ot({doc:o,awareness:r,roomCode:t.roomCode,roomUuid:t.roomUuid,participantId:e.participantId,relayUrl:e.relayUrl,passwordHash:t.passwordHash,onRejection(i){if(i==="unknown-room"){st({clearLastSession:F,logger:console,exit:process.exit});return}i==="wrong-password"&&(s=true,a?.()),i==="room-full"&&(p=true,a?.());}});rt({roomCode:l.roomCode,roomUuid:l.roomUuid,passwordHash:t.passwordHash,lastSeenAt:Date.now()});let u=render(St.createElement(vt,{session:l,participantId:e.participantId,displayName:e.displayName,accessMode:n.accessMode,presetOverrides:e.presetOverrides,customScales:e.customScales}));a=u.unmount,it({doc:l.doc,localParticipantId:e.participantId,onEnded:async i=>{console.error(i.isLocal?"Session ended.":`Session ended by ${i.hostName}.`),a?.();try{await F();}catch(c){console.error(`Failed to clear cached session: ${c instanceof Error?c.message:String(c)}`);}l.disconnect(),process.exit(0);}}),await u.waitUntilExit(),p&&(console.error("This room is full (max 20 participants)."),F(),process.exit(1)),s&&(console.error(`Password changed \u2014 rejoin with: hnch join ${t.roomCode} --password`),F(),process.exit(0)),l.disconnect(),F(),process.exit(0);}async function Kr(e){if(!(e.name!==void 0||e.relayUrl!==void 0||e.scale!==void 0||e.sessionName!==void 0||e.password!==void 0)){let o=await De(),{waitUntilExit:r}=render(St.createElement(Zn,{presetOverrides:o.presetOverrides,customScales:o.customScales}));await r(),process.exit(0);}let n={};if(e.name!==void 0&&(n.displayName=e.name),e.relayUrl!==void 0&&(n.relayUrl=e.relayUrl),e.scale!==void 0){if(e.scale!==""){let o=ge();if(!tt(e.scale,o.presetOverrides,o.customScales)){console.error(`Unknown scale: "${e.scale}". Builtin IDs: ${Object.keys(te).join(", ")}`);return}}n.defaultScale=e.scale;}e.sessionName!==void 0&&(n.defaultSessionName=e.sessionName),e.password!==void 0&&(n.defaultPassword=e.password),Me(n),console.log("Config updated.");}var no=Jr(hideBin(process.argv)).scriptName("hnch").usage("$0 <command> \u2014 terminal pointing poker").command("create","Create a new room",e=>e.option("name",{type:"string",describe:"Session name"}).option("password",{type:"boolean",describe:"Protect room with a password"}).option("scale",{type:"string",describe:"Estimation scale ID (fibonacci, t-shirt, powers-of-2, linear, risk, or custom-<id>)"}),e=>{Gr(e).catch(t=>{console.error(t instanceof Error?t.message:t),process.exit(1);});}).command("join <code>","Join an existing room",e=>e.positional("code",{type:"string",demandOption:true,describe:"6-character room code"}).option("password",{type:"boolean",describe:"Room requires a password",default:false}),e=>{zr(e).catch(t=>{console.error(t instanceof Error?t.message:t),process.exit(1);});}).command("resume","Rejoin your last session",e=>e,()=>{Qr().catch(e=>{console.error(e instanceof Error?e.message:e),process.exit(1);});}).command("config","View or update CLI config",e=>e.option("name",{type:"string",describe:"Set display name"}).option("relay-url",{type:"string",describe:"Set relay URL"}).option("scale",{type:"string",describe:"Set default estimation scale ID (empty string clears)"}).option("session-name",{type:"string",describe:"Set default session name (empty string clears)"}).option("password",{type:"boolean",describe:"Set default password prompt on (--password) or off (--no-password)"}),e=>{Kr(e).catch(t=>{console.error(t instanceof Error?t.message:t),process.exit(1);});}).demandCommand(1,"Specify a command: create, join, resume, or config").strict().help(),Gt=hideBin(process.argv);(Gt.length===0||Gt.length===1&&Gt[0]==="--")&&(no.showHelp(),process.exit(0));no.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xauyxau/hnch",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.1",
|
|
4
4
|
"description": "Pointing poker CLI for agile teams",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pointing-poker",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"typecheck": "tsc --noEmit",
|
|
44
44
|
"dev": "tsx src/index.ts",
|
|
45
45
|
"test": "vitest run",
|
|
46
|
+
"test:coverage": "vitest run --coverage",
|
|
46
47
|
"format:check": "biome format .",
|
|
47
48
|
"lint": "oxlint ."
|
|
48
49
|
},
|