2026-11-08 17:26:51 +00:00
const WORK_CHIPS = { dirty : [ "bi-pencil-square" , "not committed" , "warn" ], unpushed : [ "bi-cloud-upload" , "unpushed" , "info" ], unmerged : [ "bi-signpost-split" , "not merged" , "neutral" ], task : [ "bi-card-checklist" , "task open" , "violet" ]};
const FLOW_SIDES = { pull : "pulls before the gate" , push : "merges into when done" };
2026-11-07 23:43:26 +00:00
function workChips ( reasons ) {
if ( ! reasons || ! reasons . length ) {
return null ;
}
2026-11-08 17:26:51 +00:00
return h ( "span" , { class : "states work" , role : "group" , "aria-label" : "Open work" }, reasons . map (( reason ) => {
2026-11-07 23:43:26 +00:00
const [ name , label , tone ] = Object . hasOwn ( WORK_CHIPS , reason ) ? WORK_CHIPS [ reason ] : [ "bi-question-circle" , reason , "neutral" ];
2026-11-08 17:26:51 +00:00
return h ( "span" , { class : "state" , "data-tone" : tone , title : `open work: ${ label } ` }, icon ( name ), label );
2026-11-07 23:43:26 +00:00
}));
}
function flowLine ( flow , branch ) {
if ( ! flow ) {
return null ;
}
if ( flow . source === "invalid" ) {
2026-11-08 17:26:51 +00:00
return h ( "div" , { class : "flow invalid" , "data-flow" : "invalid" }, icon ( "bi-exclamation-triangle" ), "Merge flow invalid: pkgmgr cannot read the flow file" );
2026-11-07 23:43:26 +00:00
}
const refs = ( side ) => ( flow [ side ]. length ? flow [ side ]. map (( ref ) => h ( "code" , { class : "flow-ref" , title : ` ${ FLOW_SIDES [ side ] } : ${ ref } ` }, ref )) : h ( "span" , { class : "muted" }, "– " ));
2026-11-08 17:26:51 +00:00
const arrow = () => h ( "span" , { class : "flow-arrow" , ... named ( "flows to" )}, icon ( "bi-arrow-right" ));
return h ( "div" , { class : "flow" , "data-flow" : flow . source , role : "group" , "aria-label" : `Merge flow: ${ flow . pull . join ( ", " ) || "no source" } to ${ branch || "– " } to ${ flow . push . join ( ", " ) || "no target" } ` },
2026-11-07 23:43:26 +00:00
h ( "span" , { class : "flow-label" }, icon ( "bi-sign-merge-right" ), "Flow" ),
h ( "span" , { class : "flow-side" }, refs ( "pull" )), arrow (),
2026-11-08 17:26:51 +00:00
h ( "code" , { class : "flow-ref here" , title : "this branch" }, branch || "– " ), arrow (),
2026-11-07 23:43:26 +00:00
h ( "span" , { class : "flow-side" }, refs ( "push" )));
}
function flowDetails ( flow , branch ) {
if ( ! flow ) {
2026-11-08 17:26:51 +00:00
return h ( "p" , { class : "muted" }, "No merge flow set." );
2026-11-07 23:43:26 +00:00
}
if ( flow . source === "invalid" ) {
return flowLine ( flow , branch );
}
2026-11-08 17:26:51 +00:00
return [ h ( "h3" , {}, "Merge flow" ), flowLine ( flow , branch ), meta ([[ "Pulls before the gate" , flow . pull . join ( ", " )], [ "Merges into when done" , flow . push . join ( ", " )], [ "Source" , flow . source ]])];
2026-11-07 23:43:26 +00:00
}
function flowTarget ( names , ref ) {
if ( names . has ( ref )) {
return ref ;
}
const bare = ref . includes ( "/" ) ? ref . slice ( ref . indexOf ( "/" ) + 1 ) : "" ;
return names . has ( bare ) ? bare : null ;
}
function flowEdges ( repo ) {
const names = new Set ( repo . branches . map (( entry ) => entry . name ));
const edges = [];
for ( const branch of repo . branches ) {
for ( const flow of branch . worktrees . map (( wt ) => wt . flow ). filter (( found ) => found && found . source !== "invalid" )) {
for ( const ref of flow . pull ) {
const from = flowTarget ( names , ref );
if ( from && from !== branch . name ) {
edges . push ({ from , into : branch . name , side : "pull" , ref });
}
}
for ( const ref of flow . push ) {
const into = flowTarget ( names , ref );
if ( into && into !== branch . name ) {
edges . push ({ from : branch . name , into , side : "push" , ref });
}
}
}
}
return edges ;
}