@@ -958,6 +958,107 @@ export function ProvenanceGraph({
958958 ) ;
959959 }
960960
961+ const controlsBody = (
962+ < >
963+ < div className = "flex items-center gap-2 bg-base-100/90 border border-base-300 rounded-lg px-3 py-2 shadow-sm w-full sm:w-auto" >
964+ < input
965+ type = "text"
966+ value = { searchQuery }
967+ onChange = { ( e ) => setSearchQuery ( e . target . value ) }
968+ placeholder = "Find parameter/task..."
969+ className = "input input-bordered input-sm w-full sm:w-56 pointer-events-auto"
970+ />
971+ { searchQuery && (
972+ < button
973+ className = "btn btn-ghost btn-sm pointer-events-auto"
974+ onClick = { ( ) => setSearchQuery ( "" ) }
975+ >
976+ Clear
977+ </ button >
978+ ) }
979+ </ div >
980+ < div className = "flex flex-col sm:flex-row items-center gap-2 bg-base-100/90 border border-base-300 rounded-lg px-3 py-2 shadow-sm w-full sm:w-auto" >
981+ < div className = "flex items-center gap-2" >
982+ < div className = "text-xs text-base-content/60 whitespace-nowrap" >
983+ Focus
984+ </ div >
985+ < input
986+ type = "range"
987+ min = { 1 }
988+ max = { Math . max ( 1 , Math . min ( 10 , maxDistance || 10 ) ) }
989+ value = { Math . min (
990+ focusHops ,
991+ Math . max ( 1 , Math . min ( 10 , maxDistance || 10 ) ) ,
992+ ) }
993+ onChange = { ( e ) => setFocusHops ( Number ( e . target . value ) ) }
994+ className = "range range-xs range-primary w-28 pointer-events-auto"
995+ disabled = { effectiveShowAll }
996+ />
997+ < div className = "text-xs font-medium tabular-nums w-10 text-right" >
998+ { effectiveShowAll ? "All" : `${ focusHops } h` }
999+ </ div >
1000+ < button
1001+ className = { `btn btn-xs pointer-events-auto ${ effectiveShowAll ? "btn-primary" : "" } ` }
1002+ onClick = { ( ) => setShowAll ( ( v ) => ! v ) }
1003+ >
1004+ All
1005+ </ button >
1006+ </ div >
1007+ < div className = "flex items-center gap-2" >
1008+ < label className = "flex items-center gap-2 ml-2 cursor-pointer pointer-events-auto" >
1009+ < input
1010+ type = "checkbox"
1011+ className = "checkbox checkbox-xs checkbox-primary pointer-events-auto"
1012+ checked = { showDerivedEdges }
1013+ onChange = { ( e ) => setShowDerivedEdges ( e . target . checked ) }
1014+ />
1015+ < span className = "text-xs text-base-content/60" > derived</ span >
1016+ </ label >
1017+ < div className = "join ml-2" >
1018+ < button
1019+ className = { `btn btn-xs join-item pointer-events-auto ${ viewDetail === "full" ? "btn-primary" : "" } ` }
1020+ onClick = { ( ) => setViewDetail ( "full" ) }
1021+ >
1022+ Detail
1023+ </ button >
1024+ < button
1025+ className = { `btn btn-xs join-item pointer-events-auto ${ viewDetail === "taskFlow" ? "btn-primary" : "" } ` }
1026+ onClick = { ( ) => setViewDetail ( "taskFlow" ) }
1027+ >
1028+ Task Flow
1029+ </ button >
1030+ </ div >
1031+ </ div >
1032+ </ div >
1033+ < div className = "flex flex-wrap items-center gap-1 sm:gap-2 bg-base-100/90 border border-base-300 rounded-lg px-2 sm:px-3 py-2 shadow-sm w-full sm:w-auto sm:shrink-0" >
1034+ < span className = "badge badge-primary badge-sm whitespace-nowrap" >
1035+ { initialNodes . length } /{ apiNodes . length } shown
1036+ </ span >
1037+ < span className = "badge badge-ghost badge-sm whitespace-nowrap" >
1038+ params { visibleCounts . entityCount }
1039+ </ span >
1040+ < span className = "badge badge-ghost badge-sm whitespace-nowrap" >
1041+ tasks { visibleCounts . taskCount }
1042+ </ span >
1043+ { visibleCounts . failedTasks > 0 && (
1044+ < span className = "badge badge-error badge-sm whitespace-nowrap" >
1045+ failed { visibleCounts . failedTasks }
1046+ </ span >
1047+ ) }
1048+ { visibleCounts . lowConfidenceParams > 0 && (
1049+ < span className = "badge badge-warning badge-sm whitespace-nowrap" >
1050+ uncertain { visibleCounts . lowConfidenceParams }
1051+ </ span >
1052+ ) }
1053+ { visibleCounts . staleInputs > 0 && (
1054+ < span className = "badge badge-info badge-sm whitespace-nowrap" >
1055+ updated { visibleCounts . staleInputs }
1056+ </ span >
1057+ ) }
1058+ </ div >
1059+ </ >
1060+ ) ;
1061+
9611062 return (
9621063 < div className = "h-[calc(100vh-16rem)] min-h-[500px] flex bg-base-200 rounded-lg border border-base-300 overflow-hidden" >
9631064 { /* Graph canvas */ }
@@ -999,107 +1100,24 @@ export function ProvenanceGraph({
9991100 />
10001101 </ ReactFlow >
10011102
1002- { /* Controls / Summary */ }
1003- < div className = "absolute top-4 left-4 right-4 flex flex-col sm:flex-row sm:flex-wrap gap-2 items-start sm:items-center z-20 pointer-events-none" >
1004- < div className = "flex items-center gap-2 bg-base-100/90 border border-base-300 rounded-lg px-3 py-2 shadow-sm w-full sm:w-auto" >
1005- < input
1006- type = "text"
1007- value = { searchQuery }
1008- onChange = { ( e ) => setSearchQuery ( e . target . value ) }
1009- placeholder = "Find parameter/task..."
1010- className = "input input-bordered input-sm w-full sm:w-56 pointer-events-auto"
1011- />
1012- { searchQuery && (
1013- < button
1014- className = "btn btn-ghost btn-sm pointer-events-auto"
1015- onClick = { ( ) => setSearchQuery ( "" ) }
1016- >
1017- Clear
1018- </ button >
1019- ) }
1020- </ div >
1021-
1022- < div className = "flex items-center gap-2 bg-base-100/90 border border-base-300 rounded-lg px-3 py-2 shadow-sm w-full sm:w-auto sm:mr-auto" >
1023- < div className = "text-xs text-base-content/60 whitespace-nowrap" >
1024- Focus
1025- </ div >
1026- < input
1027- type = "range"
1028- min = { 1 }
1029- max = { Math . max ( 1 , Math . min ( 10 , maxDistance || 10 ) ) }
1030- value = { Math . min (
1031- focusHops ,
1032- Math . max ( 1 , Math . min ( 10 , maxDistance || 10 ) ) ,
1033- ) }
1034- onChange = { ( e ) => setFocusHops ( Number ( e . target . value ) ) }
1035- className = "range range-xs range-primary w-28 pointer-events-auto"
1036- disabled = { effectiveShowAll }
1037- />
1038- < div className = "text-xs font-medium tabular-nums w-10 text-right" >
1039- { effectiveShowAll ? "All" : `${ focusHops } h` }
1040- </ div >
1041- < button
1042- className = { `btn btn-xs pointer-events-auto ${ effectiveShowAll ? "btn-primary" : "" } ` }
1043- onClick = { ( ) => setShowAll ( ( v ) => ! v ) }
1103+ { /* Controls — SP: accordion (hidden on sm+) */ }
1104+ < details className = "group absolute top-2 left-2 right-2 z-20 pointer-events-auto sm:hidden" >
1105+ < summary className = "bg-base-100/90 border border-base-300 rounded-lg px-3 py-2 shadow-sm cursor-pointer select-none list-none text-sm font-medium flex items-center gap-2" >
1106+ < span
1107+ className = "inline-block transition-transform group-open:rotate-90"
1108+ aria-hidden = "true"
10441109 >
1045- All
1046- </ button >
1047- < div className = "flex items-center gap-2 ml-2" >
1048- < input
1049- type = "checkbox"
1050- className = "checkbox checkbox-xs checkbox-primary pointer-events-auto"
1051- checked = { showDerivedEdges }
1052- onChange = { ( e ) => setShowDerivedEdges ( e . target . checked ) }
1053- id = "show-derived"
1054- />
1055- < label
1056- htmlFor = "show-derived"
1057- className = "text-xs text-base-content/60 cursor-pointer pointer-events-auto"
1058- >
1059- derived
1060- </ label >
1061- </ div >
1062- < div className = "join ml-2" >
1063- < button
1064- className = { `btn btn-xs join-item pointer-events-auto ${ viewDetail === "full" ? "btn-primary" : "" } ` }
1065- onClick = { ( ) => setViewDetail ( "full" ) }
1066- >
1067- Detail
1068- </ button >
1069- < button
1070- className = { `btn btn-xs join-item pointer-events-auto ${ viewDetail === "taskFlow" ? "btn-primary" : "" } ` }
1071- onClick = { ( ) => setViewDetail ( "taskFlow" ) }
1072- >
1073- Task Flow
1074- </ button >
1075- </ div >
1076- </ div >
1077- < div className = "flex flex-wrap items-center gap-1 sm:gap-2 bg-base-100/90 border border-base-300 rounded-lg px-2 sm:px-3 py-2 shadow-sm w-full sm:w-auto sm:shrink-0" >
1078- < span className = "badge badge-primary badge-sm whitespace-nowrap" >
1079- { initialNodes . length } /{ apiNodes . length } shown
1080- </ span >
1081- < span className = "badge badge-ghost badge-sm whitespace-nowrap" >
1082- params { visibleCounts . entityCount }
1083- </ span >
1084- < span className = "badge badge-ghost badge-sm whitespace-nowrap" >
1085- tasks { visibleCounts . taskCount }
1110+ ▶
10861111 </ span >
1087- { visibleCounts . failedTasks > 0 && (
1088- < span className = "badge badge-error badge-sm whitespace-nowrap" >
1089- failed { visibleCounts . failedTasks }
1090- </ span >
1091- ) }
1092- { visibleCounts . lowConfidenceParams > 0 && (
1093- < span className = "badge badge-warning badge-sm whitespace-nowrap" >
1094- uncertain { visibleCounts . lowConfidenceParams }
1095- </ span >
1096- ) }
1097- { visibleCounts . staleInputs > 0 && (
1098- < span className = "badge badge-info badge-sm whitespace-nowrap" >
1099- updated { visibleCounts . staleInputs }
1100- </ span >
1101- ) }
1112+ Controls
1113+ </ summary >
1114+ < div className = "flex flex-col gap-2 items-start mt-2" >
1115+ { controlsBody }
11021116 </ div >
1117+ </ details >
1118+ { /* Controls — PC: always visible (hidden below sm) */ }
1119+ < div className = "hidden sm:flex sm:flex-row sm:flex-wrap gap-2 items-center absolute top-4 left-4 right-4 z-20 pointer-events-none" >
1120+ { controlsBody }
11031121 </ div >
11041122
11051123 { /* Legend */ }
0 commit comments