@xauyxau/hnch 1.9.0 → 1.10.0
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 +10 -10
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
3
|
-
`,"utf-8");}async function
|
|
4
|
-
`,"utf-8");}var
|
|
5
|
-
`)}function
|
|
2
|
+
import Is from'yargs';import {hideBin}from'yargs/helpers';import Et,{useState,useEffect,useCallback,useMemo,useRef}from'react';import {render,useApp,useInput,Box,Text}from'ink';import*as Me 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 Pr from'ws';import Ro from'clipboardy';import {jsxs,jsx,Fragment}from'react/jsx-runtime';var sr="https://hnch.dev";function _e(){let t=process.env.XDG_CONFIG_HOME||join(homedir(),".config");return join(t,"hnch")}function Ht(){return join(_e(),"config.json")}function gn(){let e=_e();existsSync(e)||mkdirSync(e,{recursive:true});}function Re(){let e=Ht();if(!existsSync(e))return {};try{return JSON.parse(readFileSync(e,"utf-8"))}catch{return {}}}function J(e){gn();let o={...Re(),...e};writeFileSync(Ht(),JSON.stringify(o,null,2)+`
|
|
3
|
+
`,"utf-8");}async function ir(e){let t=createInterface({input:process.stdin,output:process.stderr});return new Promise(o=>{t.question(e,n=>{t.close(),o(n.trim());});})}function ar(e){return e.startsWith("wss://")?"https://"+e.slice(6):e.startsWith("ws://")?"http://"+e.slice(5):e}async function Ve(){let e=Re(),t=false;if(e.participantId||(e.participantId=randomUUID(),t=true),!e.displayName){let n=await ir("Enter your display name: ");e.displayName=n||`user-${e.participantId.slice(0,6)}`,t=true;}e.relayUrl||(e.relayUrl=sr,t=true),t&&J(e);let o=process.env.HNCH_RELAY_URL||e.relayUrl;return o=ar(o),{participantId:e.participantId,displayName:e.displayName,relayUrl:o,defaultScale:e.defaultScale,defaultSessionName:e.defaultSessionName,defaultPassword:e.defaultPassword,presetOverrides:e.presetOverrides,customScales:e.customScales}}function Fe(e){gn();let o={...Re()};for(let[n,r]of Object.entries(e))r===""?delete o[n]:r!==void 0&&(o[n]=r);writeFileSync(Ht(),JSON.stringify(o,null,2)+`
|
|
4
|
+
`,"utf-8");}var rt="hnch_abstain";function pe(e){return e===rt}var cr={"unknown-room":4001,"wrong-password":4002,"invalid-invite":4003,"expired-invite":4004,"invite-already-redeemed":4005,"room-full":4006,"malformed-credentials":4010},hn=new Map(Object.entries(cr).map(([e,t])=>[t,e]));function Ie(e){return e.role??"voter"}function W(e){let{revealedAt:t,skippedAt:o}=e;return t===void 0&&o===void 0?null:t===void 0?"skipped":o===void 0||t<=o?"revealed":"skipped"}var st={id:"fibonacci",cards:["0","1","2","3","5","8","13","21"]},Sn={id:"t-shirt",cards:["XS","S","M","L","XL"]},xn={id:"powers-of-2",cards:["1","2","4","8","16","32","64"]},wn={id:"linear",cards:["1","2","3","4","5","6","7","8","9","10"]},yn={id:"risk",cards:["Low","Medium","High","Critical"]},se={[st.id]:st,[Sn.id]:Sn,[xn.id]:xn,[wn.id]:wn,[yn.id]:yn},vn={fibonacci:"Fibonacci","t-shirt":"T-shirt","powers-of-2":"Powers of 2",linear:"Linear (1\u201310)",risk:"Risk"};function Cn(e){return e in vn?vn[e]:e.length===0?e:(e.startsWith("custom-")?e.slice(7):e).split("-").map(o=>o.length===0?o:o.charAt(0).toUpperCase()+o.slice(1)).join(" ")}var dr="meta",Ye="settings",lr="participants",ur="rounds",pr=["roomUuid","createdAt","createdBy","sessionEndedAt"],fr=["name","defaultScale","hostParticipantId"],mr=["displayName","joinedAt","role","leftAt","removedBy"],gr=["id","title","description","ticketId","parentRoundId","scale","timerStartedAt","revealedAt","skippedAt","createdBy","createdAt"];function it(e){return e.getMap(dr)}function at(e){return e.getMap(Ye)}function ct(e){return e.getMap(lr)}function Ut(e){return e.getArray(ur)}function dt(e,t){let o={};for(let n of t){let r=e.get(n);r!==void 0&&(o[n]=r);}return o}function hr(e){return dt(it(e),pr)}function Sr(e){return dt(at(e),fr)}function xr(e){let t=ct(e),o=[];return t.forEach((n,r)=>{let s=dt(n,mr);o.push({id:r,...s});}),o.sort((n,r)=>n.joinedAt-r.joinedAt||n.id.localeCompare(r.id)),o}function wr(e){let t={};return e.forEach((o,n)=>{t[n]=o;}),t}function yr(e){let t=dt(e,gr),o=e.get("votes"),n=o?wr(o):{};return {...t,votes:n}}function vr(e){let t=Ut(e),o=[];return t.forEach(n=>{o.push(yr(n));}),o}function Q(e){return {meta:hr(e),settings:Sr(e),participants:xr(e),rounds:vr(e)}}function bn(e,t){if(it(e).has("roomUuid"))throw new Error("initDoc: doc already seeded");e.transact(()=>{let o=it(e);o.set("roomUuid",t.roomUuid),o.set("createdAt",t.createdAt),o.set("createdBy",t.participantId);let n=at(e);t.sessionName!==void 0&&n.set("name",t.sessionName),n.set("defaultScale",t.defaultScale),n.set("hostParticipantId",t.participantId);let r=new Me.Map;r.set("displayName",t.displayName),r.set("joinedAt",t.createdAt),ct(e).set(t.participantId,r);});}function lt(e,t){e.transact(()=>{let o=ct(e),n=o.get(t.participantId),r=n??new Me.Map;r.set("displayName",t.displayName),n===void 0&&r.set("joinedAt",Date.now()),r.delete("leftAt"),r.delete("removedBy"),n===void 0&&o.set(t.participantId,r);});}function $e(e,t){e.transact(()=>{let o=at(e);for(let[n,r]of Object.entries(t))r===void 0?o.delete(n):o.set(n,r);});}function Lt(e,t,o){e.transact(()=>{let n=ct(e),r=n.get(t),s=r??new Me.Map;s.set("displayName",o.displayName),s.set("joinedAt",o.joinedAt),o.role!==void 0&&s.set("role",o.role),r===void 0&&n.set(t,s);});}function jt(e,t){let o=t.scale??at(e).get("defaultScale");if(!o)throw new Error("appendRound: no scale (settings.defaultScale missing)");return e.transact(()=>{let n=new Me.Map;n.set("id",t.id),n.set("createdAt",t.createdAt),n.set("createdBy",t.createdBy),n.set("scale",o),t.title!==void 0&&n.set("title",t.title),t.description!==void 0&&n.set("description",t.description),t.ticketId!==void 0&&n.set("ticketId",t.ticketId),t.parentRoundId!==void 0&&n.set("parentRoundId",t.parentRoundId),n.set("timerStartedAt",t.createdAt),n.set("votes",new Me.Map),Ut(e).push([n]);}),t.id}function Rn(e,t,o){let n=Je(e,t);if(!n)throw new Error(`patchRoundDescriptor: round ${t} not found`);e.transact(()=>{o.title!==void 0&&n.set("title",o.title),o.description!==void 0&&n.set("description",o.description);});}function Je(e,t){let o=Ut(e);for(let n=0;n<o.length;n+=1){let r=o.get(n);if(r.get("id")===t)return r}}function In(e,t,o,n){let r=Je(e,t);if(!r)throw new Error(`castVote: round ${t} not found`);r.get("revealedAt")===void 0&&e.transact(()=>{r.get("votes").set(o,n);});}function Tn(e,t,o){let n=Je(e,t);if(!n)throw new Error(`clearVote: round ${t} not found`);n.get("revealedAt")===void 0&&e.transact(()=>{n.get("votes").delete(o);});}function _t(e,t,o){let n=Je(e,t);if(!n)throw new Error(`revealRound: round ${t} not found`);if(n.get("skippedAt")!==void 0)throw new Error(`revealRound: round ${t} already skipped`);e.transact(()=>{n.set("revealedAt",o);});}function En(e,t,o){let n=Je(e,t);if(!n)throw new Error(`skipRound: round ${t} not found`);if(n.get("revealedAt")!==void 0)throw new Error(`skipRound: round ${t} already revealed`);if(n.get("skippedAt")!==void 0)throw new Error(`skipRound: round ${t} already skipped`);e.transact(()=>{n.set("skippedAt",o);});}function An(e){e.transact(()=>{it(e).set("sessionEndedAt",Date.now());});}function ee(e){let{rounds:t}=e;if(t.length===0)return null;for(let o=t.length-1;o>=0;o-=1){let n=t[o];if(n.revealedAt===void 0)return n}return t.at(-1)}function Nn(e){let t=Object.values(e.votes);return t.length<2?false:t.every(o=>o===t[0])}function Pn(e){let t=Math.max(0,Math.floor(e/1e3)),o=Math.floor(t/60),n=t%60;return `${o}:${n.toString().padStart(2,"0")}`}function Dn(e){try{let t=new URL(e);return t.protocol==="http:"||t.protocol==="https:"}catch{return false}}function kn(e,t){let o=new Map;for(let i of t)o.set(i.id,i);let n=o.get(e);if(n===void 0||n.parentRoundId===void 0)return null;let r=new Set([n.id]),s=n,c=1;for(;s.parentRoundId!==void 0;){let i=o.get(s.parentRoundId);if(i===void 0||r.has(i.id))return null;r.add(i.id),s=i,c+=1;}let a=s,u=new Map;for(let i of t){if(i.parentRoundId===void 0)continue;let p=u.get(i.parentRoundId);p===void 0?u.set(i.parentRoundId,[i]):p.push(i);}let l=0,d=[a];for(;d.length>0;){let i=d.shift();l+=1;let p=u.get(i.id);p!==void 0&&d.push(...p);}return {chainRoot:a,depth:c,total:l}}var On="PBKDF2",Cr="SHA-256",Mn=new TextEncoder;function br(e){return Array.from(new Uint8Array(e)).map(t=>t.toString(16).padStart(2,"0")).join("")}async function Te(e,t){let o=await crypto.subtle.importKey("raw",Mn.encode(e),On,false,["deriveBits"]),n=await crypto.subtle.deriveBits({name:On,salt:Mn.encode(t),iterations:1e5,hash:Cr},o,256);return br(n)}var $n="ABCDEFGHJKMNPQRSTUVWXYZ23456789";if($n.length!==31)throw new Error(`Room code alphabet must be 31 characters, got ${$n.length}`);function Vt(e){return e.trim().replaceAll(/-/g,"").toUpperCase()}function Bn(e,t=true){let o=Vt(e);return !t||o.length!==6?o:`${o.slice(0,3)}-${o.slice(3)}`}function Rr(e,t){let o=new Map;t.forEach((s,c)=>o.set(s,c));let n=[...new Set(e.filter(s=>!o.has(s)))].toSorted(),r=new Map;return n.forEach((s,c)=>r.set(s,t.length+c)),s=>o.get(s)??r.get(s)??Number.MAX_SAFE_INTEGER}function Ft(e,t){let o=new Map;for(let c of e)o.set(c,(o.get(c)??0)+1);let n=null,r=0,s=[...o.keys()].toSorted((c,a)=>t(c)-t(a));for(let c of s){let a=o.get(c);a>r&&(r=a,n=c);}return n}function Hn(e,t){let o=Object.values(e);if(o.length===0)return null;let n=Rr(o,t.cards);if(t.cards.every(c=>Number.isFinite(parseFloat(c)))){let c=o.map(parseFloat).filter(Number.isFinite);if(c.length===0)return {kind:"categorical",mode:Ft(o,n)};let a=c.reduce((b,A)=>b+A,0)/c.length,u=[...c].toSorted((b,A)=>b-A),l=Math.floor(u.length/2),d=u.length%2===1?u[l]:(u[l-1]+u[l])/2,i=Ft(o,n),p;if(new Set(o).size===1)p="unanimous";else {let b=o.map(R=>n(R)),A=Math.min(...b);p=Math.max(...b)-A<=1?"close":"spread";}return {kind:"numeric",mean:a,median:d,mode:i,spread:p}}return {kind:"categorical",mode:Ft(o,n)}}function Un(e){return e.replaceAll(/\|/g,"\\|")}function Ir(e){return e.replaceAll(/\\/g,"\\\\").replaceAll(/]/g,"\\]")}function Tr(e){let o=(e.match(/`+/g)??[]).reduce((n,r)=>Math.max(n,r.length),0);return "`".repeat(o+1)}function Yt(e){if(!e)return "";if(Dn(e))return ` ([${Ir(e)}](<${e}>))`;let t=Tr(e);return ` (${t}${e}${t})`}function Er(e){return e instanceof Map?e:new Map(Object.entries(e))}function Ar(e){let t=new Map;for(let r of e)t.set(r.id,r);function o(r){if(!r.parentRoundId)return r;let s=t.get(r.parentRoundId);return s?o(s):r}let n=new Map;for(let r of e){let s=o(r),c=n.get(s.id);c?c.push(r):n.set(s.id,[r]);}return n}function Ln(e,t){let o=new Map;e.scale.cards.forEach((c,a)=>o.set(c,a));let n=new Map;for(let[c,a]of Object.entries(e.votes)){let u=t.get(c)??c,l=n.get(a);l?l.push(u):n.set(a,[u]);}let r=[...n.entries()].map(([c,a])=>({card:c,scaleIndex:o.get(c)??Number.MAX_SAFE_INTEGER,voters:[...a].toSorted().map(Un)}));return r.sort((c,a)=>{let u=a.voters.length-c.voters.length;return u!==0?u:c.scaleIndex-a.scaleIndex}),["| Vote | Voters |","|------|--------|",...r.map(c=>`| ${Un(c.card)} | ${c.voters.join(", ")} |`)].join(`
|
|
5
|
+
`)}function jn(e){let t=Hn(e.votes,e.scale),o=e.timerStartedAt!=null&&e.revealedAt!=null?Pn(e.revealedAt-e.timerStartedAt):null,n=[];return t?.kind==="numeric"?n.push(`Avg ${t.mean.toFixed(1)} \xB7 Med ${t.median}`):t?.kind==="categorical"&&n.push(`Most common: ${t.mode}`),o!=null&&n.push(`Elapsed ${o}`),n.join(" \xB7 ")}function _n(e){let{rounds:t}=e,o=Er(e.participants),n=new Map;for(let[d,i]of o)n.set(d,i.displayName);let r=t.filter(d=>d.revealedAt!=null);if(r.length===0)return "";let s=new Map;t.forEach((d,i)=>s.set(d.id,i+1));let c=Ar(r),a=new Set,u=[];for(let d of r)for(let[i,p]of c)if(p.includes(d)&&!a.has(i)){a.add(i),u.push(i);break}let l=[];for(let d of u){let i=c.get(d),p=i.find(m=>m.id===d),b=i.filter(m=>m.id!==d),A=s.get(p.id)??0,E=p.title?`## Round ${A} \u2014 ${p.title}${Yt(p.ticketId)}`:`## Round ${A}${Yt(p.ticketId)}`,R=Ln(p,n),$=jn(p),f=$?`${E}
|
|
6
6
|
|
|
7
7
|
${R}
|
|
8
8
|
|
|
9
9
|
${$}`:`${E}
|
|
10
10
|
|
|
11
|
-
${R}`;l.push(
|
|
11
|
+
${R}`;l.push(f);for(let m of b){let v=`### Re-vote ${s.get(m.id)??0}${Yt(m.ticketId)}`,k=Ln(m,n),h=jn(m),P=h?`${v}
|
|
12
12
|
|
|
13
|
-
${
|
|
13
|
+
${k}
|
|
14
14
|
|
|
15
|
-
${
|
|
15
|
+
${h}`:`${v}
|
|
16
16
|
|
|
17
|
-
${
|
|
17
|
+
${k}`;l.push(P);}}return l.join(`
|
|
18
18
|
|
|
19
19
|
`)+`
|
|
20
|
-
`}function
|
|
21
|
-
`,{encoding:"utf-8",mode:384}),chmodSync(o,384);}function F(){let e=Jt();try{unlinkSync(e);}catch(t){if(t.code!=="ENOENT")throw t}}async function ft(e){let t=true;try{await e.clearLastSession();}catch(o){t=false,e.logger.error(`Failed to clear cached session: ${o instanceof Error?o.message:String(o)}`);}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 mt(e){let t=e.doc.getMap("meta"),o=null,n=false;function r(){if(n)return;let s=t.get("sessionEndedAt");if(s==null||s===o)return;o=s;let c=t.get("createdBy"),i=c===e.localParticipantId,u=e.doc.getMap("participants"),l=e.doc.getMap("settings").get("hostParticipantId")??c,a="host";if(l){let f=u.get(l)?.get("displayName");typeof f=="string"&&f.length>0&&(a=f);}setImmediate(()=>{n||e.onEnded({isLocal:i,hostName:a,timestamp:s});});}return t.observe(r),r(),()=>{n=true,t.unobserve(r);}}var Ur="@xauyxau/hnch";function gt(e){return Mn(e)}function ht(e){return `${process.env.HNCH_SHARE_ORIGIN??"https://hnch.dev"}/join?code=${e}`}function St(e){return `npx ${Ur} join ${e}`}function Wt(e){let t=e.trim();return t===""?void 0:t}function Xn(e,t){return [e,Wt(t)]}var jr={connecting:"yellow",connected:"green",disconnected:"red"};function Zn({roomCode:e,sessionName:t,status:o,isHost:n}){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:[n&&jsx(Text,{color:"yellow",children:"[host]"}),jsx(Text,{color:jr[o],children:o})]})]})}function pe({items:e,onSelect:t,onClose:o,title:n}){if(e.length===0)return jsxs(Box,{flexDirection:"column",gap:1,children:[n&&jsx(Text,{bold:true,children:n}),jsx(Text,{dimColor:true,children:"(no items)"})]});let r=e.findIndex(i=>!i.disabled),[s,c]=useState(Math.max(r,0));return useInput((i,u)=>{if(u.upArrow)c(l=>{let a=(l-1+e.length)%e.length,d=0;for(;e[a]?.disabled&&d<e.length;)a=(a-1+e.length)%e.length,d++;return a});else if(u.downArrow)c(l=>{let a=(l+1)%e.length,d=0;for(;e[a]?.disabled&&d<e.length;)a=(a+1)%e.length,d++;return a});else if(u.return){let l=e[s];l&&!l.disabled&&t(l.value);}else u.escape&&o?.();}),jsxs(Box,{flexDirection:"column",gap:1,children:[n&&jsx(Text,{bold:true,children:n}),jsx(Box,{flexDirection:"column",children:e.map((i,u)=>{let l=u===s,a=i.disabled??false;return jsxs(Box,{gap:1,children:[jsx(Text,{...l&&{color:"cyan"},dimColor:a,children:l?">":" "}),jsx(Text,{bold:l,dimColor:a,children:i.label}),i.detail&&jsx(Text,{dimColor:true,children:i.detail})]},i.value)})}),jsx(Text,{dimColor:true,children:"\u2191\u2193 select \u23CE confirm esc close"})]})}function q({value:e,onChange:t,onSubmit:o,onCancel:n,placeholder:r,isActive:s=true,mask:c=false}){useInput((a,d)=>{d.escape?n?.():d.return?o(e):d.backspace||d.delete?t(e.slice(0,-1)):a&&!d.ctrl&&!d.meta&&t(e+a);},{isActive:s});let i=c&&e.length>0?"*".repeat(e.length):e,u=i.length>0?i:r??"",l=e.length===0&&r!==void 0;return jsxs(Text,{children:[jsx(Text,{dimColor:l,children:u}),jsx(Text,{inverse:true,children:" "})]})}function oo({mode:e,password:t,onModeChange:o,onPasswordChange:n,onClose:r}){let[s,c]=useState(e==="open"?0:1),[i,u]=useState(false);return useEffect(()=>{c(e==="open"?0:1);},[e]),useInput((l,a)=>{if(a.leftArrow)c(d=>Math.max(0,d-1));else if(a.rightArrow)c(d=>Math.min(1,d+1));else if(a.return){let d=s===0?"open":"password";d!==e&&o(d),d==="password"&&u(true);}else a.escape&&r?.();},{isActive:!i}),jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Box,{gap:1,children:["open","password"].map((l,a)=>{let d=a===s,f=l===e,C=l==="open"?"Open":"Password";return jsx(Text,{inverse:d,bold:f,...f&&{color:"cyan"},children:f?`[${C}]`:` ${C} `},l)})}),e==="password"&&jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{children:"Password: "}),jsx(q,{value:t,onChange:n,onSubmit:()=>u(false),onCancel:()=>u(false),placeholder:"(required to join)",isActive:i,mask:true}),!i&&jsx(Text,{dimColor:true,children:"\u23CE edit \u2190 \u2192 switch mode esc close"}),i&&jsx(Text,{dimColor:true,children:"\u23CE done esc cancel"})]}),e==="open"&&jsx(Text,{dimColor:true,children:"\u2190\u2192 toggle \u23CE confirm esc close"})]})}function io({scaleId:e,cards:t,disabledIndices:o,onOverrideChange:n,onClose:r}){let s=useMemo(()=>new Set(o),[o]),[c,i]=useState(0),u=l=>{let a=new Set(s);if(a.has(l))a.delete(l);else {if(a.size>=t.length-1)return;a.add(l);}let d=Array.from(a).toSorted((f,C)=>f-C);n(e,d);};return useInput((l,a)=>{a.leftArrow?i(d=>Math.max(0,d-1)):a.rightArrow?i(d=>Math.min(t.length-1,d+1)):l===" "?u(c):(a.return||a.escape)&&r();}),jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Box,{gap:1,children:t.map((l,a)=>{let d=a===c,f=s.has(a);return jsx(Text,{inverse:d,bold:!f,...!f&&{color:"cyan"},strikethrough:f,dimColor:f,children:l},l)})}),jsx(Text,{dimColor:true,children:" \u2190\u2192 navigate space toggle \u23CE/esc done"})]})}function en({initialName:e="",initialCards:t=[],onSave:o,onCancel:n}){let r=e.length>0,[s,c]=useState(e),[i,u]=useState(t.length>0?t:[""]),[l,a]=useState(0),[d,f]=useState("editing-name"),[C,A]=useState(""),E=()=>{let p=s.trim();if(p.length===0||p.length>40){A("Name must be 1-40 characters");return}c(p),A(""),f("editing-cards");},R=()=>{let p=s.trim();if(p.length===0||p.length>40){A("Name must be 1-40 characters");return}let m=i.map(w=>w.trim()).filter(w=>w.length>0),y=Array.from(new Set(m));if(y.length<2){A("Need at least 2 unique non-empty cards");return}A(""),o(p,y);};return useInput((p,m)=>{if(d==="editing-cards"){if(m.leftArrow)a(y=>Math.max(0,y-1));else if(m.rightArrow)a(y=>{let w=y+1;return w>=i.length&&i.length<25?(u(D=>[...D,""]),w):Math.min(w,i.length-1)});else if(m.return)R();else if(m.escape)n();else if(m.tab)f("editing-name");else if(m.backspace){let y=i[l]||"";if(y.length===0){if(i.length>2){let w=i.filter((D,g)=>g!==l);u(w),a(Math.max(0,l-1));}}else {let w=[...i];w[l]=y.slice(0,-1),u(w);}}else if(p&&!m.ctrl&&!m.meta){let y=i[l]||"";if(y.length<10){let w=[...i];w[l]=y+p,u(w);}}}},{isActive:d==="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(q,{value:s,onChange:c,onSubmit:E,onCancel:n,placeholder:"Scale name",isActive:d==="editing-name"})]}),d==="editing-cards"&&jsxs(Box,{flexDirection:"column",gap:0,children:[jsx(Text,{children:"Cards:"}),jsx(Box,{gap:1,flexWrap:"wrap",children:i.map((p,m)=>{let y=m===l;return jsx(Text,{inverse:y,...!y&&{color:"cyan"},children:p.length>0?p:"\xB7"},m)})})]}),C&&jsx(Text,{color:"red",children:C}),jsx(Text,{dimColor:true,children:d==="editing-name"?"\u23CE next esc cancel":"\u2190\u2192 navigate typing adds card bsp delete \u23CE save tab name esc cancel"})]})}function yt({scales:e,selectedScaleId:t,presetOverrides:o,customScales:n=[],onSelect:r,onOverrideChange:s,onCustomScaleCreate:c,onCustomScaleEdit:i,onCustomScaleDelete:u,onClose:l,showNoneOption:a=false}){let[d,f]=useState(0),[C,A]=useState(null),[E,R]=useState("browsing"),[$,p]=useState(null),[m,y]=useState(null),w=c?[...e,{id:"__create-custom__",cards:[]}]:e,D=w.length;if(useInput((g,O)=>{if(E==="browsing"){if(O.escape){l();return}if(D===0)return;if(O.upArrow)f(b=>(b-1+D)%D);else if(O.downArrow)f(b=>(b+1)%D);else if(O.return){let b=w[d];if(!b)return;if(b.id==="__create-custom__"){R("creating");return}b.id in se?A(C===b.id?null:b.id):r(b.id);}else if(g===" "){let b=w[d];b&&b.id!=="__create-custom__"&&r(b.id);}else if(g==="e"||g==="E"){let b=w[d];i&&b?.id.startsWith("custom-")&&(p(b.id),R("editing"));}else if(g==="d"||g==="D"){let b=w[d];u&&b?.id.startsWith("custom-")&&(y(b.id),R("confirming-delete"));}}else E==="confirming-delete"&&(g==="y"||g==="Y"?(m&&u&&u(m),y(null),R("browsing")):(g==="n"||g==="N"||O.escape)&&(y(null),R("browsing")));},{isActive:E==="browsing"&&C===null||E==="confirming-delete"}),E==="creating")return jsx(en,{onSave:(g,O)=>{c&&c(g,O),R("browsing");},onCancel:()=>R("browsing")});if(E==="editing"&&$){let g=n?.find(b=>b.id===$);if(e.find(b=>b.id===$)&&g)return jsx(en,{initialName:g.label,initialCards:g.cards,onSave:(b,j)=>{i&&i($,b,j),p(null),R("browsing");},onCancel:()=>{p(null),R("browsing");}})}if(E==="confirming-delete"&&m){let g=w.find(b=>b.id===m),O=g?Ie(g.id,n):"Unknown";return jsxs(Box,{flexDirection:"column",gap:1,children:[jsxs(Text,{children:["Delete ",O,"? "]}),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((g,O)=>{let b=O===d,j=g.id===t,ae=C===g.id,ne=g.id in se,oe=g.id==="__create-custom__",Y=g.cards.join(", "),v=a&&g.id==="",T=v?"(none)":oe?"[+ Create custom scale]":Ie(g.id,n);return jsxs(Box,{flexDirection:"column",children:[jsxs(Box,{gap:1,children:[jsx(Text,{...b&&{color:"cyan"},children:b?">":" "}),jsx(Text,{bold:b,children:T}),!v&&!oe&&jsx(Text,{dimColor:true,children:Y}),j&&jsx(Text,{color:"cyan",children:"[current]"})]}),ae&&!v&&!oe&&jsx(Box,{paddingLeft:2,children:ne?jsx(io,{scaleId:g.id,cards:se[g.id]?.cards??[],disabledIndices:o?.[g.id]??[],onOverrideChange:s,onClose:()=>A(null)}):null})]},g.id||"none")})}),jsx(Text,{dimColor:true,children:(()=>{let g=d<w.length?w[d]:null;return g?.id.startsWith("custom-")?`\u2191\u2193 navigate space select${i?" [e] edit":""}${u?" [d] delete":""} esc back`:g&&g.id in se?"\u2191\u2193 navigate \u23CE customize space select esc back":"\u2191\u2193 navigate \u23CE/space select esc back"})()})]})}function Zr(e,t){return t?[...e]:e.filter(o=>o.value==="display-name")}function ao({sessionName:e,displayName:t,currentScale:o,accessMode:n,presetOverrides:r,customScales:s,isHost:c=true,onChangeName:i,onChangeScale:u,onChangeDisplayName:l,onChangeAccess:a,onClose:d}){let[f,C]=useState(r??{}),A=useMemo(()=>lt(f,s),[f,s]),[E,R]=useState("main-menu"),[$,p]=useState(e??""),[m,y]=useState(t),[w,D]=useState(n==="password"?"password":"open"),[g,O]=useState(""),[b,j]=useState(false),ae=[{label:"Room name",value:"room-name",detail:`[${e??"(none)"}]`},{label:"Scale",value:"scale",detail:`[${Ie(o.id,s)}]`},{label:"Access",value:"access",detail:`[${n==="open"?"Open":"Password"}]`},{label:"Display name",value:"display-name",detail:`[${t}]`}],ne=Zr(ae,c);function oe(k){!c&&k!=="display-name"||(k==="room-name"?(p(e??""),R("editing-room-name")):k==="scale"?R("picking-scale"):k==="access"?R("picking-access"):k==="display-name"&&(y(t),R("editing-display-name")));}function Y(k){let B=k.trim()===""?void 0:k.trim();i(B),R("main-menu");}function v(k){let B=k.trim();B.length>0&&(l(B),process.nextTick(()=>J({displayName:B}))),R("main-menu");}function T(k){let B=A.find(z=>z.id===k);B&&u(B),R("main-menu");}function P(k,B){let z={...f};B.length===0?delete z[k]:z[k]=B,C(z),J({presetOverrides:Object.keys(z).length>0?z:void 0});}async function L(k,B){j(true);try{await a(k,B),D(k),O(k==="password"?B??"":"");}catch(z){console.error(`Failed to update access: ${z}`);}finally{j(false),R("main-menu");}}return E==="main-menu"?jsx(pe,{title:"\u2500\u2500 Settings \u2500\u2500",items:ne,onSelect:oe,onClose:d}):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(q,{value:$,onChange:p,onSubmit:Y,onCancel:()=>R("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(q,{value:m,onChange:y,onSubmit:v,onCancel:()=>R("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(yt,{scales:A,selectedScaleId:o.id,presetOverrides:f,onSelect:T,onOverrideChange:P,onClose:()=>R("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(oo,{mode:w,password:g,onModeChange:k=>{k==="open"&&L("open");},onPasswordChange:O,onClose:()=>R("main-menu")})]}):jsx(Box,{})}function bt({participants:e,onlineIds:t,hostId:o,votes:n,revealed:r,participantId:s}){return jsxs(Box,{flexDirection:"column",children:[jsxs(Text,{bold:true,children:["Participants (",e.length,")"]}),e.map(c=>{let i=t.has(c.id),u=i?"\u25CF":"\u25CB",l=i?"green":"gray",a=c.id===o,d=be(c)==="viewer",f=c.id===s,C=c.id in n,A=d?" (viewer)":r?n[c.id]?` \u2192 ${n[c.id]}`:"":C?" \u2713":" \xB7",E=f&&!d&&!(r&&n[c.id]);return jsxs(Box,{gap:1,children:[jsx(Text,{color:l,children:u}),jsxs(Text,{children:[c.displayName,a?" (host)":""]}),E?jsx(Text,{color:"cyan",children:"(voter)"}):jsx(Text,{color:d?"gray":C?"green":"gray",children:A})]},c.id)})]})}var es=[{label:"End session for everyone",value:"end-session",detail:"Disconnects all participants. Cannot be undone."}];function lo({onPick:e,onClose:t}){return jsx(pe,{title:"Host Actions",items:es,onSelect:e,onClose:t})}function Rt({roomCode:e,onPick:t,onClose:o}){let n=[{label:"Copy code",value:"code",detail:gt(e)},{label:"Copy share link",value:"url",detail:ht(e)},{label:"Copy npx command",value:"npx",detail:St(e)}];return jsx(pe,{title:"Share",items:n,onSelect:r=>t(r),onClose:o})}function os(e){let t=e.rounds.filter(n=>W(n)!==null),o=Object.fromEntries(e.participants.map(n=>[n.id,n]));return jn({rounds:t,participants:o})}async function uo(e,t,o){let n=os(e);try{await t(n),o("Results copied to clipboard");}catch{o("Clipboard write failed");}}function po(e,t,o){let n=Pn(e.id,o);return n===null?`Round ${t}`:`Round ${n.depth}/${n.total} [re-vote]`}function fo(e,t,o){let n=po(e,t,o),r=e.title?` \u2014 ${e.title}`:"",s=e.ticketId?` [${e.ticketId}]`:"",c=W(e)==="skipped"?" \u2014 Skipped":" \u2014 Results";return `${n}${r}${s}${c}`}function mo(e){let t=Object.values(e).map(a=>parseFloat(a)).filter(a=>!isNaN(a)).toSorted((a,d)=>a-d);if(t.length===0)return {average:"-",median:"-",agreement:"-"};let n=t.reduce((a,d)=>a+d,0)/t.length,r=Math.floor(t.length/2),s=t.length%2===0?(t[r-1]+t[r])/2:t[r],c=Object.values(e),i=new Map;for(let a of c)i.set(a,(i.get(a)??0)+1);let u=Math.max(...i.values()),l=c.length>0?Math.round(u/c.length*100):0;return {average:n.toFixed(1),median:s.toFixed(1),agreement:`${l}%`}}function nn(e,t){let o=Math.floor((t-e)/1e3),n=Math.floor(o/60),r=o%60;return `${n}:${r.toString().padStart(2,"0")}`}function go(e,t,o=[]){let n=po(e,t,o);if(W(e)==="skipped"){let l=e.title?` ${e.title}`:"",a=e.ticketId?` [${e.ticketId}]`:"";return `${n}:${l}${a} \u2014 Skipped`}let r=new Map;for(let l of Object.values(e.votes))r.set(l,(r.get(l)??0)+1);let s=[...r.entries()].toSorted((l,a)=>a[1]-l[1]||l[0].localeCompare(a[0])).map(([l,a])=>`${l}\xD7${a}`).join(", "),c=e.title?` ${e.title}`:"",i=e.ticketId?` [${e.ticketId}]`:"",u=e.timerStartedAt!==void 0&&e.revealedAt!==void 0?` \u2014 ${nn(e.timerStartedAt,e.revealedAt)}`:"";return `${n}:${c}${i} \u2014 ${s||"(no votes)"}${u}`}function ho({projection:e,onlineIds:t,isHost:o,participantId:n,rounds:r,currentRound:s,settingsOpen:c,onStartRound:i,onNewRound:u,onRevote:l,onOpenSettings:a,onHandoffHost:d,onEndSession:f,onShare:C,shareOpen:A,onSetShareOpen:E,roomCode:R,confirmingQuit:$,myRole:p,onToggleRole:m,roundTitlePromptActive:y,roundTitleInput:w,onRoundTitleChange:D,onRoundTitleSubmit:g,onRoundTitleCancel:O,roundTicketIdPromptActive:b,roundTicketIdInput:j,onRoundTicketIdChange:ae,onRoundTicketIdSubmit:ne,onRoundTicketIdCancel:oe,descriptorPromptActive:Y,descriptorInput:v,onDescriptorChange:T,onDescriptorSubmit:P,onDescriptorCancel:L,onEditDescriptor:k,canExport:B,onExportResults:z}){let[X,re]=It.useState("idle"),[he,we]=It.useState(null),[ue,ce]=It.useState(false),[ye,ve]=It.useState(false);useEffect(()=>{o||(re("idle"),we(null),ce(false),ve(false));},[o]),useInput(h=>{if(h==="c"||h==="C"){E(true);return}if(h==="w"||h==="W"){m();return}if((h==="x"||h==="X")&&B){z();return}o&&((h==="e"||h==="E")&&a(),(h==="s"||h==="S")&&i(),(h==="h"||h==="H")&&re("picking"),(h==="m"||h==="M")&&ce(true),s!==null&&W(s)!==null&&((h==="n"||h==="N")&&u(),(h==="v"||h==="V")&&W(s)!=="skipped"&&l(),(h==="t"||h==="T")&&k()));},{isActive:!c&&!$&&X==="idle"&&!y&&!b&&!ue&&!ye&&!A&&!Y}),useInput((h,Z)=>{h==="y"||h==="Y"?(f(),ve(false)):(h==="n"||h==="N"||Z.escape)&&ve(false);},{isActive:ye});let de=e.participants.filter(h=>h.id!==n&&t.has(h.id));useInput(h=>{if(X==="picking"){let Z=parseInt(h,10);!isNaN(Z)&&Z>=1&&Z<=de.length?(we(de[Z-1].id),re("confirming")):(h==="Escape"||h==="q"||h==="Q")&&re("idle");}},{isActive:X==="picking"}),useInput(h=>{X==="confirming"&&he&&(h==="y"||h==="Y"?(d(he),re("idle"),we(null)):(h==="n"||h==="N"||h==="Escape")&&(re("idle"),we(null)));},{isActive:X==="confirming"});let Ze=r.length>0,Ue=r.filter(h=>W(h)!==null),et=s!==null?Ue.slice(0,-1):Ue;return ue?jsx(lo,{onPick:h=>{ce(false),h==="end-session"&&ve(true);},onClose:()=>ce(false)}):A?jsx(Rt,{roomCode:R,onPick:h=>{E(false),C(h);},onClose:()=>E(false)}):jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:Ze?"Session":"Waiting for participants..."}),!Ze&&jsx(Text,{dimColor:true,children:"No rounds started yet."}),jsx(bt,{participants:e.participants,onlineIds:t,hostId:e.settings.hostParticipantId,votes:{},revealed:false,participantId:n}),s!==null&&W(s)!==null&&jsx(ss,{projection:e,round:s,rounds:r}),et.length>0&&jsxs(Box,{flexDirection:"column",children:[jsx(Text,{dimColor:true,children:"\u2500\u2500 History \u2500\u2500"}),et.map(h=>{let Z=r.indexOf(h)+1;return jsx(Text,{dimColor:true,children:go(h,Z,r)},h.id)})]}),X==="picking"&&de.length>0&&jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{children:"Hand off to:"}),de.map((h,Z)=>jsxs(Text,{children:["[",Z+1,"] ",h.displayName]},h.id)),jsx(Text,{dimColor:true,children:"[q] Cancel"})]}),X==="picking"&&de.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"})]}),X==="confirming"&&he&&jsx(Box,{flexDirection:"column",children:jsxs(Text,{children:["Hand off to ",e.participants.find(h=>h.id===he)?.displayName,"? [y/n]"]})}),y&&jsxs(Box,{flexDirection:"column",children:[jsxs(Box,{gap:1,children:[jsx(Text,{children:"Round title (optional, Enter to skip):"}),jsx(q,{value:w,onChange:D,onSubmit:g,onCancel:O,placeholder:"Enter to skip",isActive:y})]}),jsx(Text,{dimColor:true,children:"[Esc] Cancel"})]}),b&&jsxs(Box,{flexDirection:"column",children:[jsxs(Box,{gap:1,children:[jsx(Text,{children:"Ticket ID (optional, Enter to skip):"}),jsx(q,{value:j,onChange:ae,onSubmit:ne,onCancel:oe,placeholder:"e.g. PROJ-1234",isActive:b})]}),jsx(Text,{dimColor:true,children:"[Esc] Cancel"})]}),Y&&jsxs(Box,{flexDirection:"column",children:[jsxs(Box,{gap:1,children:[jsx(Text,{children:"Edit round title:"}),jsx(q,{value:v,onChange:T,onSubmit:P,onCancel:L,placeholder:"Enter to save",isActive:Y})]}),jsx(Text,{dimColor:true,children:"[Esc] Cancel \xB7 empty submit cancels"})]}),ye&&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"})]}),X==="idle"&&!y&&!b&&!Y&&!ye&&jsxs(Box,{gap:2,children:[o?jsx(Fragment,{children:s!==null&&W(s)!==null?jsxs(Fragment,{children:[W(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"}),B&&jsx(Text,{color:"yellow",children:"[x] Export"}),jsxs(Text,{color:"yellow",children:["[w] ",p==="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"}),B&&jsx(Text,{color:"yellow",children:"[x] Export"}),jsxs(Text,{color:"yellow",children:["[w] ",p==="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"}),B&&jsx(Text,{color:"yellow",children:"[x] Export"}),jsxs(Text,{color:"yellow",children:["[w] ",p==="viewer"?"Vote":"Observe"]})]}),jsx(Text,{children:"[q] Quit"})]})]})}function ss({projection:e,round:t,rounds:o}){let n=o.indexOf(t)+1,r=fo(t,n,o);if(W(t)==="skipped")return jsx(Box,{flexDirection:"column",gap:1,children:jsx(Text,{bold:true,children:r})});let s=mo(t.votes),c=new Map(e.participants.map(l=>[l.id,l.displayName])),i=t.timerStartedAt!==void 0&&t.revealedAt!==void 0?nn(t.timerStartedAt,t.revealedAt):null,u=Object.values(t.votes)[0];return jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:r}),En(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(([l,a])=>jsxs(Box,{gap:2,children:[jsx(Text,{children:(c.get(l)??l).padEnd(20)}),jsx(Text,{color:"cyan",bold:true,children:a.padEnd(10)})]},l)),e.participants.filter(l=>!(l.id in t.votes)).map(l=>jsxs(Box,{gap:2,children:[jsx(Text,{dimColor:true,children:l.displayName.padEnd(20)}),jsx(Text,{dimColor:true,children:"(no vote)".padEnd(10)})]},l.id))]}),jsxs(Box,{gap:2,children:[jsxs(Text,{children:["Avg: ",jsx(Text,{bold:true,children:s.average})]}),jsxs(Text,{children:["Med: ",jsx(Text,{bold:true,children:s.median})]}),jsxs(Text,{children:["Agreement: ",jsx(Text,{bold:true,children:s.agreement})]}),i!==null&&jsxs(Text,{dimColor:true,children:["\u23F1 ",i]})]})]})}function wo({scale:e,selected:t,onSelect:o,onClear:n,disabled:r}){let[s,c]=useState(0);return useInput((i,u)=>{if(!r)if(u.leftArrow)c(l=>Math.max(0,l-1));else if(u.rightArrow)c(l=>Math.min(e.cards.length-1,l+1));else if(u.return){let l=e.cards[s];l===t?n():o(l);}else (u.backspace||u.delete)&&n();}),jsxs(Box,{flexDirection:"column",gap:0,children:[jsx(Box,{gap:1,children:e.cards.map((i,u)=>{let l=u===s,a=i===t;return jsx(Text,{inverse:l,bold:a,...a&&{color:"cyan"},children:a?`[${i}]`:` ${i} `},i)})}),jsx(Text,{dimColor:true,children:" \u2190\u2192 navigate \u23CE select \u232B clear"})]})}function ls(e){let t=Math.floor(e/1e3),o=Math.floor(t/60),n=t%60;return `${o}:${n.toString().padStart(2,"0")}`}function yo({projection:e,round:t,onlineIds:o,isHost:n,myId:r,myRole:s,elapsedMs:c,roomCode:i,shareOpen:u,settingsOpen:l,onVote:a,onClearVote:d,onReveal:f,onSkip:C,onToggleRole:A,onShare:E,onSetShareOpen:R,onOpenSettings:$,confirmingQuit:p}){if(useInput(g=>{if(g==="e"||g==="E"){$();return}if(g==="c"||g==="C"){R(true);return}n&&(g==="r"||g==="R")&&f(),n&&(g==="s"||g==="S")&&C(),(g==="w"||g==="W")&&A();},{isActive:!p&&!l&&!u}),u)return jsx(Rt,{roomCode:i,onPick:g=>{R(false),E(g);},onClose:()=>R(false)});let m=t.votes[r],y=e.rounds.indexOf(t)+1,w=t.title?` \u2014 ${t.title}`:"",D=t.ticketId?` [${t.ticketId}]`:"";return jsxs(Box,{flexDirection:"column",gap:1,children:[jsxs(Box,{gap:2,children:[jsxs(Text,{bold:true,children:["Round ",y,w,D]}),c!==void 0&&jsxs(Text,{dimColor:true,children:["\u23F1 ",ls(c)]})]}),jsxs(Box,{gap:4,children:[jsx(bt,{participants:e.participants,onlineIds:o,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(wo,{scale:t.scale,selected:m,onSelect:a,onClear:d,disabled:p})]}),jsxs(Box,{gap:2,children:[n&&jsx(Text,{color:"yellow",children:"[r] Reveal"}),n&&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 ms=3e3;function At(e=ms){let[t,o]=useState(null),n=useRef(null),r=useCallback(()=>{n.current!==null&&(clearTimeout(n.current),n.current=null);},[]),s=useCallback(i=>{r(),o(i),n.current=setTimeout(()=>{o(null),n.current=null;},e);},[r,e]),c=useCallback(()=>{r(),o(null);},[r]);return useEffect(()=>()=>{r();},[r]),{notice:t,show:s,dismiss:c}}function Ss(e){let t=ee(e);return !t||W(t)!==null?"lobby":"voting"}function Pt({session:e,participantId:t,displayName:o,accessMode:n,presetOverrides:r,customScales:s}){let{exit:c}=useApp(),[i,u]=useState(e.projection()),[l,a]=useState("connecting"),[d,f]=useState(e.onlineIds()),[C,A]=useState(),[E,R]=useState(false),[$,p]=useState(n==="password"?"password":"open"),[m,y]=useState(o),[w,D]=useState(false),[g,O]=useState(false),[b,j]=useState(""),[ae,ne]=useState(false),[oe,Y]=useState(""),[v,T]=useState(),[P,L]=useState(false),[k,B]=useState(""),[z,X]=useState(null),re=At(3e3),[he,we]=useState(false),ue=At(3e3),ce=At(3e3);useEffect(()=>{let I=e.onChange(()=>{u(e.projection()),f(e.onlineIds());}),_=e.onStatusChange(a),ke=e.onHostChange(tt=>{let zo=e.projection().participants.find(Qo=>Qo.id===tt),Xo=tt===t?"You are now the host":`${zo?.displayName||"Unknown"} is now the host`;re.show(Xo);}),Go=setInterval(()=>{f(e.onlineIds());let tt=e.projection(),nt=ee(tt);nt?.timerStartedAt!==void 0&&nt.revealedAt===void 0?A(Date.now()-nt.timerStartedAt):A(void 0);},1e3);return ()=>{I(),_(),ke(),clearInterval(Go);}},[e,t,re.show]),useInput(I=>{(I==="q"||I==="Q")&&D(true);},{isActive:!E&&!w&&!g&&!ae&&!P&&!he}),useInput((I,_)=>{I==="y"||I==="Y"?(e.disconnect(),c()):(I==="n"||I==="N"||_.escape)&&D(false);},{isActive:w});let ye=Ss(i),ve=ee(i),de=i.settings.hostParticipantId===t,Ze=i.participants.find(I=>I.id===t),Ue=be(Ze??{}),et=useCallback(()=>{j(""),O(true);},[]),h=useCallback(I=>{O(false),j(""),T(Wt(I)),Y(""),ne(true);},[]),Z=useCallback(()=>{O(false),j(""),T(void 0);},[]),Eo=useCallback(I=>{ne(false),Y("");let[_,ke]=Xn(v,I);e.newRound(_,ke),T(void 0);},[e,v]),Ao=useCallback(()=>{ne(false),Y(""),T(void 0);},[]),No=useCallback(()=>{let I=e.current();!I||I.revealedAt===void 0||(X(I.id),B(I.title??""),L(true));},[e]),Po=useCallback(I=>{let _=z;if(L(false),X(null),!_)return;let ke=I.trim();ke!==""&&e.updateRoundDescriptor(_,{title:ke});},[e,z]),Do=useCallback(()=>{L(false),X(null);},[]),ko=useCallback(I=>{e.vote(I);},[e]),Oo=useCallback(()=>{e.unvote();},[e]),Mo=useCallback(()=>{e.reveal();},[e]),$o=useCallback(()=>{e.skip();},[e]),Bo=useCallback(()=>{j(""),O(true);},[]),Ho=useCallback(()=>{e.revote();},[e]),cn=useCallback(()=>{e.toggleRole();},[e]),Uo=useCallback(I=>{e.updateName(I);},[e]),jo=useCallback(I=>{e.updateScale(I);},[e]),Lo=useCallback(I=>{e.updateDisplayName(I),y(I);},[e]),_o=useCallback(async(I,_)=>{await e.updateAccess(I,_),p(I);},[e]),Fo=useCallback(I=>{try{e.handoffHost(I);}catch(_){console.error(`Handoff failed: ${_ instanceof Error?_.message:String(_)}`);}},[e]),dn=useCallback(()=>{R(true);},[]),Vo=useCallback(()=>{R(false);},[]),Yo=useCallback(()=>{e.endSessionForEveryone();},[e]),ln=useCallback(async I=>{let _=I==="code"?gt(e.roomCode):I==="url"?ht(e.roomCode):St(e.roomCode);try{await Co.write(_),ue.show("Copied!");}catch{ue.show(`Copy failed \u2014 ${_}`);}},[e.roomCode,ue]),Ot=i.rounds.filter(I=>W(I)!==null),Jo=Ot.length>0?Ot.at(-1):null,Wo=Ot.length>0,qo=useCallback(()=>uo(i,I=>Co.write(I),ce.show),[i,ce.show]);return jsxs(Box,{flexDirection:"column",children:[jsx(Zn,{roomCode:e.roomCode,sessionName:i.settings.name,status:l,isHost:de}),re.notice&&jsx(Box,{paddingX:1,children:jsx(Text,{color:"yellow",children:re.notice})}),ue.notice&&jsx(Box,{paddingX:1,children:jsx(Text,{color:ue.notice.startsWith("Copy failed")?"red":"green",children:ue.notice})}),ce.notice&&jsx(Box,{paddingX:1,children:jsx(Text,{color:ce.notice.startsWith("Clipboard write failed")?"red":"green",children:ce.notice})}),E?jsx(ao,{sessionName:i.settings.name,displayName:m,currentScale:i.settings.defaultScale,accessMode:$,presetOverrides:r,customScales:s,isHost:de,onChangeName:Uo,onChangeScale:jo,onChangeDisplayName:Lo,onChangeAccess:_o,onClose:Vo}):ye==="lobby"?jsx(ho,{projection:i,onlineIds:d,isHost:de,participantId:t,rounds:i.rounds,currentRound:Jo,settingsOpen:E,onStartRound:et,onNewRound:Bo,roundTitlePromptActive:g,roundTitleInput:b,onRoundTitleChange:j,onRoundTitleSubmit:h,onRoundTitleCancel:Z,roundTicketIdPromptActive:ae,roundTicketIdInput:oe,onRoundTicketIdChange:Y,onRoundTicketIdSubmit:Eo,onRoundTicketIdCancel:Ao,onRevote:Ho,onOpenSettings:dn,onHandoffHost:Fo,onEndSession:Yo,onShare:ln,shareOpen:he,onSetShareOpen:we,roomCode:e.roomCode,confirmingQuit:w,myRole:Ue,onToggleRole:cn,descriptorPromptActive:P,descriptorInput:k,onDescriptorChange:B,onDescriptorSubmit:Po,onDescriptorCancel:Do,onEditDescriptor:No,canExport:Wo,onExportResults:qo}):ye==="voting"&&ve?jsx(yo,{projection:i,round:ve,onlineIds:d,isHost:de,myId:t,myRole:Ue,elapsedMs:C,onVote:ko,onClearVote:Oo,onReveal:Mo,onSkip:$o,onToggleRole:cn,onShare:ln,onSetShareOpen:we,shareOpen:he,settingsOpen:E,onOpenSettings:dn,roomCode:e.roomCode,confirmingQuit:w}):null,w&&jsx(Box,{children:jsx(Text,{dimColor:true,children:"Leave session? y/n"})})]})}function bo({presetOverrides:e,customScales:t}){let{exit:o}=useApp(),[n,r]=useState("main-menu"),[s,c]=useState(Ce()),[i,u]=useState(s.displayName??""),[l,a]=useState(s.relayUrl??""),[d,f]=useState(s.defaultSessionName??""),[C,A]=useState(t??[]),[E,R]=useState(e??{});useInput(v=>{(v==="q"||v==="Q")&&o();},{isActive:n==="main-menu"});let $=useMemo(()=>lt(E,C),[E,C]),p=s.defaultScale,m=p?$.find(v=>v.id===p):void 0,y=[{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?Ie(m.id,C):"(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(v){v==="display-name"?(u(s.displayName??""),r("editing-display-name")):v==="relay-url"?(a(s.relayUrl??""),r("editing-relay-url")):v==="default-scale"?r("picking-scale"):v==="default-session-name"?(f(s.defaultSessionName??""),r("editing-session-name")):v==="default-password"&&r("picking-default-password");}function D(v){let T=v.trim();if(T.length>0){let P={...s,displayName:T};c(P),J({displayName:T});}r("main-menu");}function g(v){let T=v.trim();if(T.length>0){let P={...s,relayUrl:T};c(P),J({relayUrl:T});}r("main-menu");}function O(v){let T=v.trim(),P=T.length>0?{...s,defaultSessionName:T}:{...s};T.length===0?(delete P.defaultSessionName,c(P),_e({defaultSessionName:""})):(c(P),J({defaultSessionName:T})),r("main-menu");}function b(v){let T=v.length>0?{...s,defaultScale:v}:{...s};v.length===0?(delete T.defaultScale,c(T),_e({defaultScale:""})):(c(T),J({defaultScale:v})),r("main-menu");}function j(v,T){let P={...E};T.length===0?delete P[v]:P[v]=T,R(P),J({presetOverrides:Object.keys(P).length>0?P:void 0});}function ae(v){let T=v==="on",P={...s,defaultPassword:T};c(P),J({defaultPassword:T}),r("main-menu");}function ne(v,T){let P=`custom-${randomUUID()}`,L=[...C,{id:P,label:v,cards:T}];A(L),c({...s,customScales:L}),J({customScales:L});}function oe(v,T,P){let L=C.map(k=>k.id===v?{id:v,label:T,cards:P}:k);A(L),c({...s,customScales:L}),J({customScales:L});}function Y(v){let T=C.filter(L=>L.id!==v);A(T);let P={...s,customScales:T};s.defaultScale===v?(P.defaultScale="fibonacci",J({customScales:T,defaultScale:"fibonacci"})):J({customScales:T}),c(P);}if(n==="main-menu")return jsx(pe,{title:"\u2500\u2500 Config Settings \u2500\u2500",items:y,onSelect:w,onClose:()=>o()});if(n==="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(q,{value:i,onChange:u,onSubmit:D,onCancel:()=>r("main-menu"),placeholder:"(required)",isActive:true}),jsx(Text,{dimColor:true,children:"\u23CE confirm esc back"})]});if(n==="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(q,{value:l,onChange:a,onSubmit:g,onCancel:()=>r("main-menu"),placeholder:"(required)",isActive:true}),jsx(Text,{dimColor:true,children:"\u23CE confirm esc back"})]});if(n==="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(q,{value:d,onChange:f,onSubmit:O,onCancel:()=>r("main-menu"),placeholder:"(leave empty to clear)",isActive:true}),jsx(Text,{dimColor:true,children:"\u23CE confirm esc back"})]});if(n==="picking-scale"){let v=[{id:"",cards:[]},...$];return jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:"\u2500\u2500 Config Settings \u2500\u2500"}),jsx(yt,{scales:v,selectedScaleId:p??"",presetOverrides:E,customScales:C,onSelect:b,onOverrideChange:j,onCustomScaleCreate:ne,onCustomScaleEdit:oe,onCustomScaleDelete:Y,onClose:()=>r("main-menu"),showNoneOption:true})]})}if(n==="picking-default-password"){let v=[{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(pe,{title:"Default password:",items:v,onSelect:ae,onClose:()=>r("main-menu")})]})}return jsx(Box,{})}async function an(){return new Promise(e=>{let t=createInterface({input:process.stdin,output:process.stderr});process.stderr.write("Password: "),t.question("",o=>{t.close(),e(o);});})}async function Ts(e){let t=await Le(),o=e.scale??t.defaultScale??"fibonacci",n=e.name??t.defaultSessionName,r=e.password??t.defaultPassword??false,s;r&&(s=await an());let c=dt(o,t.presetOverrides,t.customScales);if(!c){let d=Object.keys(se).join(", ");console.error(`Unknown or fully-disabled scale: "${o}". Builtin IDs: ${d}`),process.exit(1);}let i=await Jn({participantId:t.participantId,displayName:t.displayName,relayUrl:t.relayUrl,sessionName:n,password:s,defaultScale:c}),u;s&&(u=await Re(s,i.roomUuid)),pt({roomCode:i.roomCode,roomUuid:i.roomUuid,passwordHash:u,lastSeenAt:Date.now()}),console.error(`Room created: ${i.roomCode}`),console.error(`Share: hnch join ${i.roomCode}`),console.error("");let{waitUntilExit:l,unmount:a}=render(It.createElement(Pt,{session:i,participantId:t.participantId,displayName:t.displayName,accessMode:s?"password":"open",presetOverrides:t.presetOverrides,customScales:t.customScales}));mt({doc:i.doc,localParticipantId:t.participantId,onEnded:async d=>{console.error(d.isLocal?"Session ended.":`Session ended by ${d.hostName}.`),a();try{await F();}catch(f){console.error(`Failed to clear cached session: ${f instanceof Error?f.message:String(f)}`);}i.disconnect(),process.exit(0);}}),await l(),i.disconnect(),F(),process.exit(0);}async function Es(e){let t=await Le(),o=qn(e.code);o===null&&(console.error("invalid room code"),process.exit(1));let n;e.password&&(n=await an());let r=await Ye(t.relayUrl,o),s;r.accessMode==="password"&&!e.password?s=await an():e.password&&(s=n);let c,i=false,u=await Wn({participantId:t.participantId,displayName:t.displayName,relayUrl:t.relayUrl,roomCode:o,password:s,onRejection(d){if(d==="unknown-room"){ft({clearLastSession:F,logger:console,exit:process.exit});return}d==="room-full"&&(i=true,c?.());}}),l;s&&(l=await Re(s,u.roomUuid)),pt({roomCode:u.roomCode,roomUuid:u.roomUuid,passwordHash:l,lastSeenAt:Date.now()}),console.error(`Joined room: ${u.roomCode}`),console.error("");let a=render(It.createElement(Pt,{session:u,participantId:t.participantId,displayName:t.displayName,accessMode:r.accessMode,presetOverrides:t.presetOverrides,customScales:t.customScales}));c=a.unmount,mt({doc:u.doc,localParticipantId:t.participantId,onEnded:async d=>{console.error(d.isLocal?"Session ended.":`Session ended by ${d.hostName}.`),c?.();try{await F();}catch(f){console.error(`Failed to clear cached session: ${f instanceof Error?f.message:String(f)}`);}u.disconnect(),process.exit(0);}}),await a.waitUntilExit(),i&&(console.error("This room is full (max 20 participants)."),u.disconnect(),F(),process.exit(1)),u.disconnect(),F(),process.exit(0);}async function As(){let e=await Le(),t=zn();t||(console.error("No recent session to resume."),process.exit(0));let o;try{o=await Ye(e.relayUrl,t.roomCode);}catch(a){if((a instanceof Error?a.message:String(a)).includes("Room not found"))return await ft({clearLastSession:F,logger:console,exit:process.exit});console.error("Could not reach relay \u2014 try again later."),process.exit(0);}o.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 n=new Oe.Doc,r=new Awareness(n);ct(n,{participantId:e.participantId,displayName:e.displayName}),r.setLocalStateField("participantId",e.participantId);let s=false,c=false,i,u=ut({doc:n,awareness:r,roomCode:t.roomCode,roomUuid:t.roomUuid,participantId:e.participantId,relayUrl:e.relayUrl,passwordHash:t.passwordHash,onRejection(a){if(a==="unknown-room"){ft({clearLastSession:F,logger:console,exit:process.exit});return}a==="wrong-password"&&(s=true,i?.()),a==="room-full"&&(c=true,i?.());}});pt({roomCode:u.roomCode,roomUuid:u.roomUuid,passwordHash:t.passwordHash,lastSeenAt:Date.now()});let l=render(It.createElement(Pt,{session:u,participantId:e.participantId,displayName:e.displayName,accessMode:o.accessMode,presetOverrides:e.presetOverrides,customScales:e.customScales}));i=l.unmount,mt({doc:u.doc,localParticipantId:e.participantId,onEnded:async a=>{console.error(a.isLocal?"Session ended.":`Session ended by ${a.hostName}.`),i?.();try{await F();}catch(d){console.error(`Failed to clear cached session: ${d instanceof Error?d.message:String(d)}`);}u.disconnect(),process.exit(0);}}),await l.waitUntilExit(),c&&(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)),u.disconnect(),F(),process.exit(0);}async function Ns(e){if(!(e.name!==void 0||e.relayUrl!==void 0||e.scale!==void 0||e.sessionName!==void 0||e.password!==void 0)){let n=await Le(),{waitUntilExit:r}=render(It.createElement(bo,{presetOverrides:n.presetOverrides,customScales:n.customScales}));await r(),process.exit(0);}let o={};if(e.name!==void 0&&(o.displayName=e.name),e.relayUrl!==void 0&&(o.relayUrl=e.relayUrl),e.scale!==void 0){if(e.scale!==""){let n=Ce();if(!dt(e.scale,n.presetOverrides,n.customScales)){console.error(`Unknown scale: "${e.scale}". Builtin IDs: ${Object.keys(se).join(", ")}`);return}}o.defaultScale=e.scale;}e.sessionName!==void 0&&(o.defaultSessionName=e.sessionName),e.password!==void 0&&(o.defaultPassword=e.password),_e(o),console.log("Config updated.");}var To=bs(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=>{Ts(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=>{Es(e).catch(t=>{console.error(t instanceof Error?t.message:t),process.exit(1);});}).command("resume","Rejoin your last session",e=>e,()=>{As().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=>{Ns(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(),sn=hideBin(process.argv);(sn.length===0||sn.length===1&&sn[0]==="--")&&(To.showHelp(),process.exit(0));To.parse();
|
|
20
|
+
`}function ut(e,t,o){if(e.startsWith("custom-")){let r=(o??[]).find(s=>s.id===e);return r?{id:r.id,cards:r.cards}:void 0}let n=se[e];if(n){let r=(t??{})[e]??[];if(r.length===0)return n;let s=n.cards.filter((c,a)=>!r.includes(a));return s.length===0?void 0:{id:e,cards:s}}}function pt(e,t){let o=Object.keys(se).map(r=>ut(r,e,t)).filter(r=>r!==void 0),n=(t??[]).map(r=>({id:r.id,cards:r.cards}));return [...o,...n]}function Ee(e,t){if(e.startsWith("custom-")){let o=(t??[]).find(n=>n.id===e);if(o&&o.label.trim().length>0)return o.label}return Cn(e)}async function Vn(e,t){let o=await fetch(`${e}/rooms`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({})});if(!o.ok)throw new Error(`POST /rooms failed: ${o.status}`);return await o.json()}async function We(e,t){let o=await fetch(`${e}/rooms/${encodeURIComponent(t)}`);if(o.status===404){let n=await o.json().catch(()=>null);throw new Error(`Room not found: ${n?.reason??"unknown-room"}`)}if(!o.ok)throw new Error(`GET /rooms/:code failed: ${o.status}`);return await o.json()}async function Jt(e,t,o){let n=await fetch(`${e}/rooms/${t}/access`,{method:"PATCH",headers:{"content-type":"application/json"},body:JSON.stringify({passwordHash:o})});if(!n.ok)throw new Error(`PATCH /rooms/:uuid/access failed: ${n.status}`)}function Fn(e){let o=`${e.wsBaseUrl.replace(/^http/,"ws")}/ws`,n={did:e.deviceId};e.passwordHash!==void 0&&(n.pw=e.passwordHash);let r=new WebsocketProvider(o,e.roomUuid,e.doc,{params:n,connect:true,...e.awareness!==void 0&&{awareness:e.awareness},WebSocketPolyfill:Pr}),s=l=>{e.onStatusChange?.(l.status);},c=l=>{if(l===null)return;let d=hn.get(l.code);d!==void 0&&(e.onRejection?.(d),r.shouldConnect=false,r.disconnect());};r.on("status",s),r.on("connection-close",c);let a=false,u=e.passwordHash;return {doc:e.doc,awareness:r.awareness,updateCredentials(l){if(l.passwordHash===u)return;u=l.passwordHash??void 0,r.shouldConnect=false,r.disconnect();let i={did:r.params.did??""};u!==void 0&&(i.pw=u),r.params=i,r.connect();},disconnect(){a||(a=true,r.off("status",s),r.off("connection-close",c),r.shouldConnect=false,r.disconnect(),r.destroy());}}}var Dr=5e3;function Yn(e,t,o){let n=null,r=null;function s(){let d=new Set;return t.getStates().forEach(i=>{let p=i.participantId;typeof p=="string"&&d.add(p);}),d}function c(){return e.getMap(Ye).get("hostParticipantId")}function a(){r!==null&&(clearTimeout(r),r=null),n=null;}function u(){let d=c(),i=n;if(n=null,!i||d!==i)return;let p=s();if(p.has(d))return;let b=[...p].toSorted();if(b.length===0)return;let A=b[0];A===o&&$e(e,{hostParticipantId:A});}function l(){let d=c();if(!d)return;if(s().has(d)){a();return}n!==d&&(a(),n=d,r=setTimeout(()=>{r=null,u();},Dr));}return t.on("change",l),e.getMap(Ye).observe(l),l(),{stop(){a(),t.off("change",l),e.getMap(Ye).unobserve(l);}}}async function qn(e){let t,o=await Vn(e.relayUrl);e.password&&(t=await Te(e.password,o.uuid),await Jt(e.relayUrl,o.uuid,t));let n=new Me.Doc,r=new Awareness(n),s=Date.now();return bn(n,{roomUuid:o.uuid,createdAt:s,participantId:e.participantId,displayName:e.displayName,defaultScale:e.defaultScale??st,...e.sessionName!==void 0&&{sessionName:e.sessionName}}),r.setLocalStateField("participantId",e.participantId),ft({doc:n,awareness:r,roomCode:o.code,roomUuid:o.uuid,participantId:e.participantId,relayUrl:e.relayUrl,passwordHash:t})}async function Gn(e){let t=await We(e.relayUrl,e.roomCode),o;if(t.accessMode==="password"){if(!e.password)throw new Error("Room requires a password");o=await Te(e.password,t.uuid);}let n=new Me.Doc,r=new Awareness(n);return lt(n,{participantId:e.participantId,displayName:e.displayName}),r.setLocalStateField("participantId",e.participantId),ft({doc:n,awareness:r,roomCode:e.roomCode,roomUuid:t.uuid,participantId:e.participantId,relayUrl:e.relayUrl,passwordHash:o,onRejection:e.onRejection})}function ft(e){let{doc:t,awareness:o,participantId:n,relayUrl:r,roomUuid:s}=e,c=new Set,a=new Set,u=new Set,l=Fn({wsBaseUrl:e.relayUrl,roomUuid:e.roomUuid,doc:t,deviceId:n,passwordHash:e.passwordHash,awareness:o,onStatusChange(f){c.forEach(m=>m(f));},onRejection(f){console.error(`Connection rejected: ${f}`),e.onRejection?.(f);}}),d=Yn(t,o,n),i=()=>{a.forEach(f=>f());};t.getMap("meta").observeDeep(i),t.getMap("settings").observeDeep(i),t.getMap("participants").observeDeep(i),t.getArray("rounds").observeDeep(i);function p(){let f=new Set;return o.getStates().forEach(m=>{let y=m.participantId;typeof y=="string"&&f.add(y);}),f}let b=new Set,A=setInterval(()=>{let f=Q(t),m=ee(f);if(!m||m.revealedAt!==void 0||b.has(m.id))return;let y=p();if(y.size===0)return;let v=f.participants.filter(h=>y.has(h.id)&&Ie(h)==="voter");if(v.length===0)return;v.filter(h=>h.id in m.votes).length>=v.length&&(b.add(m.id),_t(t,m.id,Date.now()));},250),E=t.getMap("settings").get("hostParticipantId"),R=()=>{let f=t.getMap("settings").get("hostParticipantId");f!==E&&f!==void 0&&u.forEach(m=>m(f)),E=f;};t.getMap("settings").observe(R);let $=false;return {doc:t,awareness:o,roomCode:e.roomCode,roomUuid:e.roomUuid,projection(){return Q(t)},current(){return ee(Q(t))},isHost(){return Q(t).settings.hostParticipantId===n},onlineIds:p,vote(f){let m=ee(Q(t));m&&In(t,m.id,n,f);},unvote(){let f=ee(Q(t));f&&Tn(t,f.id,n);},reveal(){let f=ee(Q(t));f&&_t(t,f.id,Date.now());},skip(){let f=ee(Q(t));f&&En(t,f.id,Date.now());},newRound(f,m){let y=randomUUID();return jt(t,{id:y,createdAt:Date.now(),createdBy:n,...f!==void 0&&{title:f},...m!==void 0&&{ticketId:m}}),y},revote(){let f=ee(Q(t)),m=randomUUID();return jt(t,{id:m,createdAt:Date.now(),createdBy:n,...f?.id!==void 0&&{parentRoundId:f.id},...f?.title!==void 0&&{title:f.title},...f?.description!==void 0&&{description:f.description},...f?.ticketId!==void 0&&{ticketId:f.ticketId}}),m},updateRoundDescriptor(f,m){Rn(t,f,m);},updateName(f){$e(t,{name:f});},updateScale(f){$e(t,{defaultScale:f});},updateDisplayName(f){let v=Q(t).participants.find(k=>k.id===n)?.joinedAt??Date.now();Lt(t,n,{displayName:f,joinedAt:v});},async updateAccess(f,m){let y=null;if(f==="password"){if(!m)throw new Error("Password required for password mode");y=await Te(m,s);}await Jt(r,s,y),l.updateCredentials({passwordHash:y});},handoffHost(f){if(!this.isHost())throw new Error("Only the current host can hand off");if(!p().has(f))throw new Error("Target participant is not online");if(!Q(t).participants.some(k=>k.id===f))throw new Error("Target participant does not exist");$e(t,{hostParticipantId:f});},toggleRole(){let m=Q(t).participants.find(h=>h.id===n),v=Ie(m??{})==="viewer"?"voter":"viewer",k=m?.joinedAt??Date.now();Lt(t,n,{displayName:m?.displayName??"",joinedAt:k,role:v});},endSessionForEveryone(){if(!this.isHost())throw new Error("Only the current host can end the session");An(t);},onChange(f){return a.add(f),()=>a.delete(f)},onStatusChange(f){return c.add(f),()=>c.delete(f)},onHostChange(f){return u.add(f),()=>u.delete(f)},disconnect(){$||($=true,clearInterval(A),d.stop(),t.getMap("meta").unobserveDeep(i),t.getMap("settings").unobserveDeep(i),t.getMap("settings").unobserve(R),t.getMap("participants").unobserveDeep(i),t.getArray("rounds").unobserveDeep(i),l.disconnect());}}}function zn(e){let t=Vt(e);return t===""?null:t}var Ur=1440*60*1e3;function qt(){return join(_e(),"last-session.json")}function Lr(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 Qn(){let e=qt();if(!existsSync(e))return null;try{let t=JSON.parse(readFileSync(e,"utf-8"));return !Lr(t)||Date.now()-t.lastSeenAt>Ur?(V(),null):t}catch{return V(),null}}function mt(e){let t=_e();mkdirSync(t,{recursive:true,mode:448}),chmodSync(t,448);let o=qt();writeFileSync(o,JSON.stringify(e,null,2)+`
|
|
21
|
+
`,{encoding:"utf-8",mode:384}),chmodSync(o,384);}function V(){let e=qt();try{unlinkSync(e);}catch(t){if(t.code!=="ENOENT")throw t}}async function gt(e){let t=true;try{await e.clearLastSession();}catch(o){t=false,e.logger.error(`Failed to clear cached session: ${o instanceof Error?o.message:String(o)}`);}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 ht(e){let t=e.doc.getMap("meta"),o=null,n=false;function r(){if(n)return;let s=t.get("sessionEndedAt");if(s==null||s===o)return;o=s;let c=t.get("createdBy"),a=c===e.localParticipantId,u=e.doc.getMap("participants"),l=e.doc.getMap("settings").get("hostParticipantId")??c,d="host";if(l){let p=u.get(l)?.get("displayName");typeof p=="string"&&p.length>0&&(d=p);}setImmediate(()=>{n||e.onEnded({isLocal:a,hostName:d,timestamp:s});});}return t.observe(r),r(),()=>{n=true,t.unobserve(r);}}var jr="@xauyxau/hnch";function St(e){return Bn(e)}function xt(e){return `${process.env.HNCH_SHARE_ORIGIN??"https://hnch.dev"}/join?code=${e}`}function wt(e){return `npx ${jr} join ${e}`}function Gt(e){let t=e.trim();return t===""?void 0:t}function Kn(e,t){return [e,Gt(t)]}var _r={connecting:"yellow",connected:"green",disconnected:"red"};function to({roomCode:e,sessionName:t,status:o,isHost:n}){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:[n&&jsx(Text,{color:"yellow",children:"[host]"}),jsx(Text,{color:_r[o],children:o})]})]})}function me({items:e,onSelect:t,onClose:o,title:n}){if(e.length===0)return jsxs(Box,{flexDirection:"column",gap:1,children:[n&&jsx(Text,{bold:true,children:n}),jsx(Text,{dimColor:true,children:"(no items)"})]});let r=e.findIndex(a=>!a.disabled),[s,c]=useState(Math.max(r,0));return useInput((a,u)=>{if(u.upArrow)c(l=>{let d=(l-1+e.length)%e.length,i=0;for(;e[d]?.disabled&&i<e.length;)d=(d-1+e.length)%e.length,i++;return d});else if(u.downArrow)c(l=>{let d=(l+1)%e.length,i=0;for(;e[d]?.disabled&&i<e.length;)d=(d+1)%e.length,i++;return d});else if(u.return){let l=e[s];l&&!l.disabled&&t(l.value);}else u.escape&&o?.();}),jsxs(Box,{flexDirection:"column",gap:1,children:[n&&jsx(Text,{bold:true,children:n}),jsx(Box,{flexDirection:"column",children:e.map((a,u)=>{let l=u===s,d=a.disabled??false;return jsxs(Box,{gap:1,children:[jsx(Text,{...l&&{color:"cyan"},dimColor:d,children:l?">":" "}),jsx(Text,{bold:l,dimColor:d,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 q({value:e,onChange:t,onSubmit:o,onCancel:n,placeholder:r,isActive:s=true,mask:c=false}){useInput((d,i)=>{i.escape?n?.():i.return?o(e):i.backspace||i.delete?t(e.slice(0,-1)):d&&!i.ctrl&&!i.meta&&t(e+d);},{isActive:s});let a=c&&e.length>0?"*".repeat(e.length):e,u=a.length>0?a:r??"",l=e.length===0&&r!==void 0;return jsxs(Text,{children:[jsx(Text,{dimColor:l,children:u}),jsx(Text,{inverse:true,children:" "})]})}function so({mode:e,password:t,onModeChange:o,onPasswordChange:n,onClose:r}){let[s,c]=useState(e==="open"?0:1),[a,u]=useState(false);return useEffect(()=>{c(e==="open"?0:1);},[e]),useInput((l,d)=>{if(d.leftArrow)c(i=>Math.max(0,i-1));else if(d.rightArrow)c(i=>Math.min(1,i+1));else if(d.return){let i=s===0?"open":"password";i!==e&&o(i),i==="password"&&u(true);}else d.escape&&r?.();},{isActive:!a}),jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Box,{gap:1,children:["open","password"].map((l,d)=>{let i=d===s,p=l===e,b=l==="open"?"Open":"Password";return jsx(Text,{inverse:i,bold:p,...p&&{color:"cyan"},children:p?`[${b}]`:` ${b} `},l)})}),e==="password"&&jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{children:"Password: "}),jsx(q,{value:t,onChange:n,onSubmit:()=>u(false),onCancel:()=>u(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 co({scaleId:e,cards:t,disabledIndices:o,onOverrideChange:n,onClose:r}){let s=useMemo(()=>new Set(o),[o]),[c,a]=useState(0),u=l=>{let d=new Set(s);if(d.has(l))d.delete(l);else {if(d.size>=t.length-1)return;d.add(l);}let i=Array.from(d).toSorted((p,b)=>p-b);n(e,i);};return useInput((l,d)=>{d.leftArrow?a(i=>Math.max(0,i-1)):d.rightArrow?a(i=>Math.min(t.length-1,i+1)):l===" "?u(c):(d.return||d.escape)&&r();}),jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Box,{gap:1,children:t.map((l,d)=>{let i=d===c,p=s.has(d);return jsx(Text,{inverse:i,bold:!p,...!p&&{color:"cyan"},strikethrough:p,dimColor:p,children:l},l)})}),jsx(Text,{dimColor:true,children:" \u2190\u2192 navigate space toggle \u23CE/esc done"})]})}function nn({initialName:e="",initialCards:t=[],onSave:o,onCancel:n}){let r=e.length>0,[s,c]=useState(e),[a,u]=useState(t.length>0?t:[""]),[l,d]=useState(0),[i,p]=useState("editing-name"),[b,A]=useState(""),E=()=>{let f=s.trim();if(f.length===0||f.length>40){A("Name must be 1-40 characters");return}c(f),A(""),p("editing-cards");},R=()=>{let f=s.trim();if(f.length===0||f.length>40){A("Name must be 1-40 characters");return}let m=a.map(v=>v.trim()).filter(v=>v.length>0),y=Array.from(new Set(m));if(y.length<2){A("Need at least 2 unique non-empty cards");return}A(""),o(f,y);};return useInput((f,m)=>{if(i==="editing-cards"){if(m.leftArrow)d(y=>Math.max(0,y-1));else if(m.rightArrow)d(y=>{let v=y+1;return v>=a.length&&a.length<25?(u(k=>[...k,""]),v):Math.min(v,a.length-1)});else if(m.return)R();else if(m.escape)n();else if(m.tab)p("editing-name");else if(m.backspace){let y=a[l]||"";if(y.length===0){if(a.length>2){let v=a.filter((k,h)=>h!==l);u(v),d(Math.max(0,l-1));}}else {let v=[...a];v[l]=y.slice(0,-1),u(v);}}else if(f&&!m.ctrl&&!m.meta){let y=a[l]||"";if(y.length<10){let v=[...a];v[l]=y+f,u(v);}}}},{isActive:i==="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(q,{value:s,onChange:c,onSubmit:E,onCancel:n,placeholder:"Scale name",isActive:i==="editing-name"})]}),i==="editing-cards"&&jsxs(Box,{flexDirection:"column",gap:0,children:[jsx(Text,{children:"Cards:"}),jsx(Box,{gap:1,flexWrap:"wrap",children:a.map((f,m)=>{let y=m===l;return jsx(Text,{inverse:y,...!y&&{color:"cyan"},children:f.length>0?f:"\xB7"},m)})})]}),b&&jsx(Text,{color:"red",children:b}),jsx(Text,{dimColor:true,children:i==="editing-name"?"\u23CE next esc cancel":"\u2190\u2192 navigate typing adds card bsp delete \u23CE save tab name esc cancel"})]})}function Ct({scales:e,selectedScaleId:t,presetOverrides:o,customScales:n=[],onSelect:r,onOverrideChange:s,onCustomScaleCreate:c,onCustomScaleEdit:a,onCustomScaleDelete:u,onClose:l,showNoneOption:d=false}){let[i,p]=useState(0),[b,A]=useState(null),[E,R]=useState("browsing"),[$,f]=useState(null),[m,y]=useState(null),v=c?[...e,{id:"__create-custom__",cards:[]}]:e,k=v.length;if(useInput((h,P)=>{if(E==="browsing"){if(P.escape){l();return}if(k===0)return;if(P.upArrow)p(g=>(g-1+k)%k);else if(P.downArrow)p(g=>(g+1)%k);else if(P.return){let g=v[i];if(!g)return;if(g.id==="__create-custom__"){R("creating");return}g.id in se?A(b===g.id?null:g.id):r(g.id);}else if(h===" "){let g=v[i];g&&g.id!=="__create-custom__"&&r(g.id);}else if(h==="e"||h==="E"){let g=v[i];a&&g?.id.startsWith("custom-")&&(f(g.id),R("editing"));}else if(h==="d"||h==="D"){let g=v[i];u&&g?.id.startsWith("custom-")&&(y(g.id),R("confirming-delete"));}}else E==="confirming-delete"&&(h==="y"||h==="Y"?(m&&u&&u(m),y(null),R("browsing")):(h==="n"||h==="N"||P.escape)&&(y(null),R("browsing")));},{isActive:E==="browsing"&&b===null||E==="confirming-delete"}),E==="creating")return jsx(nn,{onSave:(h,P)=>{c&&c(h,P),R("browsing");},onCancel:()=>R("browsing")});if(E==="editing"&&$){let h=n?.find(g=>g.id===$);if(e.find(g=>g.id===$)&&h)return jsx(nn,{initialName:h.label,initialCards:h.cards,onSave:(g,L)=>{a&&a($,g,L),f(null),R("browsing");},onCancel:()=>{f(null),R("browsing");}})}if(E==="confirming-delete"&&m){let h=v.find(g=>g.id===m),P=h?Ee(h.id,n):"Unknown";return jsxs(Box,{flexDirection:"column",gap:1,children:[jsxs(Text,{children:["Delete ",P,"? "]}),jsx(Text,{children:"[y] yes [n] no esc cancel"})]})}return v.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:v.map((h,P)=>{let g=P===i,L=h.id===t,de=b===h.id,ne=h.id in se,oe=h.id==="__create-custom__",Y=h.cards.join(", "),C=d&&h.id==="",T=C?"(none)":oe?"[+ Create custom scale]":Ee(h.id,n);return jsxs(Box,{flexDirection:"column",children:[jsxs(Box,{gap:1,children:[jsx(Text,{...g&&{color:"cyan"},children:g?">":" "}),jsx(Text,{bold:g,children:T}),!C&&!oe&&jsx(Text,{dimColor:true,children:Y}),L&&jsx(Text,{color:"cyan",children:"[current]"})]}),de&&!C&&!oe&&jsx(Box,{paddingLeft:2,children:ne?jsx(co,{scaleId:h.id,cards:se[h.id]?.cards??[],disabledIndices:o?.[h.id]??[],onOverrideChange:s,onClose:()=>A(null)}):null})]},h.id||"none")})}),jsx(Text,{dimColor:true,children:(()=>{let h=i<v.length?v[i]:null;return h?.id.startsWith("custom-")?`\u2191\u2193 navigate space select${a?" [e] edit":""}${u?" [d] delete":""} esc back`:h&&h.id in se?"\u2191\u2193 navigate \u23CE customize space select esc back":"\u2191\u2193 navigate \u23CE/space select esc back"})()})]})}function ts(e,t){return t?[...e]:e.filter(o=>o.value==="display-name")}function lo({sessionName:e,displayName:t,currentScale:o,accessMode:n,presetOverrides:r,customScales:s,isHost:c=true,onChangeName:a,onChangeScale:u,onChangeDisplayName:l,onChangeAccess:d,onClose:i}){let[p,b]=useState(r??{}),A=useMemo(()=>pt(p,s),[p,s]),[E,R]=useState("main-menu"),[$,f]=useState(e??""),[m,y]=useState(t),[v,k]=useState(n==="password"?"password":"open"),[h,P]=useState(""),[g,L]=useState(false),de=[{label:"Room name",value:"room-name",detail:`[${e??"(none)"}]`},{label:"Scale",value:"scale",detail:`[${Ee(o.id,s)}]`},{label:"Access",value:"access",detail:`[${n==="open"?"Open":"Password"}]`},{label:"Display name",value:"display-name",detail:`[${t}]`}],ne=ts(de,c);function oe(O){!c&&O!=="display-name"||(O==="room-name"?(f(e??""),R("editing-room-name")):O==="scale"?R("picking-scale"):O==="access"?R("picking-access"):O==="display-name"&&(y(t),R("editing-display-name")));}function Y(O){let B=O.trim()===""?void 0:O.trim();a(B),R("main-menu");}function C(O){let B=O.trim();B.length>0&&(l(B),process.nextTick(()=>J({displayName:B}))),R("main-menu");}function T(O){let B=A.find(z=>z.id===O);B&&u(B),R("main-menu");}function D(O,B){let z={...p};B.length===0?delete z[O]:z[O]=B,b(z),J({presetOverrides:Object.keys(z).length>0?z:void 0});}async function j(O,B){L(true);try{await d(O,B),k(O),P(O==="password"?B??"":"");}catch(z){console.error(`Failed to update access: ${z}`);}finally{L(false),R("main-menu");}}return E==="main-menu"?jsx(me,{title:"\u2500\u2500 Settings \u2500\u2500",items:ne,onSelect:oe,onClose:i}):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(q,{value:$,onChange:f,onSubmit:Y,onCancel:()=>R("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(q,{value:m,onChange:y,onSubmit:C,onCancel:()=>R("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(Ct,{scales:A,selectedScaleId:o.id,presetOverrides:p,onSelect:T,onOverrideChange:D,onClose:()=>R("main-menu")})]}):E==="picking-access"?jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:"\u2500\u2500 Settings \u2500\u2500"}),g&&jsx(Text,{dimColor:true,children:"Updating access..."}),!g&&jsx(so,{mode:v,password:h,onModeChange:O=>{O==="open"&&j("open");},onPasswordChange:P,onClose:()=>R("main-menu")})]}):jsx(Box,{})}function It({participants:e,onlineIds:t,hostId:o,votes:n,revealed:r,participantId:s}){return jsxs(Box,{flexDirection:"column",children:[jsxs(Text,{bold:true,children:["Participants (",e.length,")"]}),e.map(c=>{let a=t.has(c.id),u=a?"\u25CF":"\u25CB",l=a?"green":"gray",d=c.id===o,i=Ie(c)==="viewer",p=c.id===s,b=c.id in n,A=i?" (viewer)":r?n[c.id]?` \u2192 ${n[c.id]}`:"":b?" \u2713":" \xB7",E=p&&!i&&!(r&&n[c.id]);return jsxs(Box,{gap:1,children:[jsx(Text,{color:l,children:u}),jsxs(Text,{children:[c.displayName,d?" (host)":""]}),E?jsx(Text,{color:"cyan",children:"(voter)"}):jsx(Text,{color:i?"gray":b?"green":"gray",children:A})]},c.id)})]})}var ns=[{label:"End session for everyone",value:"end-session",detail:"Disconnects all participants. Cannot be undone."}];function po({onPick:e,onClose:t}){return jsx(me,{title:"Host Actions",items:ns,onSelect:e,onClose:t})}function Tt({roomCode:e,onPick:t,onClose:o}){let n=[{label:"Copy code",value:"code",detail:St(e)},{label:"Copy share link",value:"url",detail:xt(e)},{label:"Copy npx command",value:"npx",detail:wt(e)}];return jsx(me,{title:"Share",items:n,onSelect:r=>t(r),onClose:o})}function ss(e){let t=e.rounds.filter(n=>W(n)!==null),o=Object.fromEntries(e.participants.map(n=>[n.id,n]));return _n({rounds:t,participants:o})}async function fo(e,t,o){let n=ss(e);try{await t(n),o("Results copied to clipboard");}catch{o("Clipboard write failed");}}function mo(e,t,o){let n=kn(e.id,o);return n===null?`Round ${t}`:`Round ${n.depth}/${n.total} [re-vote]`}function go(e,t,o){let n=mo(e,t,o),r=e.title?` \u2014 ${e.title}`:"",s=e.ticketId?` [${e.ticketId}]`:"",c=W(e)==="skipped"?" \u2014 Skipped":" \u2014 Results";return `${n}${r}${s}${c}`}function ho(e){let t=Object.values(e).filter(d=>!pe(d)).map(d=>parseFloat(d)).filter(d=>!isNaN(d)).toSorted((d,i)=>d-i);if(t.length===0)return {average:"-",median:"-",agreement:"-"};let n=t.reduce((d,i)=>d+i,0)/t.length,r=Math.floor(t.length/2),s=t.length%2===0?(t[r-1]+t[r])/2:t[r],c=Object.values(e).filter(d=>!pe(d)),a=new Map;for(let d of c)a.set(d,(a.get(d)??0)+1);let u=Math.max(...a.values()),l=c.length>0?Math.round(u/c.length*100):0;return {average:n.toFixed(1),median:s.toFixed(1),agreement:`${l}%`}}function rn(e,t){let o=Math.floor((t-e)/1e3),n=Math.floor(o/60),r=o%60;return `${n}:${r.toString().padStart(2,"0")}`}function So(e,t,o=[]){let n=mo(e,t,o);if(W(e)==="skipped"){let i=e.title?` ${e.title}`:"",p=e.ticketId?` [${e.ticketId}]`:"";return `${n}:${i}${p} \u2014 Skipped`}let r=new Map;for(let i of Object.values(e.votes))pe(i)||r.set(i,(r.get(i)??0)+1);let s=[...r.entries()].toSorted((i,p)=>p[1]-i[1]||i[0].localeCompare(p[0])).map(([i,p])=>`${i}\xD7${p}`).join(", "),c=Object.values(e.votes).filter(pe).length,a=c>0?` (${c} abstained)`:"",u=e.title?` ${e.title}`:"",l=e.ticketId?` [${e.ticketId}]`:"",d=e.timerStartedAt!==void 0&&e.revealedAt!==void 0?` \u2014 ${rn(e.timerStartedAt,e.revealedAt)}`:"";return `${n}:${u}${l} \u2014 ${s||"(no votes)"}${a}${d}`}function xo({projection:e,onlineIds:t,isHost:o,participantId:n,rounds:r,currentRound:s,settingsOpen:c,onStartRound:a,onNewRound:u,onRevote:l,onOpenSettings:d,onHandoffHost:i,onEndSession:p,onShare:b,shareOpen:A,onSetShareOpen:E,roomCode:R,confirmingQuit:$,myRole:f,onToggleRole:m,roundTitlePromptActive:y,roundTitleInput:v,onRoundTitleChange:k,onRoundTitleSubmit:h,onRoundTitleCancel:P,roundTicketIdPromptActive:g,roundTicketIdInput:L,onRoundTicketIdChange:de,onRoundTicketIdSubmit:ne,onRoundTicketIdCancel:oe,descriptorPromptActive:Y,descriptorInput:C,onDescriptorChange:T,onDescriptorSubmit:D,onDescriptorCancel:j,onEditDescriptor:O,canExport:B,onExportResults:z}){let[X,re]=Et.useState("idle"),[xe,ve]=Et.useState(null),[fe,le]=Et.useState(false),[Ce,be]=Et.useState(false);useEffect(()=>{o||(re("idle"),ve(null),le(false),be(false));},[o]),useInput(w=>{if(w==="c"||w==="C"){E(true);return}if(w==="w"||w==="W"){m();return}if((w==="x"||w==="X")&&B){z();return}o&&((w==="e"||w==="E")&&d(),(w==="s"||w==="S")&&a(),(w==="h"||w==="H")&&re("picking"),(w==="m"||w==="M")&&le(true),s!==null&&W(s)!==null&&((w==="n"||w==="N")&&u(),(w==="v"||w==="V")&&W(s)!=="skipped"&&l(),(w==="t"||w==="T")&&O()));},{isActive:!c&&!$&&X==="idle"&&!y&&!g&&!fe&&!Ce&&!A&&!Y}),useInput((w,Z)=>{w==="y"||w==="Y"?(p(),be(false)):(w==="n"||w==="N"||Z.escape)&&be(false);},{isActive:Ce});let ue=e.participants.filter(w=>w.id!==n&&t.has(w.id));useInput(w=>{if(X==="picking"){let Z=parseInt(w,10);!isNaN(Z)&&Z>=1&&Z<=ue.length?(ve(ue[Z-1].id),re("confirming")):(w==="Escape"||w==="q"||w==="Q")&&re("idle");}},{isActive:X==="picking"}),useInput(w=>{X==="confirming"&&xe&&(w==="y"||w==="Y"?(i(xe),re("idle"),ve(null)):(w==="n"||w==="N"||w==="Escape")&&(re("idle"),ve(null)));},{isActive:X==="confirming"});let et=r.length>0,je=r.filter(w=>W(w)!==null),tt=s!==null?je.slice(0,-1):je;return fe?jsx(po,{onPick:w=>{le(false),w==="end-session"&&be(true);},onClose:()=>le(false)}):A?jsx(Tt,{roomCode:R,onPick:w=>{E(false),b(w);},onClose:()=>E(false)}):jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:et?"Session":"Waiting for participants..."}),!et&&jsx(Text,{dimColor:true,children:"No rounds started yet."}),jsx(It,{participants:e.participants,onlineIds:t,hostId:e.settings.hostParticipantId,votes:{},revealed:false,participantId:n}),s!==null&&W(s)!==null&&jsx(as,{projection:e,round:s,rounds:r}),tt.length>0&&jsxs(Box,{flexDirection:"column",children:[jsx(Text,{dimColor:true,children:"\u2500\u2500 History \u2500\u2500"}),tt.map(w=>{let Z=r.indexOf(w)+1;return jsx(Text,{dimColor:true,children:So(w,Z,r)},w.id)})]}),X==="picking"&&ue.length>0&&jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{children:"Hand off to:"}),ue.map((w,Z)=>jsxs(Text,{children:["[",Z+1,"] ",w.displayName]},w.id)),jsx(Text,{dimColor:true,children:"[q] Cancel"})]}),X==="picking"&&ue.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"})]}),X==="confirming"&&xe&&jsx(Box,{flexDirection:"column",children:jsxs(Text,{children:["Hand off to ",e.participants.find(w=>w.id===xe)?.displayName,"? [y/n]"]})}),y&&jsxs(Box,{flexDirection:"column",children:[jsxs(Box,{gap:1,children:[jsx(Text,{children:"Round title (optional, Enter to skip):"}),jsx(q,{value:v,onChange:k,onSubmit:h,onCancel:P,placeholder:"Enter to skip",isActive:y})]}),jsx(Text,{dimColor:true,children:"[Esc] Cancel"})]}),g&&jsxs(Box,{flexDirection:"column",children:[jsxs(Box,{gap:1,children:[jsx(Text,{children:"Ticket ID (optional, Enter to skip):"}),jsx(q,{value:L,onChange:de,onSubmit:ne,onCancel:oe,placeholder:"e.g. PROJ-1234",isActive:g})]}),jsx(Text,{dimColor:true,children:"[Esc] Cancel"})]}),Y&&jsxs(Box,{flexDirection:"column",children:[jsxs(Box,{gap:1,children:[jsx(Text,{children:"Edit round title:"}),jsx(q,{value:C,onChange:T,onSubmit:D,onCancel:j,placeholder:"Enter to save",isActive:Y})]}),jsx(Text,{dimColor:true,children:"[Esc] Cancel \xB7 empty submit cancels"})]}),Ce&&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"})]}),X==="idle"&&!y&&!g&&!Y&&!Ce&&jsxs(Box,{gap:2,children:[o?jsx(Fragment,{children:s!==null&&W(s)!==null?jsxs(Fragment,{children:[W(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"}),B&&jsx(Text,{color:"yellow",children:"[x] Export"}),jsxs(Text,{color:"yellow",children:["[w] ",f==="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"}),B&&jsx(Text,{color:"yellow",children:"[x] Export"}),jsxs(Text,{color:"yellow",children:["[w] ",f==="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"}),B&&jsx(Text,{color:"yellow",children:"[x] Export"}),jsxs(Text,{color:"yellow",children:["[w] ",f==="viewer"?"Vote":"Observe"]})]}),jsx(Text,{children:"[q] Quit"})]})]})}function as({projection:e,round:t,rounds:o}){let n=o.indexOf(t)+1,r=go(t,n,o);if(W(t)==="skipped")return jsx(Box,{flexDirection:"column",gap:1,children:jsx(Text,{bold:true,children:r})});let s=ho(t.votes),c=new Map(e.participants.map(p=>[p.id,p.displayName])),a=t.timerStartedAt!==void 0&&t.revealedAt!==void 0?rn(t.timerStartedAt,t.revealedAt):null,u=Object.entries(t.votes),l=u.filter(([,p])=>!pe(p)),d=u.filter(([,p])=>pe(p)).map(([p])=>p),i=l[0]?.[1];return jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:r}),Nn(t)&&i!==void 0&&jsxs(Text,{bold:true,color:"green",children:["Consensus! Everyone voted ",i]}),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)})]}),l.map(([p,b])=>jsxs(Box,{gap:2,children:[jsx(Text,{children:(c.get(p)??p).padEnd(20)}),jsx(Text,{color:"cyan",bold:true,children:b.padEnd(10)})]},p)),d.map(p=>jsxs(Box,{gap:2,children:[jsx(Text,{dimColor:true,children:(c.get(p)??p).padEnd(20)}),jsx(Text,{dimColor:true,children:"(abstained)".padEnd(10)})]},p)),e.participants.filter(p=>!(p.id in t.votes)).map(p=>jsxs(Box,{gap:2,children:[jsx(Text,{dimColor:true,children:p.displayName.padEnd(20)}),jsx(Text,{dimColor:true,children:"(no vote)".padEnd(10)})]},p.id))]}),jsxs(Box,{gap:2,children:[jsxs(Text,{children:["Avg: ",jsx(Text,{bold:true,children:s.average})]}),jsxs(Text,{children:["Med: ",jsx(Text,{bold:true,children:s.median})]}),jsxs(Text,{children:["Agreement: ",jsx(Text,{bold:true,children:s.agreement})]}),a!==null&&jsxs(Text,{dimColor:true,children:["\u23F1 ",a]})]})]})}function vo({scale:e,selected:t,onSelect:o,onClear:n,disabled:r}){let[s,c]=useState(0);return useInput((a,u)=>{if(!r)if(u.leftArrow)c(l=>Math.max(0,l-1));else if(u.rightArrow)c(l=>Math.min(e.cards.length-1,l+1));else if(u.return){let l=e.cards[s];l===t?n():o(l);}else (u.backspace||u.delete)&&n();}),jsxs(Box,{flexDirection:"column",gap:0,children:[jsx(Box,{gap:1,children:e.cards.map((a,u)=>{let l=u===s,d=a===t;return jsx(Text,{inverse:l,bold:d,...d&&{color:"cyan"},children:d?`[${a}]`:` ${a} `},a)})}),jsx(Text,{dimColor:true,children:" \u2190\u2192 navigate \u23CE select \u232B clear"})]})}function ps(e){let t=Math.floor(e/1e3),o=Math.floor(t/60),n=t%60;return `${o}:${n.toString().padStart(2,"0")}`}function Co({projection:e,round:t,onlineIds:o,isHost:n,myId:r,myRole:s,elapsedMs:c,roomCode:a,shareOpen:u,settingsOpen:l,onVote:d,onClearVote:i,onReveal:p,onSkip:b,onToggleRole:A,onShare:E,onSetShareOpen:R,onOpenSettings:$,confirmingQuit:f}){let m=t.votes[r],y=m!==void 0&&pe(m);if(useInput(g=>{if(g==="e"||g==="E"){$();return}if(g==="c"||g==="C"){R(true);return}if(s==="voter"&&(g==="a"||g==="A")){y?i():d(rt);return}n&&(g==="r"||g==="R")&&p(),n&&(g==="s"||g==="S")&&b(),(g==="w"||g==="W")&&A();},{isActive:!f&&!l&&!u}),u)return jsx(Tt,{roomCode:a,onPick:g=>{R(false),E(g);},onClose:()=>R(false)});let v=m,k=e.rounds.indexOf(t)+1,h=t.title?` \u2014 ${t.title}`:"",P=t.ticketId?` [${t.ticketId}]`:"";return jsxs(Box,{flexDirection:"column",gap:1,children:[jsxs(Box,{gap:2,children:[jsxs(Text,{bold:true,children:["Round ",k,h,P]}),c!==void 0&&jsxs(Text,{dimColor:true,children:["\u23F1 ",ps(c)]})]}),jsxs(Box,{gap:4,children:[jsx(It,{participants:e.participants,onlineIds:o,hostId:e.settings.hostParticipantId,votes:t.votes,revealed:false}),s==="viewer"?jsx(Box,{flexDirection:"column",children:jsx(Text,{dimColor:true,children:"You're observing"})}):jsxs(Box,{flexDirection:"column",gap:1,children:[y&&jsx(Text,{dimColor:true,children:"(abstaining this round \u2014 press [a] to vote)"}),jsx(vo,{scale:t.scale,selected:y?void 0:v,onSelect:d,onClear:i,disabled:f||y})]})]}),jsxs(Box,{gap:2,children:[n&&jsx(Text,{color:"yellow",children:"[r] Reveal"}),n&&jsx(Text,{color:"yellow",children:"[s] Skip"}),s==="voter"&&jsxs(Text,{color:"yellow",children:["[a] ",y?"Vote":"Abstain"]}),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 hs=3e3;function Pt(e=hs){let[t,o]=useState(null),n=useRef(null),r=useCallback(()=>{n.current!==null&&(clearTimeout(n.current),n.current=null);},[]),s=useCallback(a=>{r(),o(a),n.current=setTimeout(()=>{o(null),n.current=null;},e);},[r,e]),c=useCallback(()=>{r(),o(null);},[r]);return useEffect(()=>()=>{r();},[r]),{notice:t,show:s,dismiss:c}}function ws(e){let t=ee(e);return !t||W(t)!==null?"lobby":"voting"}function kt({session:e,participantId:t,displayName:o,accessMode:n,presetOverrides:r,customScales:s}){let{exit:c}=useApp(),[a,u]=useState(e.projection()),[l,d]=useState("connecting"),[i,p]=useState(e.onlineIds()),[b,A]=useState(),[E,R]=useState(false),[$,f]=useState(n==="password"?"password":"open"),[m,y]=useState(o),[v,k]=useState(false),[h,P]=useState(false),[g,L]=useState(""),[de,ne]=useState(false),[oe,Y]=useState(""),[C,T]=useState(),[D,j]=useState(false),[O,B]=useState(""),[z,X]=useState(null),re=Pt(3e3),[xe,ve]=useState(false),fe=Pt(3e3),le=Pt(3e3);useEffect(()=>{let I=e.onChange(()=>{u(e.projection()),p(e.onlineIds());}),_=e.onStatusChange(d),Oe=e.onHostChange(nt=>{let Qo=e.projection().participants.find(Zo=>Zo.id===nt),Ko=nt===t?"You are now the host":`${Qo?.displayName||"Unknown"} is now the host`;re.show(Ko);}),Xo=setInterval(()=>{p(e.onlineIds());let nt=e.projection(),ot=ee(nt);ot?.timerStartedAt!==void 0&&ot.revealedAt===void 0?A(Date.now()-ot.timerStartedAt):A(void 0);},1e3);return ()=>{I(),_(),Oe(),clearInterval(Xo);}},[e,t,re.show]),useInput(I=>{(I==="q"||I==="Q")&&k(true);},{isActive:!E&&!v&&!h&&!de&&!D&&!xe}),useInput((I,_)=>{I==="y"||I==="Y"?(e.disconnect(),c()):(I==="n"||I==="N"||_.escape)&&k(false);},{isActive:v});let Ce=ws(a),be=ee(a),ue=a.settings.hostParticipantId===t,et=a.participants.find(I=>I.id===t),je=Ie(et??{}),tt=useCallback(()=>{L(""),P(true);},[]),w=useCallback(I=>{P(false),L(""),T(Gt(I)),Y(""),ne(true);},[]),Z=useCallback(()=>{P(false),L(""),T(void 0);},[]),No=useCallback(I=>{ne(false),Y("");let[_,Oe]=Kn(C,I);e.newRound(_,Oe),T(void 0);},[e,C]),Po=useCallback(()=>{ne(false),Y(""),T(void 0);},[]),Do=useCallback(()=>{let I=e.current();!I||I.revealedAt===void 0||(X(I.id),B(I.title??""),j(true));},[e]),ko=useCallback(I=>{let _=z;if(j(false),X(null),!_)return;let Oe=I.trim();Oe!==""&&e.updateRoundDescriptor(_,{title:Oe});},[e,z]),Oo=useCallback(()=>{j(false),X(null);},[]),Mo=useCallback(I=>{e.vote(I);},[e]),$o=useCallback(()=>{e.unvote();},[e]),Bo=useCallback(()=>{e.reveal();},[e]),Ho=useCallback(()=>{e.skip();},[e]),Uo=useCallback(()=>{L(""),P(true);},[]),Lo=useCallback(()=>{e.revote();},[e]),ln=useCallback(()=>{e.toggleRole();},[e]),jo=useCallback(I=>{e.updateName(I);},[e]),_o=useCallback(I=>{e.updateScale(I);},[e]),Vo=useCallback(I=>{e.updateDisplayName(I),y(I);},[e]),Fo=useCallback(async(I,_)=>{await e.updateAccess(I,_),f(I);},[e]),Yo=useCallback(I=>{try{e.handoffHost(I);}catch(_){console.error(`Handoff failed: ${_ instanceof Error?_.message:String(_)}`);}},[e]),un=useCallback(()=>{R(true);},[]),Jo=useCallback(()=>{R(false);},[]),Wo=useCallback(()=>{e.endSessionForEveryone();},[e]),pn=useCallback(async I=>{let _=I==="code"?St(e.roomCode):I==="url"?xt(e.roomCode):wt(e.roomCode);try{await Ro.write(_),fe.show("Copied!");}catch{fe.show(`Copy failed \u2014 ${_}`);}},[e.roomCode,fe]),$t=a.rounds.filter(I=>W(I)!==null),qo=$t.length>0?$t.at(-1):null,Go=$t.length>0,zo=useCallback(()=>fo(a,I=>Ro.write(I),le.show),[a,le.show]);return jsxs(Box,{flexDirection:"column",children:[jsx(to,{roomCode:e.roomCode,sessionName:a.settings.name,status:l,isHost:ue}),re.notice&&jsx(Box,{paddingX:1,children:jsx(Text,{color:"yellow",children:re.notice})}),fe.notice&&jsx(Box,{paddingX:1,children:jsx(Text,{color:fe.notice.startsWith("Copy failed")?"red":"green",children:fe.notice})}),le.notice&&jsx(Box,{paddingX:1,children:jsx(Text,{color:le.notice.startsWith("Clipboard write failed")?"red":"green",children:le.notice})}),E?jsx(lo,{sessionName:a.settings.name,displayName:m,currentScale:a.settings.defaultScale,accessMode:$,presetOverrides:r,customScales:s,isHost:ue,onChangeName:jo,onChangeScale:_o,onChangeDisplayName:Vo,onChangeAccess:Fo,onClose:Jo}):Ce==="lobby"?jsx(xo,{projection:a,onlineIds:i,isHost:ue,participantId:t,rounds:a.rounds,currentRound:qo,settingsOpen:E,onStartRound:tt,onNewRound:Uo,roundTitlePromptActive:h,roundTitleInput:g,onRoundTitleChange:L,onRoundTitleSubmit:w,onRoundTitleCancel:Z,roundTicketIdPromptActive:de,roundTicketIdInput:oe,onRoundTicketIdChange:Y,onRoundTicketIdSubmit:No,onRoundTicketIdCancel:Po,onRevote:Lo,onOpenSettings:un,onHandoffHost:Yo,onEndSession:Wo,onShare:pn,shareOpen:xe,onSetShareOpen:ve,roomCode:e.roomCode,confirmingQuit:v,myRole:je,onToggleRole:ln,descriptorPromptActive:D,descriptorInput:O,onDescriptorChange:B,onDescriptorSubmit:ko,onDescriptorCancel:Oo,onEditDescriptor:Do,canExport:Go,onExportResults:zo}):Ce==="voting"&&be?jsx(Co,{projection:a,round:be,onlineIds:i,isHost:ue,myId:t,myRole:je,elapsedMs:b,onVote:Mo,onClearVote:$o,onReveal:Bo,onSkip:Ho,onToggleRole:ln,onShare:pn,onSetShareOpen:ve,shareOpen:xe,settingsOpen:E,onOpenSettings:un,roomCode:e.roomCode,confirmingQuit:v}):null,v&&jsx(Box,{children:jsx(Text,{dimColor:true,children:"Leave session? y/n"})})]})}function Io({presetOverrides:e,customScales:t}){let{exit:o}=useApp(),[n,r]=useState("main-menu"),[s,c]=useState(Re()),[a,u]=useState(s.displayName??""),[l,d]=useState(s.relayUrl??""),[i,p]=useState(s.defaultSessionName??""),[b,A]=useState(t??[]),[E,R]=useState(e??{});useInput(C=>{(C==="q"||C==="Q")&&o();},{isActive:n==="main-menu"});let $=useMemo(()=>pt(E,b),[E,b]),f=s.defaultScale,m=f?$.find(C=>C.id===f):void 0,y=[{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?Ee(m.id,b):"(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 v(C){C==="display-name"?(u(s.displayName??""),r("editing-display-name")):C==="relay-url"?(d(s.relayUrl??""),r("editing-relay-url")):C==="default-scale"?r("picking-scale"):C==="default-session-name"?(p(s.defaultSessionName??""),r("editing-session-name")):C==="default-password"&&r("picking-default-password");}function k(C){let T=C.trim();if(T.length>0){let D={...s,displayName:T};c(D),J({displayName:T});}r("main-menu");}function h(C){let T=C.trim();if(T.length>0){let D={...s,relayUrl:T};c(D),J({relayUrl:T});}r("main-menu");}function P(C){let T=C.trim(),D=T.length>0?{...s,defaultSessionName:T}:{...s};T.length===0?(delete D.defaultSessionName,c(D),Fe({defaultSessionName:""})):(c(D),J({defaultSessionName:T})),r("main-menu");}function g(C){let T=C.length>0?{...s,defaultScale:C}:{...s};C.length===0?(delete T.defaultScale,c(T),Fe({defaultScale:""})):(c(T),J({defaultScale:C})),r("main-menu");}function L(C,T){let D={...E};T.length===0?delete D[C]:D[C]=T,R(D),J({presetOverrides:Object.keys(D).length>0?D:void 0});}function de(C){let T=C==="on",D={...s,defaultPassword:T};c(D),J({defaultPassword:T}),r("main-menu");}function ne(C,T){let D=`custom-${randomUUID()}`,j=[...b,{id:D,label:C,cards:T}];A(j),c({...s,customScales:j}),J({customScales:j});}function oe(C,T,D){let j=b.map(O=>O.id===C?{id:C,label:T,cards:D}:O);A(j),c({...s,customScales:j}),J({customScales:j});}function Y(C){let T=b.filter(j=>j.id!==C);A(T);let D={...s,customScales:T};s.defaultScale===C?(D.defaultScale="fibonacci",J({customScales:T,defaultScale:"fibonacci"})):J({customScales:T}),c(D);}if(n==="main-menu")return jsx(me,{title:"\u2500\u2500 Config Settings \u2500\u2500",items:y,onSelect:v,onClose:()=>o()});if(n==="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(q,{value:a,onChange:u,onSubmit:k,onCancel:()=>r("main-menu"),placeholder:"(required)",isActive:true}),jsx(Text,{dimColor:true,children:"\u23CE confirm esc back"})]});if(n==="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(q,{value:l,onChange:d,onSubmit:h,onCancel:()=>r("main-menu"),placeholder:"(required)",isActive:true}),jsx(Text,{dimColor:true,children:"\u23CE confirm esc back"})]});if(n==="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(q,{value:i,onChange:p,onSubmit:P,onCancel:()=>r("main-menu"),placeholder:"(leave empty to clear)",isActive:true}),jsx(Text,{dimColor:true,children:"\u23CE confirm esc back"})]});if(n==="picking-scale"){let C=[{id:"",cards:[]},...$];return jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:"\u2500\u2500 Config Settings \u2500\u2500"}),jsx(Ct,{scales:C,selectedScaleId:f??"",presetOverrides:E,customScales:b,onSelect:g,onOverrideChange:L,onCustomScaleCreate:ne,onCustomScaleEdit:oe,onCustomScaleDelete:Y,onClose:()=>r("main-menu"),showNoneOption:true})]})}if(n==="picking-default-password"){let C=[{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(me,{title:"Default password:",items:C,onSelect:de,onClose:()=>r("main-menu")})]})}return jsx(Box,{})}async function dn(){return new Promise(e=>{let t=createInterface({input:process.stdin,output:process.stderr});process.stderr.write("Password: "),t.question("",o=>{t.close(),e(o);});})}async function As(e){let t=await Ve(),o=e.scale??t.defaultScale??"fibonacci",n=e.name??t.defaultSessionName,r=e.password??t.defaultPassword??false,s;r&&(s=await dn());let c=ut(o,t.presetOverrides,t.customScales);if(!c){let i=Object.keys(se).join(", ");console.error(`Unknown or fully-disabled scale: "${o}". Builtin IDs: ${i}`),process.exit(1);}let a=await qn({participantId:t.participantId,displayName:t.displayName,relayUrl:t.relayUrl,sessionName:n,password:s,defaultScale:c}),u;s&&(u=await Te(s,a.roomUuid)),mt({roomCode:a.roomCode,roomUuid:a.roomUuid,passwordHash:u,lastSeenAt:Date.now()}),console.error(`Room created: ${a.roomCode}`),console.error(`Share: hnch join ${a.roomCode}`),console.error("");let{waitUntilExit:l,unmount:d}=render(Et.createElement(kt,{session:a,participantId:t.participantId,displayName:t.displayName,accessMode:s?"password":"open",presetOverrides:t.presetOverrides,customScales:t.customScales}));ht({doc:a.doc,localParticipantId:t.participantId,onEnded:async i=>{console.error(i.isLocal?"Session ended.":`Session ended by ${i.hostName}.`),d();try{await V();}catch(p){console.error(`Failed to clear cached session: ${p instanceof Error?p.message:String(p)}`);}a.disconnect(),process.exit(0);}}),await l(),a.disconnect(),V(),process.exit(0);}async function Ns(e){let t=await Ve(),o=zn(e.code);o===null&&(console.error("invalid room code"),process.exit(1));let n;e.password&&(n=await dn());let r=await We(t.relayUrl,o),s;r.accessMode==="password"&&!e.password?s=await dn():e.password&&(s=n);let c,a=false,u=await Gn({participantId:t.participantId,displayName:t.displayName,relayUrl:t.relayUrl,roomCode:o,password:s,onRejection(i){if(i==="unknown-room"){gt({clearLastSession:V,logger:console,exit:process.exit});return}i==="room-full"&&(a=true,c?.());}}),l;s&&(l=await Te(s,u.roomUuid)),mt({roomCode:u.roomCode,roomUuid:u.roomUuid,passwordHash:l,lastSeenAt:Date.now()}),console.error(`Joined room: ${u.roomCode}`),console.error("");let d=render(Et.createElement(kt,{session:u,participantId:t.participantId,displayName:t.displayName,accessMode:r.accessMode,presetOverrides:t.presetOverrides,customScales:t.customScales}));c=d.unmount,ht({doc:u.doc,localParticipantId:t.participantId,onEnded:async i=>{console.error(i.isLocal?"Session ended.":`Session ended by ${i.hostName}.`),c?.();try{await V();}catch(p){console.error(`Failed to clear cached session: ${p instanceof Error?p.message:String(p)}`);}u.disconnect(),process.exit(0);}}),await d.waitUntilExit(),a&&(console.error("This room is full (max 20 participants)."),u.disconnect(),V(),process.exit(1)),u.disconnect(),V(),process.exit(0);}async function Ps(){let e=await Ve(),t=Qn();t||(console.error("No recent session to resume."),process.exit(0));let o;try{o=await We(e.relayUrl,t.roomCode);}catch(d){if((d instanceof Error?d.message:String(d)).includes("Room not found"))return await gt({clearLastSession:V,logger:console,exit:process.exit});console.error("Could not reach relay \u2014 try again later."),process.exit(0);}o.accessMode==="password"&&!t.passwordHash&&(console.error(`Password changed \u2014 rejoin with: hnch join ${t.roomCode} --password`),V(),process.exit(0)),console.error(`Rejoining room ${t.roomCode}\u2026`),console.error("");let n=new Me.Doc,r=new Awareness(n);lt(n,{participantId:e.participantId,displayName:e.displayName}),r.setLocalStateField("participantId",e.participantId);let s=false,c=false,a,u=ft({doc:n,awareness:r,roomCode:t.roomCode,roomUuid:t.roomUuid,participantId:e.participantId,relayUrl:e.relayUrl,passwordHash:t.passwordHash,onRejection(d){if(d==="unknown-room"){gt({clearLastSession:V,logger:console,exit:process.exit});return}d==="wrong-password"&&(s=true,a?.()),d==="room-full"&&(c=true,a?.());}});mt({roomCode:u.roomCode,roomUuid:u.roomUuid,passwordHash:t.passwordHash,lastSeenAt:Date.now()});let l=render(Et.createElement(kt,{session:u,participantId:e.participantId,displayName:e.displayName,accessMode:o.accessMode,presetOverrides:e.presetOverrides,customScales:e.customScales}));a=l.unmount,ht({doc:u.doc,localParticipantId:e.participantId,onEnded:async d=>{console.error(d.isLocal?"Session ended.":`Session ended by ${d.hostName}.`),a?.();try{await V();}catch(i){console.error(`Failed to clear cached session: ${i instanceof Error?i.message:String(i)}`);}u.disconnect(),process.exit(0);}}),await l.waitUntilExit(),c&&(console.error("This room is full (max 20 participants)."),V(),process.exit(1)),s&&(console.error(`Password changed \u2014 rejoin with: hnch join ${t.roomCode} --password`),V(),process.exit(0)),u.disconnect(),V(),process.exit(0);}async function Ds(e){if(!(e.name!==void 0||e.relayUrl!==void 0||e.scale!==void 0||e.sessionName!==void 0||e.password!==void 0)){let n=await Ve(),{waitUntilExit:r}=render(Et.createElement(Io,{presetOverrides:n.presetOverrides,customScales:n.customScales}));await r(),process.exit(0);}let o={};if(e.name!==void 0&&(o.displayName=e.name),e.relayUrl!==void 0&&(o.relayUrl=e.relayUrl),e.scale!==void 0){if(e.scale!==""){let n=Re();if(!ut(e.scale,n.presetOverrides,n.customScales)){console.error(`Unknown scale: "${e.scale}". Builtin IDs: ${Object.keys(se).join(", ")}`);return}}o.defaultScale=e.scale;}e.sessionName!==void 0&&(o.defaultSessionName=e.sessionName),e.password!==void 0&&(o.defaultPassword=e.password),Fe(o),console.log("Config updated.");}var Ao=Is(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=>{As(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=>{Ns(e).catch(t=>{console.error(t instanceof Error?t.message:t),process.exit(1);});}).command("resume","Rejoin your last session",e=>e,()=>{Ps().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=>{Ds(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(),cn=hideBin(process.argv);(cn.length===0||cn.length===1&&cn[0]==="--")&&(Ao.showHelp(),process.exit(0));Ao.parse();
|