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