357357.file-picker-hdr { display : flex; align-items : center; gap : 8px ; padding : 10px 12px ; border-bottom : 1px solid var (--border ); font-family : var (--mono ); font-size : 12px ; color : var (--text2 ); }
358358.file-picker-search { margin : 12px ; margin-bottom : 8px ; width : calc (100% - 24px ); background : var (--bg3 ); border : 1px solid var (--border ); border-radius : var (--radius ); color : var (--text ); font-family : var (--mono ); font-size : 12px ; padding : 8px 10px ; outline : none; }
359359.file-picker-search : focus { border-color : var (--accent ); box-shadow : 0 0 0 1px var (--accent ); }
360+ .file-picker-controls { display : flex; align-items : center; gap : 10px ; padding : 0 12px 10px ; }
361+ .file-picker-filter { display : flex; align-items : center; gap : 8px ; font-family : var (--mono ); font-size : 11px ; color : var (--text3 ); }
362+ .file-picker-filter select { background : var (--bg3 ); border : 1px solid var (--border ); border-radius : var (--radius ); color : var (--text ); font-family : var (--mono ); font-size : 11px ; padding : 6px 8px ; outline : none; }
363+ .file-picker-filter select : focus { border-color : var (--accent ); box-shadow : 0 0 0 1px var (--accent ); }
360364.file-picker-list { padding : 0 12px 12px ; overflow : auto; }
361365.file-picker-item { padding : 8px 10px ; margin : 0 0 6px ; border : 1px solid var (--border ); border-radius : var (--radius ); background : var (--bg3 ); cursor : pointer; transition : all .15s ; }
362366.file-picker-item : hover { border-color : var (--accent ); background : var (--bg-hover ); }
363367.file-picker-item .active { border-color : var (--accent ); box-shadow : 0 0 0 1px rgba (88 , 166 , 255 , .35 ); }
364368.file-picker-path { font-family : var (--mono ); font-size : 12px ; color : var (--text ); display : flex; align-items : center; gap : 8px ; flex-wrap : wrap; }
365369.file-picker-meta { margin-top : 2px ; font-size : 10px ; color : var (--text3 ); }
366370.file-picker-tag { display : inline-block; padding : 1px 6px ; border-radius : 999px ; font-size : 9px ; font-weight : 700 ; letter-spacing : .03em ; text-transform : uppercase; }
371+ .file-picker-tag .cgr { background : rgba (88 , 166 , 255 , .14 ); color : var (--accent ); }
372+ .file-picker-tag .cg { background : rgba (210 , 153 , 34 , .14 ); color : var (--amber ); }
373+ .file-picker-tag .root { background : rgba (139 , 148 , 158 , .18 ); color : var (--text2 ); }
374+ .file-picker-tag .example { background : rgba (63 , 185 , 80 , .14 ); color : var (--green ); }
375+ .file-picker-tag .testing { background : rgba (188 , 140 , 255 , .14 ); color : var (--purple ); }
367376.file-picker-tag .template { background : rgba (240 , 136 , 62 , .15 ); color : var (--coral ); }
368377.file-picker-empty { padding : 14px 6px ; color : var (--text3 ); font-size : 12px ; text-align : center; }
369378.file-picker-trigger { display : inline-flex; align-items : center; justify-content : center; width : 22px ; height : 22px ; border : 1px solid var (--border ); border-radius : var (--radius ); background : var (--bg3 ); color : var (--text2 ); cursor : pointer; font-size : 12px ; transition : all .15s ; }
@@ -510,6 +519,14 @@ <h1>◇ CommandGraph</h1>
510519 < button class ="btn " onclick ="closeFilePicker() " style ="font-size:10px;padding:2px 8px "> Close</ button >
511520 </ div >
512521 < input class ="file-picker-search " id ="filePickerSearch " type ="text " placeholder ="Filter .cgr / .cg files " oninput ="renderFilePicker(this.value) ">
522+ < div class ="file-picker-controls ">
523+ < label class ="file-picker-filter " for ="filePickerTagFilter ">
524+ < span > Show</ span >
525+ < select id ="filePickerTagFilter " onchange ="renderFilePicker(filePickerSearch.value) ">
526+ < option value ="all "> all files</ option >
527+ </ select >
528+ </ label >
529+ </ div >
513530 < div class ="file-picker-list " id ="filePickerList "> </ div >
514531 </ div >
515532</ div >
@@ -539,6 +556,7 @@ <h1>◇ CommandGraph</h1>
539556let applyStopClicks = 0 ;
540557let availableGraphFiles = [ ] ;
541558let availableGraphFileEntries = [ ] ;
559+ let filePickerTagOptions = [ ] ;
542560
543561const codeEl = document . getElementById ( 'code' ) ;
544562const hlEl = document . getElementById ( 'highlight' ) ;
@@ -558,6 +576,7 @@ <h1>◇ CommandGraph</h1>
558576const rerunBtn = document . getElementById ( 'rerunBtn' ) ;
559577const filePickerBackdrop = document . getElementById ( 'filePickerBackdrop' ) ;
560578const filePickerSearch = document . getElementById ( 'filePickerSearch' ) ;
579+ const filePickerTagFilter = document . getElementById ( 'filePickerTagFilter' ) ;
561580const filePickerList = document . getElementById ( 'filePickerList' ) ;
562581const filePickerBtn = document . getElementById ( 'filePickerBtn' ) ;
563582let execInputWrap = null ;
@@ -632,7 +651,13 @@ <h1>◇ CommandGraph</h1>
632651
633652function renderFilePicker ( filter = '' ) {
634653 const q = String ( filter || '' ) . toLowerCase ( ) . trim ( ) ;
635- const files = availableGraphFileEntries . filter ( f => ! q || f . name . toLowerCase ( ) . includes ( q ) ) ;
654+ const activeTag = filePickerTagFilter ? filePickerTagFilter . value : 'all' ;
655+ const files = availableGraphFileEntries . filter ( f => {
656+ const matchesSearch = ! q || f . name . toLowerCase ( ) . includes ( q ) ;
657+ const tags = Array . isArray ( f . tags ) ? f . tags : [ ] ;
658+ const matchesTag = activeTag === 'all' || tags . includes ( activeTag ) ;
659+ return matchesSearch && matchesTag ;
660+ } ) ;
636661 filePickerList . innerHTML = '' ;
637662 if ( ! files . length ) {
638663 filePickerList . innerHTML = '<div class="file-picker-empty">No matching graph files</div>' ;
@@ -642,14 +667,47 @@ <h1>◇ CommandGraph</h1>
642667 const name = file . name ;
643668 const isActive = currentFile === name ;
644669 const label = name . split ( '/' ) . pop ( ) ;
670+ const tags = Array . isArray ( file . tags ) ? file . tags : [ ] ;
671+ const tagsHtml = tags . map ( tag => `<span class="file-picker-tag ${ esc ( tag ) } ">${ esc ( tag ) } </span>` ) . join ( '' ) ;
645672 const item = document . createElement ( 'div' ) ;
646673 item . className = 'file-picker-item' + ( isActive ? ' active' : '' ) ;
647- item . innerHTML = `<div class="file-picker-path"><span>${ esc ( label ) } </span>${ file . is_template ? '<span class="file-picker-tag template">template</span>' : '' } </div><div class="file-picker-meta">${ esc ( name ) } ${ isActive ? ' · current file' : '' } </div>` ;
674+ item . innerHTML = `<div class="file-picker-path"><span>${ esc ( label ) } </span>${ tagsHtml } </div><div class="file-picker-meta">${ esc ( name ) } ${ isActive ? ' · current file' : '' } </div>` ;
648675 item . addEventListener ( 'click' , ( ) => { void chooseFileFromPicker ( name ) ; } ) ;
649676 filePickerList . appendChild ( item ) ;
650677 } ) ;
651678}
652679
680+ function filePickerTagLabel ( tag ) {
681+ const labels = {
682+ all : 'all files' ,
683+ root : 'root files' ,
684+ example : 'examples' ,
685+ testing : 'testing' ,
686+ template : 'templates' ,
687+ cgr : '.cgr' ,
688+ cg : '.cg' ,
689+ } ;
690+ return labels [ tag ] || tag ;
691+ }
692+
693+ function refreshFilePickerTagOptions ( ) {
694+ if ( ! filePickerTagFilter ) return ;
695+ const current = filePickerTagFilter . value || 'all' ;
696+ const preferredOrder = [ 'root' , 'example' , 'testing' , 'template' , 'cgr' , 'cg' ] ;
697+ const available = new Set ( ) ;
698+ availableGraphFileEntries . forEach ( file => {
699+ ( file . tags || [ ] ) . forEach ( tag => available . add ( tag ) ) ;
700+ } ) ;
701+ filePickerTagOptions = preferredOrder . filter ( tag => available . has ( tag ) ) ;
702+ available . forEach ( tag => {
703+ if ( ! filePickerTagOptions . includes ( tag ) ) filePickerTagOptions . push ( tag ) ;
704+ } ) ;
705+ const nextValue = current === 'all' || available . has ( current ) ? current : 'all' ;
706+ filePickerTagFilter . innerHTML = `<option value="all">${ esc ( filePickerTagLabel ( 'all' ) ) } </option>`
707+ + filePickerTagOptions . map ( tag => `<option value="${ esc ( tag ) } ">${ esc ( filePickerTagLabel ( tag ) ) } </option>` ) . join ( '' ) ;
708+ filePickerTagFilter . value = nextValue ;
709+ }
710+
653711function openFilePicker ( ) {
654712 if ( ! availableGraphFiles . length ) return ;
655713 filePickerBackdrop . style . display = 'flex' ;
@@ -2956,7 +3014,8 @@ <h1>◇ CommandGraph</h1>
29563014 try {
29573015 const info = await api ( 'GET' , '/api/info' ) ;
29583016 availableGraphFiles = info . files || [ ] ;
2959- availableGraphFileEntries = info . file_entries || availableGraphFiles . map ( name => ( { name, is_template : false } ) ) ;
3017+ availableGraphFileEntries = info . file_entries || availableGraphFiles . map ( name => ( { name, is_template : false , tags : [ ] } ) ) ;
3018+ refreshFilePickerTagOptions ( ) ;
29603019 filenameEl . title = availableGraphFiles . length ? 'Click to choose file' : 'No graph files available' ;
29613020 filePickerBtn . classList . toggle ( 'disabled' , ! availableGraphFiles . length ) ;
29623021 filePickerBtn . title = availableGraphFiles . length ? 'Open graph file (Ctrl+O)' : 'No graph files available' ;
0 commit comments