2026-11-07 17:46:39 +00:00
const hier = { chart : null , sig : "" , closed : new Set (), ids : 0 };
const OFFICE_ICONS = { chair : "bi-award" , line : "bi-person-badge" , cluster : "bi-collection" , work : "bi-briefcase" , other : "bi-signpost-split" };
2026-11-08 17:26:51 +00:00
const OFFICE_GROUPS = [[ "cluster" , "Organizers" , "Organizer" ], [ "work" , "Presidents" , "President" ], [ "other" , "Other offices" , "Office" ]];
2026-11-07 17:46:39 +00:00
function hierarchyScreen () {
hier . chart = h ( "section" , { class : "card org-chart" , "aria-labelledby" : "org-title" });
orderDom . box = h ( "section" , { class : "card agent-order" , id : "agent-order" , "aria-labelledby" : "agent-order-title" });
hier . sig = "" ;
return h ( "div" , { class : "hierarchy-body" }, hier . chart , orderDom . box );
}
function hierarchySig () {
const held = state . claims . map (( claim ) => [ claim . office , claim . session , claim . agent , claim . work , claim . beat !== null && claim . beat >= state . now - ACTIVE_BEAT ]);
2026-11-11 07:44:02 +00:00
return JSON . stringify ([ held , state . agent_order , state . people , state . presence , [... look . hidden ]]);
2026-11-07 17:46:39 +00:00
}
function drawHierarchy ( force ) {
if ( ! hier . chart || ! hier . chart . isConnected ) {
hier . chart = null ;
return ;
}
const sig = hierarchySig ();
if ( ! force && sig === hier . sig ) {
return ;
}
hier . sig = sig ;
drawChart ();
drawAgentOrder ();
}
function focusKey ( box ) {
const at = document . activeElement ;
return at && box . contains ( at ) ? at . dataset . focus || null : null ;
}
function refocus ( box , key ) {
const found = key === null ? null : [... box . querySelectorAll ( "[data-focus]" )]. find (( element ) => element . dataset . focus === key );
if ( found ) {
found . focus ();
}
}
function officeBtn ( name , label , hint , run , focus ) {
const button = iconBtn ( name , label , "btn small ghost" , run , hint );
button . dataset . focus = focus ;
return button ;
}
function holderOf ( claim ) {
return claim . agent || ( claim . session === "-" ? "" : claim . session );
}
function officeRow ( claim , label , title ) {
const who = holderOf ( claim );
const active = claim . beat !== null && claim . beat >= state . now - ACTIVE_BEAT ;
return h ( "div" , { class : "org-row" , "data-office" : claim . office },
h ( "span" , { class : "org-office" , title : ` ${ title } ${ claim . office } ` }, icon ( OFFICE_ICONS [ claim . kind ]), h ( "strong" , {}, label )),
2026-11-08 21:18:55 +00:00
who ? h ( "button" , { type : "button" , class : "linkish org-holder" , "data-focus" : `agent: ${ claim . office } ` , title : `Open ${ who } ` , on : { click : () => openAgent ( who , near ( `#main [data-focus="agent: ${ CSS . escape ( claim . office ) } "]` ))}}, face ( who , false ), who ) : h ( "span" , { class : "muted" }, "– " ),
2026-11-11 07:44:02 +00:00
claim . session === "-" ? null : presenceMark ( claim . session , false ),
2026-11-08 17:26:51 +00:00
active ? chip ( "bi-play-circle" , "active" , "ok" ) : chip ( "bi-pause-circle" , "no heartbeat" , "neutral" ),
2026-11-07 17:46:39 +00:00
claim . work && claim . work !== "-" ? h ( "span" , { class : "muted org-work" , title : claim . work }, short ( claim . work )) : null ,
h ( "span" , { class : "org-actions" },
2026-11-08 17:26:51 +00:00
officeBtn ( "bi-arrow-left-right" , "Hand over" , `Hand over: ${ claim . office } to another session, with a passkey signature` , () => officeSheet ( "takeover" , claim ), `takeover: ${ claim . office } ` ),
officeBtn ( "bi-person-x" , "Recall" , `Recall: free ${ claim . office } , with a passkey signature` , () => officeSheet ( "recall" , claim ), `recall: ${ claim . office } ` )));
2026-11-07 17:46:39 +00:00
}
function vacantRow ( office , label ) {
return h ( "div" , { class : "org-row vacant" , "data-office" : office },
h ( "span" , { class : "org-office" }, icon ( OFFICE_ICONS . chair ), h ( "strong" , {}, label )),
2026-11-08 17:26:51 +00:00
pill ( "vacant" , "warn" ),
h ( "span" , { class : "org-actions" }, officeBtn ( "bi-person-plus" , "Appoint" , `Appoint: give ${ office } to a session, with a passkey signature` , () => officeSheet ( "appoint" , { office , session : "" , work : "" }), `appoint: ${ office } ` )));
2026-11-07 17:46:39 +00:00
}
function groupRow ( glyph , label , count , note ) {
return h ( "div" , { class : "org-row group" }, h ( "span" , { class : "org-office" }, icon ( glyph ), h ( "strong" , {}, label )), counter ( count ), h ( "span" , { class : "muted" }, note ));
}
function orgNode ( id , label , row , children ) {
if ( ! children . length ) {
return h ( "li" , {}, h ( "div" , { class : "org-line" }, h ( "span" , { class : "org-gap" , "aria-hidden" : "true" }), row ));
}
const open = ! hier . closed . has ( id );
hier . ids += 1 ;
const list = h ( "ul" , { class : "org-tree" , id : `org- ${ hier . ids } ` , hidden : ! open }, children );
2026-11-08 17:26:51 +00:00
const toggle = h ( "button" , { type : "button" , class : "btn small ghost icon-only org-toggle" , "aria-expanded" : String ( open ), "aria-controls" : list . id , "data-focus" : `toggle: ${ id } ` , ... named ( ` ${ open ? "Collapse" : "Expand" } : ${ label } ` )}, icon ( open ? "bi-chevron-down" : "bi-chevron-right" ));
2026-11-07 17:46:39 +00:00
toggle . addEventListener ( "click" , () => {
const now = list . hidden ;
list . hidden = ! now ;
toggle . setAttribute ( "aria-expanded" , String ( now ));
2026-11-08 17:26:51 +00:00
relabel ( toggle , now ? "bi-chevron-down" : "bi-chevron-right" , ` ${ now ? "Collapse" : "Expand" } : ${ label } ` );
2026-11-07 17:46:39 +00:00
if ( now ) {
hier . closed . delete ( id );
} else {
hier . closed . add ( id );
}
});
return h ( "li" , { "data-node" : id }, h ( "div" , { class : "org-line" }, toggle , row ), list );
}
function chartTree () {
const claims = state . claims ;
const dimensions = new Map ();
for ( const claim of claims . filter (( entry ) => entry . kind === "line" )) {
dimensions . set ( claim . dimension , [...( dimensions . get ( claim . dimension ) || []), claim ]);
}
const lines = [... dimensions ]. sort (([ a ], [ b ]) => a . localeCompare ( b )). map (([ dimension , held ]) => orgNode ( `dimension: ${ dimension } ` , dimension ,
2026-11-08 17:26:51 +00:00
groupRow ( "bi-diagram-2" , dimension , held . length , held . length === 1 ? "line" : "lines" ),
held . map (( claim ) => orgNode ( claim . office , claim . office , officeRow ( claim , claim . line , "Representative" ), []))));
2026-11-07 17:46:39 +00:00
const groups = OFFICE_GROUPS . map (([ kind , label , title ]) => [ kind , label , title , claims . filter (( entry ) => entry . kind === kind )]). filter (([, , , held ]) => held . length )
. map (([ kind , label , title , held ]) => orgNode ( `group: ${ kind } ` , label , groupRow ( OFFICE_ICONS [ kind ], label , held . length , kind === "other" ? "" : ` ${ kind } /…` ),
held . map (( claim ) => orgNode ( claim . office , claim . office , officeRow ( claim , kind === "other" ? claim . office : claim . office . slice ( kind . length + 1 ), title ), []))));
const chair = claims . find (( entry ) => entry . kind === "chair" );
2026-11-08 17:26:51 +00:00
const chairNode = orgNode ( "chair" , "Chair" , chair ? officeRow ( chair , "Chair" , "Office" ) : vacantRow ( "chair" , "Chair" ), [... lines , ... groups ]);
const root = h ( "div" , { class : "org-row org-root" }, h ( "span" , { class : "org-office" }, icon ( "bi-person-circle" ), h ( "strong" , {}, HUMAN )), h ( "span" , { class : "muted" }, "assigns every office with a passkey signature" ));
2026-11-07 17:46:39 +00:00
return h ( "ul" , { class : "org-tree root" }, orgNode ( "operator" , HUMAN , root , [ chairNode ]));
}
function drawChart () {
const key = focusKey ( hier . chart );
hier . ids = 0 ;
const count = state . claims . length ;
hier . chart . replaceChildren (
2026-11-08 17:26:51 +00:00
h ( "div" , { class : "inbox-head" }, h ( "h2" , { class : "group-title" , id : "org-title" }, "Hierarchy" , counter ( count )),
h ( "span" , { class : "muted" }, "Formal offices from the council: Operator → Chair → dimensions → lines, plus organizers and presidents" ),
2026-11-07 17:46:39 +00:00
h ( "span" , { class : "spacer" }),
2026-11-08 17:26:51 +00:00
h ( "button" , { type : "button" , class : "btn small soft" , "data-focus" : "appoint:new" , ... named ( "New office" , "New office: give a free office to a session, with a passkey signature" ), on : { click : () => officeSheet ( "appoint" , null )}}, icon ( "bi-person-plus" ), "New office" )),
2026-11-07 17:46:39 +00:00
chartTree ());
refocus ( hier . chart , key );
}