donobu 5.43.2 → 5.45.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.
@@ -1678,7 +1678,7 @@ function renderHtml(report, triage, outputDir) {
1678
1678
  ? `<div class="flow-id-detail"><span class="detail-label">Flow ID</span><span class="flow-id-value">${esc(test.flowId)}<button class="copy-flow-id" data-flow-id="${esc(test.flowId)}" title="Copy flow ID"><svg viewBox="0 0 24 24"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg></button></span></div>`
1679
1679
  : '';
1680
1680
  testSectionsHtml += `
1681
- <div class="test-card ${sc.label.toLowerCase().replace(/ /g, '')} ${expandableClass}" id="${testId}" data-status="${test.status}" data-file="${esc(test.file)}" data-tags="${esc(JSON.stringify(test.tags))}"${test.plan ? ` data-reason="${esc(test.plan.plan.failureReason)}"` : ''} ${hasDetails ? `data-detail="${testId}"` : ''}>
1681
+ <div class="test-card ${sc.label.toLowerCase().replace(/ /g, '')} ${expandableClass}" id="${testId}" data-status="${test.status}" data-file="${esc(test.file)}" data-search="${esc((displayFileName + ' ' + test.specTitle).toLowerCase())}" data-tags="${esc(JSON.stringify(test.tags))}"${test.plan ? ` data-reason="${esc(test.plan.plan.failureReason)}"` : ''} ${hasDetails ? `data-detail="${testId}"` : ''}>
1682
1682
  <div class="test-summary">
1683
1683
  ${chevron}
1684
1684
  <span class="status-dot" style="background:${sc.color}" title="${sc.label}"></span>
@@ -1769,6 +1769,17 @@ body::before{content:'';position:fixed;top:-750px;left:50%;transform:translateX(
1769
1769
  .stat-pill.active{background:var(--accent);border-color:var(--accent);color:#fff}
1770
1770
  .stat-pill.active .pill-count{background:rgba(255,255,255,.25);color:#fff}
1771
1771
  .pill-count{font-size:11px;font-weight:700;background:var(--overlay-light-active);color:var(--text-dim);padding:1px 7px;border-radius:calc(var(--radius) - 2px);min-width:20px;text-align:center;transition:all .2s}
1772
+ /* Substring search across test filename + spec title. Lives in the same row
1773
+ * as the stat pills and tag/diagnosis filter. */
1774
+ .filter-search-wrap{position:relative;display:inline-flex;align-items:center;flex-shrink:0}
1775
+ .filter-search-icon{position:absolute;left:8px;width:14px;height:14px;color:var(--text-muted);fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;pointer-events:none}
1776
+ .filter-search{background:var(--surface);border:1px solid var(--border);color:var(--text);font:inherit;font-size:12px;height:28px;padding:0 10px 0 28px;border-radius:var(--radius);width:200px;outline:none;transition:border-color .15s,background .15s}
1777
+ .filter-search::placeholder{color:var(--text-dim)}
1778
+ .filter-search:hover{border-color:var(--text-dim)}
1779
+ .filter-search:focus{border-color:var(--accent);background:var(--surface-raised)}
1780
+ /* Hide the WebKit search clear "x" — Clear Filters wipes it via the same UI. */
1781
+ .filter-search::-webkit-search-cancel-button{-webkit-appearance:none;appearance:none}
1782
+
1772
1783
  .clear-filter{background:var(--surface);border:1px solid var(--border);color:var(--text-muted);padding:6px 14px;border-radius:var(--radius);cursor:pointer;font-size:12px;font-weight:500;font-family:inherit;display:none;align-items:center;gap:5px;flex-shrink:0;transition:all .2s}
1773
1784
  .clear-filter:hover{background:var(--surface-raised);border-color:var(--text-dim);color:var(--text)}
1774
1785
  .clear-filter.visible{display:flex}
@@ -2131,6 +2142,10 @@ details.ai-invocation[open]>summary .native-step-chevron{transform:rotate(90deg)
2131
2142
  <div class="test-bar">${testBarHtml}</div>
2132
2143
  <div class="summary-stats">
2133
2144
  <div class="stat-pills">${statPillsHtml}</div>
2145
+ <label class="filter-search-wrap" title="Search test titles">
2146
+ <svg class="filter-search-icon" viewBox="0 0 24 24" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg>
2147
+ <input type="search" class="filter-search" data-filter-search placeholder="Search tests…" autocomplete="off" spellcheck="false" />
2148
+ </label>
2134
2149
  <div class="tag-filter-controls" data-tag-filter-controls hidden>
2135
2150
  <div class="tag-filter-trigger-wrap">
2136
2151
  <button class="add-tag-filter" data-add-tag-filter title="Filter by tag or diagnosis"><span class="add-tag-plus">+</span> Filter</button>
@@ -2165,15 +2180,43 @@ details.ai-invocation[open]>summary .native-step-chevron{transform:rotate(90deg)
2165
2180
  var activeStatus=null;
2166
2181
  var activeTags=new Set();
2167
2182
  var activeReasons=new Set();
2183
+ var activeSearch=''; // lowercase substring match against data-search
2168
2184
  var allTags=[];
2169
2185
  var allReasons=[]; // ordered list of REASON keys present in the report
2170
2186
  var REASON_LABELS=${JSON.stringify(REASON_LABELS)};
2171
2187
 
2172
2188
  function cardTags(card){var raw=card.getAttribute('data-tags');if(!raw)return [];try{var v=JSON.parse(raw);return Array.isArray(v)?v:[]}catch(_){return []}}
2173
- function tagCount(t){var n=0;document.querySelectorAll('.test-card').forEach(function(c){if(cardTags(c).indexOf(t)!==-1)n++});return n}
2189
+
2190
+ // Faceted-search counts. Each filter option's badge shows "how many tests
2191
+ // would this option contribute given the rest of the filters." The semantics
2192
+ // per dimension match how clicking interacts:
2193
+ // - Status pills (single-select replace): ignore current activeStatus.
2194
+ // - Tag menu items (multi-select AND): use ALL current filters.
2195
+ // - Reason menu items (multi-select OR): ignore current activeReasons.
2196
+ // Search is free-form and not counted.
2197
+ function cardsMatching(ignoreStatus,ignoreTags,ignoreReasons){
2198
+ var out=[];
2199
+ document.querySelectorAll('.test-card').forEach(function(card){
2200
+ var statusOk=ignoreStatus||activeStatus===null||card.getAttribute('data-status')===activeStatus;
2201
+ var tagsOk=true;
2202
+ if(!ignoreTags&&activeTags.size>0){
2203
+ var t=cardTags(card);
2204
+ activeTags.forEach(function(w){if(t.indexOf(w)===-1)tagsOk=false});
2205
+ }
2206
+ var reasonOk=ignoreReasons||activeReasons.size===0||activeReasons.has(card.getAttribute('data-reason')||'');
2207
+ var searchOk=activeSearch.length===0||(card.getAttribute('data-search')||'').indexOf(activeSearch)!==-1;
2208
+ if(statusOk&&tagsOk&&reasonOk&&searchOk)out.push(card);
2209
+ });
2210
+ return out;
2211
+ }
2212
+ function tagCount(t){
2213
+ var pool=cardsMatching(false,false,false);
2214
+ var n=0;for(var i=0;i<pool.length;i++){if(cardTags(pool[i]).indexOf(t)!==-1)n++}
2215
+ return n;
2216
+ }
2174
2217
 
2175
2218
  function applyFilters(){
2176
- var anyActive=activeStatus!==null||activeTags.size>0||activeReasons.size>0;
2219
+ var anyActive=activeStatus!==null||activeTags.size>0||activeReasons.size>0||activeSearch.length>0;
2177
2220
  document.querySelector('.clear-filter').classList.toggle('visible',anyActive);
2178
2221
  var visibleTests=0;
2179
2222
  var visibleFiles=Object.create(null);
@@ -2189,7 +2232,12 @@ details.ai-invocation[open]>summary .native-step-chevron{transform:rotate(90deg)
2189
2232
  var r=card.getAttribute('data-reason')||'';
2190
2233
  reasonOk=activeReasons.has(r);
2191
2234
  }
2192
- var hide=!(statusOk&&tagsOk&&reasonOk);
2235
+ var searchOk=true;
2236
+ if(activeSearch.length>0){
2237
+ var hay=card.getAttribute('data-search')||'';
2238
+ searchOk=hay.indexOf(activeSearch)!==-1;
2239
+ }
2240
+ var hide=!(statusOk&&tagsOk&&reasonOk&&searchOk);
2193
2241
  card.classList.toggle('hidden-by-filter',hide);
2194
2242
  if(!hide){
2195
2243
  visibleTests++;
@@ -2203,6 +2251,11 @@ details.ai-invocation[open]>summary .native-step-chevron{transform:rotate(90deg)
2203
2251
  var card=document.getElementById(block.getAttribute('data-target'));
2204
2252
  block.classList.toggle('hidden-by-filter',!!(card&&card.classList.contains('hidden-by-filter')));
2205
2253
  });
2254
+ // Faceted-search live counts: status pills, and the tag/diagnosis menu
2255
+ // (refreshed if currently open) all reflect "given the other filters,
2256
+ // how many tests would this option contribute".
2257
+ updateStatPillCounts();
2258
+ if(tagMenuOpen())openTagMenu(); // re-render menu items with fresh counts
2206
2259
  // "X tests across Y files" subtitle reflects the current filter result.
2207
2260
  // When no filter is active the form matches the original (no "of Y").
2208
2261
  var sub=document.querySelector('[data-summary-sub]');
@@ -2227,6 +2280,7 @@ details.ai-invocation[open]>summary .native-step-chevron{transform:rotate(90deg)
2227
2280
  if(activeStatus)p.set('status',activeStatus);
2228
2281
  activeTags.forEach(function(t){p.append('tag',t)});
2229
2282
  activeReasons.forEach(function(r){p.append('reason',r)});
2283
+ if(activeSearch)p.set('q',activeSearch);
2230
2284
  var qs=p.toString();
2231
2285
  var next=location.pathname+(qs?'?'+qs:'')+(location.hash||'');
2232
2286
  if(next!==location.pathname+location.search+location.hash){
@@ -2277,25 +2331,42 @@ details.ai-invocation[open]>summary .native-step-chevron{transform:rotate(90deg)
2277
2331
  function addReason(r){if(!r||activeReasons.has(r))return;activeReasons.add(r);renderActiveChips();applyFilters()}
2278
2332
  function removeReason(r){if(!activeReasons.delete(r))return;renderActiveChips();applyFilters()}
2279
2333
 
2280
- function reasonCount(r){var n=0;document.querySelectorAll('.test-card').forEach(function(c){if(c.getAttribute('data-reason')===r)n++});return n}
2334
+ function reasonCount(r){
2335
+ var pool=cardsMatching(false,false,true);
2336
+ var n=0;for(var i=0;i<pool.length;i++){if(pool[i].getAttribute('data-reason')===r)n++}
2337
+ return n;
2338
+ }
2339
+ function updateStatPillCounts(){
2340
+ var pool=cardsMatching(true,false,false);
2341
+ var counts=Object.create(null);
2342
+ for(var i=0;i<pool.length;i++){var s=pool[i].getAttribute('data-status');counts[s]=(counts[s]||0)+1}
2343
+ document.querySelectorAll('.stat-pill[data-filter]').forEach(function(pill){
2344
+ var key=pill.getAttribute('data-filter');
2345
+ var span=pill.querySelector('.pill-count');
2346
+ if(span)span.textContent=counts[key]||0;
2347
+ });
2348
+ }
2281
2349
 
2282
2350
  function openTagMenu(){
2283
2351
  var menu=document.querySelector('[data-tag-menu]');
2284
2352
  if(!menu)return;
2285
2353
  var trigger=document.querySelector('[data-add-tag-filter]');
2286
2354
  menu.innerHTML='';
2287
- var availTags=allTags.filter(function(t){return !activeTags.has(t)});
2288
- var availReasons=allReasons.filter(function(r){return !activeReasons.has(r)});
2355
+ // For each available tag/reason, compute its preview count under the
2356
+ // current other filters. Options with count 0 are hidden — they'd lead
2357
+ // to an empty view, so they're not useful to offer.
2358
+ var tagsWithCounts=allTags.filter(function(t){return !activeTags.has(t)}).map(function(t){return {key:t,count:tagCount(t)}}).filter(function(x){return x.count>0});
2359
+ var reasonsWithCounts=allReasons.filter(function(r){return !activeReasons.has(r)}).map(function(r){return {key:r,count:reasonCount(r)}}).filter(function(x){return x.count>0});
2289
2360
  var added=false;
2290
2361
  if(allTags.length>0){
2291
2362
  var hT=document.createElement('div');hT.className='tag-menu-section';hT.textContent='Tags';menu.appendChild(hT);
2292
- if(availTags.length===0){
2293
- var emptyT=document.createElement('div');emptyT.className='tag-menu-empty';emptyT.textContent='All tags selected';menu.appendChild(emptyT);
2363
+ if(tagsWithCounts.length===0){
2364
+ var emptyT=document.createElement('div');emptyT.className='tag-menu-empty';emptyT.textContent=allTags.length===activeTags.size?'All tags selected':'No matching tags';menu.appendChild(emptyT);
2294
2365
  }else{
2295
- availTags.forEach(function(t){
2296
- var item=document.createElement('button');item.className='tag-menu-item';item.setAttribute('data-tag-menu-item',t);
2297
- var label=document.createElement('span');label.textContent=t;
2298
- var count=document.createElement('span');count.className='tag-menu-count';count.textContent=tagCount(t);
2366
+ tagsWithCounts.forEach(function(x){
2367
+ var item=document.createElement('button');item.className='tag-menu-item';item.setAttribute('data-tag-menu-item',x.key);
2368
+ var label=document.createElement('span');label.textContent=x.key;
2369
+ var count=document.createElement('span');count.className='tag-menu-count';count.textContent=x.count;
2299
2370
  item.appendChild(label);item.appendChild(count);
2300
2371
  menu.appendChild(item);
2301
2372
  });
@@ -2304,14 +2375,14 @@ details.ai-invocation[open]>summary .native-step-chevron{transform:rotate(90deg)
2304
2375
  }
2305
2376
  if(allReasons.length>0){
2306
2377
  var hR=document.createElement('div');hR.className='tag-menu-section';hR.textContent='Diagnoses';menu.appendChild(hR);
2307
- if(availReasons.length===0){
2308
- var emptyR=document.createElement('div');emptyR.className='tag-menu-empty';emptyR.textContent='All diagnoses selected';menu.appendChild(emptyR);
2378
+ if(reasonsWithCounts.length===0){
2379
+ var emptyR=document.createElement('div');emptyR.className='tag-menu-empty';emptyR.textContent=allReasons.length===activeReasons.size?'All diagnoses selected':'No matching diagnoses';menu.appendChild(emptyR);
2309
2380
  }else{
2310
- availReasons.forEach(function(r){
2311
- var meta=REASON_LABELS[r]||REASON_LABELS['UNKNOWN'];
2312
- var item=document.createElement('button');item.className='tag-menu-item';item.setAttribute('data-reason-menu-item',r);
2381
+ reasonsWithCounts.forEach(function(x){
2382
+ var meta=REASON_LABELS[x.key]||REASON_LABELS['UNKNOWN'];
2383
+ var item=document.createElement('button');item.className='tag-menu-item';item.setAttribute('data-reason-menu-item',x.key);
2313
2384
  var label=document.createElement('span');label.textContent=meta.label;label.style.color=meta.color;
2314
- var count=document.createElement('span');count.className='tag-menu-count';count.textContent=reasonCount(r);
2385
+ var count=document.createElement('span');count.className='tag-menu-count';count.textContent=x.count;
2315
2386
  item.appendChild(label);item.appendChild(count);
2316
2387
  menu.appendChild(item);
2317
2388
  });
@@ -2335,7 +2406,10 @@ details.ai-invocation[open]>summary .native-step-chevron{transform:rotate(90deg)
2335
2406
  activeStatus=null;
2336
2407
  activeTags.clear();
2337
2408
  activeReasons.clear();
2409
+ activeSearch='';
2338
2410
  document.querySelectorAll('.stat-pill').forEach(function(p){p.classList.remove('active')});
2411
+ var searchInput=document.querySelector('[data-filter-search]');
2412
+ if(searchInput)searchInput.value='';
2339
2413
  renderActiveChips();
2340
2414
  closeTagMenu();
2341
2415
  applyFilters();
@@ -2463,8 +2537,20 @@ details.ai-invocation[open]>summary .native-step-chevron{transform:rotate(90deg)
2463
2537
  p.getAll('tag').forEach(function(t){if(tagSet[t])activeTags.add(t)});
2464
2538
  var reasonSet={};allReasons.forEach(function(r){reasonSet[r]=true});
2465
2539
  p.getAll('reason').forEach(function(r){if(reasonSet[r])activeReasons.add(r)});
2540
+ var q=p.get('q');
2541
+ var searchInput=document.querySelector('[data-filter-search]');
2542
+ if(q){
2543
+ activeSearch=q.toLowerCase();
2544
+ if(searchInput)searchInput.value=q;
2545
+ }
2546
+ if(searchInput){
2547
+ searchInput.addEventListener('input',function(){
2548
+ activeSearch=searchInput.value.trim().toLowerCase();
2549
+ applyFilters();
2550
+ });
2551
+ }
2466
2552
  if(activeTags.size>0||activeReasons.size>0)renderActiveChips();
2467
- if(activeStatus!==null||activeTags.size>0||activeReasons.size>0)applyFilters();
2553
+ if(activeStatus!==null||activeTags.size>0||activeReasons.size>0||activeSearch.length>0)applyFilters();
2468
2554
  })();
2469
2555
 
2470
2556
  // Open #?testId=<id> deep links to the matching test card. Used by the
@@ -1678,7 +1678,7 @@ function renderHtml(report, triage, outputDir) {
1678
1678
  ? `<div class="flow-id-detail"><span class="detail-label">Flow ID</span><span class="flow-id-value">${esc(test.flowId)}<button class="copy-flow-id" data-flow-id="${esc(test.flowId)}" title="Copy flow ID"><svg viewBox="0 0 24 24"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg></button></span></div>`
1679
1679
  : '';
1680
1680
  testSectionsHtml += `
1681
- <div class="test-card ${sc.label.toLowerCase().replace(/ /g, '')} ${expandableClass}" id="${testId}" data-status="${test.status}" data-file="${esc(test.file)}" data-tags="${esc(JSON.stringify(test.tags))}"${test.plan ? ` data-reason="${esc(test.plan.plan.failureReason)}"` : ''} ${hasDetails ? `data-detail="${testId}"` : ''}>
1681
+ <div class="test-card ${sc.label.toLowerCase().replace(/ /g, '')} ${expandableClass}" id="${testId}" data-status="${test.status}" data-file="${esc(test.file)}" data-search="${esc((displayFileName + ' ' + test.specTitle).toLowerCase())}" data-tags="${esc(JSON.stringify(test.tags))}"${test.plan ? ` data-reason="${esc(test.plan.plan.failureReason)}"` : ''} ${hasDetails ? `data-detail="${testId}"` : ''}>
1682
1682
  <div class="test-summary">
1683
1683
  ${chevron}
1684
1684
  <span class="status-dot" style="background:${sc.color}" title="${sc.label}"></span>
@@ -1769,6 +1769,17 @@ body::before{content:'';position:fixed;top:-750px;left:50%;transform:translateX(
1769
1769
  .stat-pill.active{background:var(--accent);border-color:var(--accent);color:#fff}
1770
1770
  .stat-pill.active .pill-count{background:rgba(255,255,255,.25);color:#fff}
1771
1771
  .pill-count{font-size:11px;font-weight:700;background:var(--overlay-light-active);color:var(--text-dim);padding:1px 7px;border-radius:calc(var(--radius) - 2px);min-width:20px;text-align:center;transition:all .2s}
1772
+ /* Substring search across test filename + spec title. Lives in the same row
1773
+ * as the stat pills and tag/diagnosis filter. */
1774
+ .filter-search-wrap{position:relative;display:inline-flex;align-items:center;flex-shrink:0}
1775
+ .filter-search-icon{position:absolute;left:8px;width:14px;height:14px;color:var(--text-muted);fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;pointer-events:none}
1776
+ .filter-search{background:var(--surface);border:1px solid var(--border);color:var(--text);font:inherit;font-size:12px;height:28px;padding:0 10px 0 28px;border-radius:var(--radius);width:200px;outline:none;transition:border-color .15s,background .15s}
1777
+ .filter-search::placeholder{color:var(--text-dim)}
1778
+ .filter-search:hover{border-color:var(--text-dim)}
1779
+ .filter-search:focus{border-color:var(--accent);background:var(--surface-raised)}
1780
+ /* Hide the WebKit search clear "x" — Clear Filters wipes it via the same UI. */
1781
+ .filter-search::-webkit-search-cancel-button{-webkit-appearance:none;appearance:none}
1782
+
1772
1783
  .clear-filter{background:var(--surface);border:1px solid var(--border);color:var(--text-muted);padding:6px 14px;border-radius:var(--radius);cursor:pointer;font-size:12px;font-weight:500;font-family:inherit;display:none;align-items:center;gap:5px;flex-shrink:0;transition:all .2s}
1773
1784
  .clear-filter:hover{background:var(--surface-raised);border-color:var(--text-dim);color:var(--text)}
1774
1785
  .clear-filter.visible{display:flex}
@@ -2131,6 +2142,10 @@ details.ai-invocation[open]>summary .native-step-chevron{transform:rotate(90deg)
2131
2142
  <div class="test-bar">${testBarHtml}</div>
2132
2143
  <div class="summary-stats">
2133
2144
  <div class="stat-pills">${statPillsHtml}</div>
2145
+ <label class="filter-search-wrap" title="Search test titles">
2146
+ <svg class="filter-search-icon" viewBox="0 0 24 24" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg>
2147
+ <input type="search" class="filter-search" data-filter-search placeholder="Search tests…" autocomplete="off" spellcheck="false" />
2148
+ </label>
2134
2149
  <div class="tag-filter-controls" data-tag-filter-controls hidden>
2135
2150
  <div class="tag-filter-trigger-wrap">
2136
2151
  <button class="add-tag-filter" data-add-tag-filter title="Filter by tag or diagnosis"><span class="add-tag-plus">+</span> Filter</button>
@@ -2165,15 +2180,43 @@ details.ai-invocation[open]>summary .native-step-chevron{transform:rotate(90deg)
2165
2180
  var activeStatus=null;
2166
2181
  var activeTags=new Set();
2167
2182
  var activeReasons=new Set();
2183
+ var activeSearch=''; // lowercase substring match against data-search
2168
2184
  var allTags=[];
2169
2185
  var allReasons=[]; // ordered list of REASON keys present in the report
2170
2186
  var REASON_LABELS=${JSON.stringify(REASON_LABELS)};
2171
2187
 
2172
2188
  function cardTags(card){var raw=card.getAttribute('data-tags');if(!raw)return [];try{var v=JSON.parse(raw);return Array.isArray(v)?v:[]}catch(_){return []}}
2173
- function tagCount(t){var n=0;document.querySelectorAll('.test-card').forEach(function(c){if(cardTags(c).indexOf(t)!==-1)n++});return n}
2189
+
2190
+ // Faceted-search counts. Each filter option's badge shows "how many tests
2191
+ // would this option contribute given the rest of the filters." The semantics
2192
+ // per dimension match how clicking interacts:
2193
+ // - Status pills (single-select replace): ignore current activeStatus.
2194
+ // - Tag menu items (multi-select AND): use ALL current filters.
2195
+ // - Reason menu items (multi-select OR): ignore current activeReasons.
2196
+ // Search is free-form and not counted.
2197
+ function cardsMatching(ignoreStatus,ignoreTags,ignoreReasons){
2198
+ var out=[];
2199
+ document.querySelectorAll('.test-card').forEach(function(card){
2200
+ var statusOk=ignoreStatus||activeStatus===null||card.getAttribute('data-status')===activeStatus;
2201
+ var tagsOk=true;
2202
+ if(!ignoreTags&&activeTags.size>0){
2203
+ var t=cardTags(card);
2204
+ activeTags.forEach(function(w){if(t.indexOf(w)===-1)tagsOk=false});
2205
+ }
2206
+ var reasonOk=ignoreReasons||activeReasons.size===0||activeReasons.has(card.getAttribute('data-reason')||'');
2207
+ var searchOk=activeSearch.length===0||(card.getAttribute('data-search')||'').indexOf(activeSearch)!==-1;
2208
+ if(statusOk&&tagsOk&&reasonOk&&searchOk)out.push(card);
2209
+ });
2210
+ return out;
2211
+ }
2212
+ function tagCount(t){
2213
+ var pool=cardsMatching(false,false,false);
2214
+ var n=0;for(var i=0;i<pool.length;i++){if(cardTags(pool[i]).indexOf(t)!==-1)n++}
2215
+ return n;
2216
+ }
2174
2217
 
2175
2218
  function applyFilters(){
2176
- var anyActive=activeStatus!==null||activeTags.size>0||activeReasons.size>0;
2219
+ var anyActive=activeStatus!==null||activeTags.size>0||activeReasons.size>0||activeSearch.length>0;
2177
2220
  document.querySelector('.clear-filter').classList.toggle('visible',anyActive);
2178
2221
  var visibleTests=0;
2179
2222
  var visibleFiles=Object.create(null);
@@ -2189,7 +2232,12 @@ details.ai-invocation[open]>summary .native-step-chevron{transform:rotate(90deg)
2189
2232
  var r=card.getAttribute('data-reason')||'';
2190
2233
  reasonOk=activeReasons.has(r);
2191
2234
  }
2192
- var hide=!(statusOk&&tagsOk&&reasonOk);
2235
+ var searchOk=true;
2236
+ if(activeSearch.length>0){
2237
+ var hay=card.getAttribute('data-search')||'';
2238
+ searchOk=hay.indexOf(activeSearch)!==-1;
2239
+ }
2240
+ var hide=!(statusOk&&tagsOk&&reasonOk&&searchOk);
2193
2241
  card.classList.toggle('hidden-by-filter',hide);
2194
2242
  if(!hide){
2195
2243
  visibleTests++;
@@ -2203,6 +2251,11 @@ details.ai-invocation[open]>summary .native-step-chevron{transform:rotate(90deg)
2203
2251
  var card=document.getElementById(block.getAttribute('data-target'));
2204
2252
  block.classList.toggle('hidden-by-filter',!!(card&&card.classList.contains('hidden-by-filter')));
2205
2253
  });
2254
+ // Faceted-search live counts: status pills, and the tag/diagnosis menu
2255
+ // (refreshed if currently open) all reflect "given the other filters,
2256
+ // how many tests would this option contribute".
2257
+ updateStatPillCounts();
2258
+ if(tagMenuOpen())openTagMenu(); // re-render menu items with fresh counts
2206
2259
  // "X tests across Y files" subtitle reflects the current filter result.
2207
2260
  // When no filter is active the form matches the original (no "of Y").
2208
2261
  var sub=document.querySelector('[data-summary-sub]');
@@ -2227,6 +2280,7 @@ details.ai-invocation[open]>summary .native-step-chevron{transform:rotate(90deg)
2227
2280
  if(activeStatus)p.set('status',activeStatus);
2228
2281
  activeTags.forEach(function(t){p.append('tag',t)});
2229
2282
  activeReasons.forEach(function(r){p.append('reason',r)});
2283
+ if(activeSearch)p.set('q',activeSearch);
2230
2284
  var qs=p.toString();
2231
2285
  var next=location.pathname+(qs?'?'+qs:'')+(location.hash||'');
2232
2286
  if(next!==location.pathname+location.search+location.hash){
@@ -2277,25 +2331,42 @@ details.ai-invocation[open]>summary .native-step-chevron{transform:rotate(90deg)
2277
2331
  function addReason(r){if(!r||activeReasons.has(r))return;activeReasons.add(r);renderActiveChips();applyFilters()}
2278
2332
  function removeReason(r){if(!activeReasons.delete(r))return;renderActiveChips();applyFilters()}
2279
2333
 
2280
- function reasonCount(r){var n=0;document.querySelectorAll('.test-card').forEach(function(c){if(c.getAttribute('data-reason')===r)n++});return n}
2334
+ function reasonCount(r){
2335
+ var pool=cardsMatching(false,false,true);
2336
+ var n=0;for(var i=0;i<pool.length;i++){if(pool[i].getAttribute('data-reason')===r)n++}
2337
+ return n;
2338
+ }
2339
+ function updateStatPillCounts(){
2340
+ var pool=cardsMatching(true,false,false);
2341
+ var counts=Object.create(null);
2342
+ for(var i=0;i<pool.length;i++){var s=pool[i].getAttribute('data-status');counts[s]=(counts[s]||0)+1}
2343
+ document.querySelectorAll('.stat-pill[data-filter]').forEach(function(pill){
2344
+ var key=pill.getAttribute('data-filter');
2345
+ var span=pill.querySelector('.pill-count');
2346
+ if(span)span.textContent=counts[key]||0;
2347
+ });
2348
+ }
2281
2349
 
2282
2350
  function openTagMenu(){
2283
2351
  var menu=document.querySelector('[data-tag-menu]');
2284
2352
  if(!menu)return;
2285
2353
  var trigger=document.querySelector('[data-add-tag-filter]');
2286
2354
  menu.innerHTML='';
2287
- var availTags=allTags.filter(function(t){return !activeTags.has(t)});
2288
- var availReasons=allReasons.filter(function(r){return !activeReasons.has(r)});
2355
+ // For each available tag/reason, compute its preview count under the
2356
+ // current other filters. Options with count 0 are hidden — they'd lead
2357
+ // to an empty view, so they're not useful to offer.
2358
+ var tagsWithCounts=allTags.filter(function(t){return !activeTags.has(t)}).map(function(t){return {key:t,count:tagCount(t)}}).filter(function(x){return x.count>0});
2359
+ var reasonsWithCounts=allReasons.filter(function(r){return !activeReasons.has(r)}).map(function(r){return {key:r,count:reasonCount(r)}}).filter(function(x){return x.count>0});
2289
2360
  var added=false;
2290
2361
  if(allTags.length>0){
2291
2362
  var hT=document.createElement('div');hT.className='tag-menu-section';hT.textContent='Tags';menu.appendChild(hT);
2292
- if(availTags.length===0){
2293
- var emptyT=document.createElement('div');emptyT.className='tag-menu-empty';emptyT.textContent='All tags selected';menu.appendChild(emptyT);
2363
+ if(tagsWithCounts.length===0){
2364
+ var emptyT=document.createElement('div');emptyT.className='tag-menu-empty';emptyT.textContent=allTags.length===activeTags.size?'All tags selected':'No matching tags';menu.appendChild(emptyT);
2294
2365
  }else{
2295
- availTags.forEach(function(t){
2296
- var item=document.createElement('button');item.className='tag-menu-item';item.setAttribute('data-tag-menu-item',t);
2297
- var label=document.createElement('span');label.textContent=t;
2298
- var count=document.createElement('span');count.className='tag-menu-count';count.textContent=tagCount(t);
2366
+ tagsWithCounts.forEach(function(x){
2367
+ var item=document.createElement('button');item.className='tag-menu-item';item.setAttribute('data-tag-menu-item',x.key);
2368
+ var label=document.createElement('span');label.textContent=x.key;
2369
+ var count=document.createElement('span');count.className='tag-menu-count';count.textContent=x.count;
2299
2370
  item.appendChild(label);item.appendChild(count);
2300
2371
  menu.appendChild(item);
2301
2372
  });
@@ -2304,14 +2375,14 @@ details.ai-invocation[open]>summary .native-step-chevron{transform:rotate(90deg)
2304
2375
  }
2305
2376
  if(allReasons.length>0){
2306
2377
  var hR=document.createElement('div');hR.className='tag-menu-section';hR.textContent='Diagnoses';menu.appendChild(hR);
2307
- if(availReasons.length===0){
2308
- var emptyR=document.createElement('div');emptyR.className='tag-menu-empty';emptyR.textContent='All diagnoses selected';menu.appendChild(emptyR);
2378
+ if(reasonsWithCounts.length===0){
2379
+ var emptyR=document.createElement('div');emptyR.className='tag-menu-empty';emptyR.textContent=allReasons.length===activeReasons.size?'All diagnoses selected':'No matching diagnoses';menu.appendChild(emptyR);
2309
2380
  }else{
2310
- availReasons.forEach(function(r){
2311
- var meta=REASON_LABELS[r]||REASON_LABELS['UNKNOWN'];
2312
- var item=document.createElement('button');item.className='tag-menu-item';item.setAttribute('data-reason-menu-item',r);
2381
+ reasonsWithCounts.forEach(function(x){
2382
+ var meta=REASON_LABELS[x.key]||REASON_LABELS['UNKNOWN'];
2383
+ var item=document.createElement('button');item.className='tag-menu-item';item.setAttribute('data-reason-menu-item',x.key);
2313
2384
  var label=document.createElement('span');label.textContent=meta.label;label.style.color=meta.color;
2314
- var count=document.createElement('span');count.className='tag-menu-count';count.textContent=reasonCount(r);
2385
+ var count=document.createElement('span');count.className='tag-menu-count';count.textContent=x.count;
2315
2386
  item.appendChild(label);item.appendChild(count);
2316
2387
  menu.appendChild(item);
2317
2388
  });
@@ -2335,7 +2406,10 @@ details.ai-invocation[open]>summary .native-step-chevron{transform:rotate(90deg)
2335
2406
  activeStatus=null;
2336
2407
  activeTags.clear();
2337
2408
  activeReasons.clear();
2409
+ activeSearch='';
2338
2410
  document.querySelectorAll('.stat-pill').forEach(function(p){p.classList.remove('active')});
2411
+ var searchInput=document.querySelector('[data-filter-search]');
2412
+ if(searchInput)searchInput.value='';
2339
2413
  renderActiveChips();
2340
2414
  closeTagMenu();
2341
2415
  applyFilters();
@@ -2463,8 +2537,20 @@ details.ai-invocation[open]>summary .native-step-chevron{transform:rotate(90deg)
2463
2537
  p.getAll('tag').forEach(function(t){if(tagSet[t])activeTags.add(t)});
2464
2538
  var reasonSet={};allReasons.forEach(function(r){reasonSet[r]=true});
2465
2539
  p.getAll('reason').forEach(function(r){if(reasonSet[r])activeReasons.add(r)});
2540
+ var q=p.get('q');
2541
+ var searchInput=document.querySelector('[data-filter-search]');
2542
+ if(q){
2543
+ activeSearch=q.toLowerCase();
2544
+ if(searchInput)searchInput.value=q;
2545
+ }
2546
+ if(searchInput){
2547
+ searchInput.addEventListener('input',function(){
2548
+ activeSearch=searchInput.value.trim().toLowerCase();
2549
+ applyFilters();
2550
+ });
2551
+ }
2466
2552
  if(activeTags.size>0||activeReasons.size>0)renderActiveChips();
2467
- if(activeStatus!==null||activeTags.size>0||activeReasons.size>0)applyFilters();
2553
+ if(activeStatus!==null||activeTags.size>0||activeReasons.size>0||activeSearch.length>0)applyFilters();
2468
2554
  })();
2469
2555
 
2470
2556
  // Open #?testId=<id> deep links to the matching test card. Used by the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "donobu",
3
- "version": "5.43.2",
3
+ "version": "5.45.0",
4
4
  "description": "Create browser automations with an LLM agent and replay them as Playwright scripts.",
5
5
  "main": "dist/main.js",
6
6
  "module": "dist/esm/main.js",