donobu 5.42.0 → 5.43.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-tags="${esc(JSON.stringify(test.tags))}" ${hasDetails ? `data-detail="${testId}"` : ''}>
1681
+ <div class="test-card ${sc.label.toLowerCase().replace(/ /g, '')} ${expandableClass}" id="${testId}" data-status="${test.status}" 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>
@@ -1786,6 +1786,8 @@ body::before{content:'';position:fixed;top:-750px;left:50%;transform:translateX(
1786
1786
  .tag-menu-item:hover{background:var(--surface)}
1787
1787
  .tag-menu-item .tag-menu-count{color:var(--text-muted);font-size:11px;font-family:var(--mono)}
1788
1788
  .tag-menu-empty{padding:8px 10px;font-size:12px;color:var(--text-muted);font-style:italic}
1789
+ .tag-menu-section{padding:8px 10px 4px;font-size:10px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:var(--text-dim);font-family:inherit}
1790
+ .tag-menu-section:not(:first-child){margin-top:4px;border-top:1px solid var(--border)}
1789
1791
  .active-tag-filters{display:inline-flex;align-items:center;gap:6px;flex-wrap:wrap}
1790
1792
  .tag-chip{display:inline-flex;align-items:center;gap:6px;background:rgba(255,127,58,.12);border:1px solid rgba(255,127,58,.3);color:var(--accent);font-size:11px;font-family:var(--mono);padding:3px 4px 3px 8px;border-radius:4px}
1791
1793
  .tag-chip-remove{background:transparent;border:none;color:inherit;cursor:pointer;font-size:14px;line-height:1;padding:0 4px;font-family:inherit;opacity:.7;transition:opacity .15s}
@@ -2136,7 +2138,7 @@ details.ai-invocation[open]>summary .native-step-chevron{transform:rotate(90deg)
2136
2138
  <div class="stat-pills">${statPillsHtml}</div>
2137
2139
  <div class="tag-filter-controls" data-tag-filter-controls hidden>
2138
2140
  <div class="tag-filter-trigger-wrap">
2139
- <button class="add-tag-filter" data-add-tag-filter title="Filter by tag"><span class="add-tag-plus">+</span> Tag</button>
2141
+ <button class="add-tag-filter" data-add-tag-filter title="Filter by tag or diagnosis"><span class="add-tag-plus">+</span> Filter</button>
2140
2142
  <div class="tag-menu" data-tag-menu hidden></div>
2141
2143
  </div>
2142
2144
  <div class="active-tag-filters" data-active-tag-filters></div>
@@ -2160,18 +2162,24 @@ details.ai-invocation[open]>summary .native-step-chevron{transform:rotate(90deg)
2160
2162
 
2161
2163
  <script>
2162
2164
  (function(){
2163
- // Filters compose: a card is visible iff its status matches the active
2164
- // status filter (if any) AND it carries every tag in activeTags. The two
2165
- // dimensions are independent; "Clear Filter" wipes both.
2165
+ // Filters compose across three dimensions:
2166
+ // status — single-select; card.data-status must match (if set).
2167
+ // tags — multi-select AND; card must carry every active tag.
2168
+ // reasons — multi-select OR; card.data-reason must match any active reason
2169
+ // (a card has at most one diagnosis, so AND would always be 0/1).
2170
+ // "Clear Filters" wipes all three.
2166
2171
  var activeStatus=null;
2167
2172
  var activeTags=new Set();
2173
+ var activeReasons=new Set();
2168
2174
  var allTags=[];
2175
+ var allReasons=[]; // ordered list of REASON keys present in the report
2176
+ var REASON_LABELS=${JSON.stringify(REASON_LABELS)};
2169
2177
 
2170
2178
  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 []}}
2171
2179
  function tagCount(t){var n=0;document.querySelectorAll('.test-card').forEach(function(c){if(cardTags(c).indexOf(t)!==-1)n++});return n}
2172
2180
 
2173
2181
  function applyFilters(){
2174
- var anyActive=activeStatus!==null||activeTags.size>0;
2182
+ var anyActive=activeStatus!==null||activeTags.size>0||activeReasons.size>0;
2175
2183
  document.querySelector('.clear-filter').classList.toggle('visible',anyActive);
2176
2184
  var visible=0;
2177
2185
  document.querySelectorAll('.test-card').forEach(function(card){
@@ -2181,7 +2189,12 @@ details.ai-invocation[open]>summary .native-step-chevron{transform:rotate(90deg)
2181
2189
  var t=cardTags(card);
2182
2190
  activeTags.forEach(function(want){if(t.indexOf(want)===-1)tagsOk=false});
2183
2191
  }
2184
- var hide=!(statusOk&&tagsOk);
2192
+ var reasonOk=true;
2193
+ if(activeReasons.size>0){
2194
+ var r=card.getAttribute('data-reason')||'';
2195
+ reasonOk=activeReasons.has(r);
2196
+ }
2197
+ var hide=!(statusOk&&tagsOk&&reasonOk);
2185
2198
  card.classList.toggle('hidden-by-filter',hide);
2186
2199
  if(!hide)visible++;
2187
2200
  });
@@ -2201,6 +2214,7 @@ details.ai-invocation[open]>summary .native-step-chevron{transform:rotate(90deg)
2201
2214
  var p=new URLSearchParams();
2202
2215
  if(activeStatus)p.set('status',activeStatus);
2203
2216
  activeTags.forEach(function(t){p.append('tag',t)});
2217
+ activeReasons.forEach(function(r){p.append('reason',r)});
2204
2218
  var qs=p.toString();
2205
2219
  var next=location.pathname+(qs?'?'+qs:'')+(location.hash||'');
2206
2220
  if(next!==location.pathname+location.search+location.hash){
@@ -2214,6 +2228,15 @@ details.ai-invocation[open]>summary .native-step-chevron{transform:rotate(90deg)
2214
2228
  applyFilters();
2215
2229
  }
2216
2230
 
2231
+ // Convert "#rrggbb" or "rgb(...)" into a low-alpha background for tinted
2232
+ // diagnosis chips. Falls back to the accent tint if parsing fails.
2233
+ function hexToRgba(hex,a){
2234
+ if(!hex||hex.charAt(0)!=='#'||(hex.length!==4&&hex.length!==7))return 'rgba(255,127,58,'+a+')';
2235
+ var s=hex.length===4?('#'+hex[1]+hex[1]+hex[2]+hex[2]+hex[3]+hex[3]):hex;
2236
+ var r=parseInt(s.slice(1,3),16),g=parseInt(s.slice(3,5),16),b=parseInt(s.slice(5,7),16);
2237
+ return 'rgba('+r+','+g+','+b+','+a+')';
2238
+ }
2239
+
2217
2240
  function renderActiveChips(){
2218
2241
  var c=document.querySelector('[data-active-tag-filters]');
2219
2242
  if(!c)return;
@@ -2225,28 +2248,66 @@ details.ai-invocation[open]>summary .native-step-chevron{transform:rotate(90deg)
2225
2248
  chip.appendChild(label);chip.appendChild(btn);
2226
2249
  c.appendChild(chip);
2227
2250
  });
2251
+ activeReasons.forEach(function(r){
2252
+ var meta=REASON_LABELS[r]||REASON_LABELS['UNKNOWN'];
2253
+ var chip=document.createElement('span');chip.className='tag-chip reason-chip';
2254
+ chip.style.background=hexToRgba(meta.color,0.14);
2255
+ chip.style.borderColor=hexToRgba(meta.color,0.4);
2256
+ chip.style.color=meta.color;
2257
+ var label=document.createElement('span');label.textContent=meta.label;
2258
+ var btn=document.createElement('button');btn.className='tag-chip-remove';btn.setAttribute('data-remove-reason',r);btn.setAttribute('title','Remove filter');btn.textContent='×';
2259
+ chip.appendChild(label);chip.appendChild(btn);
2260
+ c.appendChild(chip);
2261
+ });
2228
2262
  }
2229
2263
  function addTag(t){if(!t||activeTags.has(t))return;activeTags.add(t);renderActiveChips();applyFilters()}
2230
2264
  function removeTag(t){if(!activeTags.delete(t))return;renderActiveChips();applyFilters()}
2265
+ function addReason(r){if(!r||activeReasons.has(r))return;activeReasons.add(r);renderActiveChips();applyFilters()}
2266
+ function removeReason(r){if(!activeReasons.delete(r))return;renderActiveChips();applyFilters()}
2267
+
2268
+ function reasonCount(r){var n=0;document.querySelectorAll('.test-card').forEach(function(c){if(c.getAttribute('data-reason')===r)n++});return n}
2231
2269
 
2232
2270
  function openTagMenu(){
2233
2271
  var menu=document.querySelector('[data-tag-menu]');
2234
2272
  if(!menu)return;
2235
2273
  var trigger=document.querySelector('[data-add-tag-filter]');
2236
2274
  menu.innerHTML='';
2237
- var available=allTags.filter(function(t){return !activeTags.has(t)});
2238
- if(available.length===0){
2239
- var empty=document.createElement('div');empty.className='tag-menu-empty';
2240
- empty.textContent=allTags.length?'All tags selected':'No tags';
2241
- menu.appendChild(empty);
2242
- }else{
2243
- available.forEach(function(t){
2244
- var item=document.createElement('button');item.className='tag-menu-item';item.setAttribute('data-tag-menu-item',t);
2245
- var label=document.createElement('span');label.textContent=t;
2246
- var count=document.createElement('span');count.className='tag-menu-count';count.textContent=tagCount(t);
2247
- item.appendChild(label);item.appendChild(count);
2248
- menu.appendChild(item);
2249
- });
2275
+ var availTags=allTags.filter(function(t){return !activeTags.has(t)});
2276
+ var availReasons=allReasons.filter(function(r){return !activeReasons.has(r)});
2277
+ var added=false;
2278
+ if(allTags.length>0){
2279
+ var hT=document.createElement('div');hT.className='tag-menu-section';hT.textContent='Tags';menu.appendChild(hT);
2280
+ if(availTags.length===0){
2281
+ var emptyT=document.createElement('div');emptyT.className='tag-menu-empty';emptyT.textContent='All tags selected';menu.appendChild(emptyT);
2282
+ }else{
2283
+ availTags.forEach(function(t){
2284
+ var item=document.createElement('button');item.className='tag-menu-item';item.setAttribute('data-tag-menu-item',t);
2285
+ var label=document.createElement('span');label.textContent=t;
2286
+ var count=document.createElement('span');count.className='tag-menu-count';count.textContent=tagCount(t);
2287
+ item.appendChild(label);item.appendChild(count);
2288
+ menu.appendChild(item);
2289
+ });
2290
+ }
2291
+ added=true;
2292
+ }
2293
+ if(allReasons.length>0){
2294
+ var hR=document.createElement('div');hR.className='tag-menu-section';hR.textContent='Diagnoses';menu.appendChild(hR);
2295
+ if(availReasons.length===0){
2296
+ var emptyR=document.createElement('div');emptyR.className='tag-menu-empty';emptyR.textContent='All diagnoses selected';menu.appendChild(emptyR);
2297
+ }else{
2298
+ availReasons.forEach(function(r){
2299
+ var meta=REASON_LABELS[r]||REASON_LABELS['UNKNOWN'];
2300
+ var item=document.createElement('button');item.className='tag-menu-item';item.setAttribute('data-reason-menu-item',r);
2301
+ var label=document.createElement('span');label.textContent=meta.label;label.style.color=meta.color;
2302
+ var count=document.createElement('span');count.className='tag-menu-count';count.textContent=reasonCount(r);
2303
+ item.appendChild(label);item.appendChild(count);
2304
+ menu.appendChild(item);
2305
+ });
2306
+ }
2307
+ added=true;
2308
+ }
2309
+ if(!added){
2310
+ var empty=document.createElement('div');empty.className='tag-menu-empty';empty.textContent='No filters available';menu.appendChild(empty);
2250
2311
  }
2251
2312
  menu.hidden=false;
2252
2313
  if(trigger)trigger.classList.add('active');
@@ -2261,6 +2322,7 @@ details.ai-invocation[open]>summary .native-step-chevron{transform:rotate(90deg)
2261
2322
  function clearAllFilters(){
2262
2323
  activeStatus=null;
2263
2324
  activeTags.clear();
2325
+ activeReasons.clear();
2264
2326
  document.querySelectorAll('.stat-pill').forEach(function(p){p.classList.remove('active')});
2265
2327
  renderActiveChips();
2266
2328
  closeTagMenu();
@@ -2303,8 +2365,12 @@ details.ai-invocation[open]>summary .native-step-chevron{transform:rotate(90deg)
2303
2365
  if(addTagBtn){if(tagMenuOpen()){closeTagMenu()}else{openTagMenu()}return}
2304
2366
  var tagItem=e.target.closest('[data-tag-menu-item]');
2305
2367
  if(tagItem){addTag(tagItem.getAttribute('data-tag-menu-item'));closeTagMenu();return}
2368
+ var reasonItem=e.target.closest('[data-reason-menu-item]');
2369
+ if(reasonItem){addReason(reasonItem.getAttribute('data-reason-menu-item'));closeTagMenu();return}
2306
2370
  var tagRemove=e.target.closest('[data-remove-tag]');
2307
2371
  if(tagRemove){removeTag(tagRemove.getAttribute('data-remove-tag'));return}
2372
+ var reasonRemove=e.target.closest('[data-remove-reason]');
2373
+ if(reasonRemove){removeReason(reasonRemove.getAttribute('data-remove-reason'));return}
2308
2374
  // Stat pill filter
2309
2375
  var pill=e.target.closest('.stat-pill[data-filter]');
2310
2376
  if(pill){toggleStatus(pill.getAttribute('data-filter'));return}
@@ -2349,23 +2415,29 @@ details.ai-invocation[open]>summary .native-step-chevron{transform:rotate(90deg)
2349
2415
  // Auto-expand failed/timedout/interrupted/healed tests
2350
2416
  document.querySelectorAll('.test-card.failed,.test-card.timedout,.test-card.interrupted,.test-card.healed').forEach(function(r){r.classList.add('expanded')});
2351
2417
 
2352
- // Collect the cumulative set of tags present across all test cards and
2353
- // reveal the +tag-filter controls only when at least one tag exists.
2418
+ // Collect the cumulative set of tags + diagnosis reasons present across all
2419
+ // test cards. Reveal the +filter controls when at least one of either exists.
2354
2420
  (function(){
2355
- var seen=Object.create(null);
2421
+ var seenTags=Object.create(null);
2422
+ var seenReasons=Object.create(null);
2356
2423
  document.querySelectorAll('.test-card').forEach(function(card){
2357
- var raw=card.getAttribute('data-tags');if(!raw)return;
2358
- try{var tags=JSON.parse(raw);if(Array.isArray(tags)){tags.forEach(function(t){if(typeof t==='string'&&t)seen[t]=true})}}catch(_){}
2424
+ var raw=card.getAttribute('data-tags');
2425
+ if(raw){try{var tags=JSON.parse(raw);if(Array.isArray(tags)){tags.forEach(function(t){if(typeof t==='string'&&t)seenTags[t]=true})}}catch(_){}}
2426
+ var r=card.getAttribute('data-reason');
2427
+ if(r)seenReasons[r]=true;
2359
2428
  });
2360
- allTags=Object.keys(seen).sort();
2429
+ allTags=Object.keys(seenTags).sort();
2430
+ // Preserve the REASON_LABELS declaration order rather than alphabetical —
2431
+ // they're already arranged from most-frequent/specific to UNKNOWN catch-all.
2432
+ allReasons=Object.keys(REASON_LABELS).filter(function(r){return seenReasons[r]});
2361
2433
  var controls=document.querySelector('[data-tag-filter-controls]');
2362
- if(controls&&allTags.length>0)controls.hidden=false;
2434
+ if(controls&&(allTags.length>0||allReasons.length>0))controls.hidden=false;
2363
2435
  })();
2364
2436
 
2365
- // Seed filter state from ?status=...&tag=... so shared URLs restore the
2366
- // view. Status values not in the known stat-pill set are ignored; tag
2367
- // values not present in this report are dropped so a stale URL can't
2368
- // poison the state.
2437
+ // Seed filter state from ?status=...&tag=...&reason=... so shared URLs
2438
+ // restore the view. Status values not in the known stat-pill set are
2439
+ // ignored; tag and reason values not present in this report are dropped
2440
+ // so a stale URL can't poison the state.
2369
2441
  (function(){
2370
2442
  var p=new URLSearchParams(location.search);
2371
2443
  var s=p.get('status');
@@ -2377,8 +2449,10 @@ details.ai-invocation[open]>summary .native-step-chevron{transform:rotate(90deg)
2377
2449
  }
2378
2450
  var tagSet={};allTags.forEach(function(t){tagSet[t]=true});
2379
2451
  p.getAll('tag').forEach(function(t){if(tagSet[t])activeTags.add(t)});
2380
- if(activeTags.size>0)renderActiveChips();
2381
- if(activeStatus!==null||activeTags.size>0)applyFilters();
2452
+ var reasonSet={};allReasons.forEach(function(r){reasonSet[r]=true});
2453
+ p.getAll('reason').forEach(function(r){if(reasonSet[r])activeReasons.add(r)});
2454
+ if(activeTags.size>0||activeReasons.size>0)renderActiveChips();
2455
+ if(activeStatus!==null||activeTags.size>0||activeReasons.size>0)applyFilters();
2382
2456
  })();
2383
2457
 
2384
2458
  // 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-tags="${esc(JSON.stringify(test.tags))}" ${hasDetails ? `data-detail="${testId}"` : ''}>
1681
+ <div class="test-card ${sc.label.toLowerCase().replace(/ /g, '')} ${expandableClass}" id="${testId}" data-status="${test.status}" 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>
@@ -1786,6 +1786,8 @@ body::before{content:'';position:fixed;top:-750px;left:50%;transform:translateX(
1786
1786
  .tag-menu-item:hover{background:var(--surface)}
1787
1787
  .tag-menu-item .tag-menu-count{color:var(--text-muted);font-size:11px;font-family:var(--mono)}
1788
1788
  .tag-menu-empty{padding:8px 10px;font-size:12px;color:var(--text-muted);font-style:italic}
1789
+ .tag-menu-section{padding:8px 10px 4px;font-size:10px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:var(--text-dim);font-family:inherit}
1790
+ .tag-menu-section:not(:first-child){margin-top:4px;border-top:1px solid var(--border)}
1789
1791
  .active-tag-filters{display:inline-flex;align-items:center;gap:6px;flex-wrap:wrap}
1790
1792
  .tag-chip{display:inline-flex;align-items:center;gap:6px;background:rgba(255,127,58,.12);border:1px solid rgba(255,127,58,.3);color:var(--accent);font-size:11px;font-family:var(--mono);padding:3px 4px 3px 8px;border-radius:4px}
1791
1793
  .tag-chip-remove{background:transparent;border:none;color:inherit;cursor:pointer;font-size:14px;line-height:1;padding:0 4px;font-family:inherit;opacity:.7;transition:opacity .15s}
@@ -2136,7 +2138,7 @@ details.ai-invocation[open]>summary .native-step-chevron{transform:rotate(90deg)
2136
2138
  <div class="stat-pills">${statPillsHtml}</div>
2137
2139
  <div class="tag-filter-controls" data-tag-filter-controls hidden>
2138
2140
  <div class="tag-filter-trigger-wrap">
2139
- <button class="add-tag-filter" data-add-tag-filter title="Filter by tag"><span class="add-tag-plus">+</span> Tag</button>
2141
+ <button class="add-tag-filter" data-add-tag-filter title="Filter by tag or diagnosis"><span class="add-tag-plus">+</span> Filter</button>
2140
2142
  <div class="tag-menu" data-tag-menu hidden></div>
2141
2143
  </div>
2142
2144
  <div class="active-tag-filters" data-active-tag-filters></div>
@@ -2160,18 +2162,24 @@ details.ai-invocation[open]>summary .native-step-chevron{transform:rotate(90deg)
2160
2162
 
2161
2163
  <script>
2162
2164
  (function(){
2163
- // Filters compose: a card is visible iff its status matches the active
2164
- // status filter (if any) AND it carries every tag in activeTags. The two
2165
- // dimensions are independent; "Clear Filter" wipes both.
2165
+ // Filters compose across three dimensions:
2166
+ // status — single-select; card.data-status must match (if set).
2167
+ // tags — multi-select AND; card must carry every active tag.
2168
+ // reasons — multi-select OR; card.data-reason must match any active reason
2169
+ // (a card has at most one diagnosis, so AND would always be 0/1).
2170
+ // "Clear Filters" wipes all three.
2166
2171
  var activeStatus=null;
2167
2172
  var activeTags=new Set();
2173
+ var activeReasons=new Set();
2168
2174
  var allTags=[];
2175
+ var allReasons=[]; // ordered list of REASON keys present in the report
2176
+ var REASON_LABELS=${JSON.stringify(REASON_LABELS)};
2169
2177
 
2170
2178
  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 []}}
2171
2179
  function tagCount(t){var n=0;document.querySelectorAll('.test-card').forEach(function(c){if(cardTags(c).indexOf(t)!==-1)n++});return n}
2172
2180
 
2173
2181
  function applyFilters(){
2174
- var anyActive=activeStatus!==null||activeTags.size>0;
2182
+ var anyActive=activeStatus!==null||activeTags.size>0||activeReasons.size>0;
2175
2183
  document.querySelector('.clear-filter').classList.toggle('visible',anyActive);
2176
2184
  var visible=0;
2177
2185
  document.querySelectorAll('.test-card').forEach(function(card){
@@ -2181,7 +2189,12 @@ details.ai-invocation[open]>summary .native-step-chevron{transform:rotate(90deg)
2181
2189
  var t=cardTags(card);
2182
2190
  activeTags.forEach(function(want){if(t.indexOf(want)===-1)tagsOk=false});
2183
2191
  }
2184
- var hide=!(statusOk&&tagsOk);
2192
+ var reasonOk=true;
2193
+ if(activeReasons.size>0){
2194
+ var r=card.getAttribute('data-reason')||'';
2195
+ reasonOk=activeReasons.has(r);
2196
+ }
2197
+ var hide=!(statusOk&&tagsOk&&reasonOk);
2185
2198
  card.classList.toggle('hidden-by-filter',hide);
2186
2199
  if(!hide)visible++;
2187
2200
  });
@@ -2201,6 +2214,7 @@ details.ai-invocation[open]>summary .native-step-chevron{transform:rotate(90deg)
2201
2214
  var p=new URLSearchParams();
2202
2215
  if(activeStatus)p.set('status',activeStatus);
2203
2216
  activeTags.forEach(function(t){p.append('tag',t)});
2217
+ activeReasons.forEach(function(r){p.append('reason',r)});
2204
2218
  var qs=p.toString();
2205
2219
  var next=location.pathname+(qs?'?'+qs:'')+(location.hash||'');
2206
2220
  if(next!==location.pathname+location.search+location.hash){
@@ -2214,6 +2228,15 @@ details.ai-invocation[open]>summary .native-step-chevron{transform:rotate(90deg)
2214
2228
  applyFilters();
2215
2229
  }
2216
2230
 
2231
+ // Convert "#rrggbb" or "rgb(...)" into a low-alpha background for tinted
2232
+ // diagnosis chips. Falls back to the accent tint if parsing fails.
2233
+ function hexToRgba(hex,a){
2234
+ if(!hex||hex.charAt(0)!=='#'||(hex.length!==4&&hex.length!==7))return 'rgba(255,127,58,'+a+')';
2235
+ var s=hex.length===4?('#'+hex[1]+hex[1]+hex[2]+hex[2]+hex[3]+hex[3]):hex;
2236
+ var r=parseInt(s.slice(1,3),16),g=parseInt(s.slice(3,5),16),b=parseInt(s.slice(5,7),16);
2237
+ return 'rgba('+r+','+g+','+b+','+a+')';
2238
+ }
2239
+
2217
2240
  function renderActiveChips(){
2218
2241
  var c=document.querySelector('[data-active-tag-filters]');
2219
2242
  if(!c)return;
@@ -2225,28 +2248,66 @@ details.ai-invocation[open]>summary .native-step-chevron{transform:rotate(90deg)
2225
2248
  chip.appendChild(label);chip.appendChild(btn);
2226
2249
  c.appendChild(chip);
2227
2250
  });
2251
+ activeReasons.forEach(function(r){
2252
+ var meta=REASON_LABELS[r]||REASON_LABELS['UNKNOWN'];
2253
+ var chip=document.createElement('span');chip.className='tag-chip reason-chip';
2254
+ chip.style.background=hexToRgba(meta.color,0.14);
2255
+ chip.style.borderColor=hexToRgba(meta.color,0.4);
2256
+ chip.style.color=meta.color;
2257
+ var label=document.createElement('span');label.textContent=meta.label;
2258
+ var btn=document.createElement('button');btn.className='tag-chip-remove';btn.setAttribute('data-remove-reason',r);btn.setAttribute('title','Remove filter');btn.textContent='×';
2259
+ chip.appendChild(label);chip.appendChild(btn);
2260
+ c.appendChild(chip);
2261
+ });
2228
2262
  }
2229
2263
  function addTag(t){if(!t||activeTags.has(t))return;activeTags.add(t);renderActiveChips();applyFilters()}
2230
2264
  function removeTag(t){if(!activeTags.delete(t))return;renderActiveChips();applyFilters()}
2265
+ function addReason(r){if(!r||activeReasons.has(r))return;activeReasons.add(r);renderActiveChips();applyFilters()}
2266
+ function removeReason(r){if(!activeReasons.delete(r))return;renderActiveChips();applyFilters()}
2267
+
2268
+ function reasonCount(r){var n=0;document.querySelectorAll('.test-card').forEach(function(c){if(c.getAttribute('data-reason')===r)n++});return n}
2231
2269
 
2232
2270
  function openTagMenu(){
2233
2271
  var menu=document.querySelector('[data-tag-menu]');
2234
2272
  if(!menu)return;
2235
2273
  var trigger=document.querySelector('[data-add-tag-filter]');
2236
2274
  menu.innerHTML='';
2237
- var available=allTags.filter(function(t){return !activeTags.has(t)});
2238
- if(available.length===0){
2239
- var empty=document.createElement('div');empty.className='tag-menu-empty';
2240
- empty.textContent=allTags.length?'All tags selected':'No tags';
2241
- menu.appendChild(empty);
2242
- }else{
2243
- available.forEach(function(t){
2244
- var item=document.createElement('button');item.className='tag-menu-item';item.setAttribute('data-tag-menu-item',t);
2245
- var label=document.createElement('span');label.textContent=t;
2246
- var count=document.createElement('span');count.className='tag-menu-count';count.textContent=tagCount(t);
2247
- item.appendChild(label);item.appendChild(count);
2248
- menu.appendChild(item);
2249
- });
2275
+ var availTags=allTags.filter(function(t){return !activeTags.has(t)});
2276
+ var availReasons=allReasons.filter(function(r){return !activeReasons.has(r)});
2277
+ var added=false;
2278
+ if(allTags.length>0){
2279
+ var hT=document.createElement('div');hT.className='tag-menu-section';hT.textContent='Tags';menu.appendChild(hT);
2280
+ if(availTags.length===0){
2281
+ var emptyT=document.createElement('div');emptyT.className='tag-menu-empty';emptyT.textContent='All tags selected';menu.appendChild(emptyT);
2282
+ }else{
2283
+ availTags.forEach(function(t){
2284
+ var item=document.createElement('button');item.className='tag-menu-item';item.setAttribute('data-tag-menu-item',t);
2285
+ var label=document.createElement('span');label.textContent=t;
2286
+ var count=document.createElement('span');count.className='tag-menu-count';count.textContent=tagCount(t);
2287
+ item.appendChild(label);item.appendChild(count);
2288
+ menu.appendChild(item);
2289
+ });
2290
+ }
2291
+ added=true;
2292
+ }
2293
+ if(allReasons.length>0){
2294
+ var hR=document.createElement('div');hR.className='tag-menu-section';hR.textContent='Diagnoses';menu.appendChild(hR);
2295
+ if(availReasons.length===0){
2296
+ var emptyR=document.createElement('div');emptyR.className='tag-menu-empty';emptyR.textContent='All diagnoses selected';menu.appendChild(emptyR);
2297
+ }else{
2298
+ availReasons.forEach(function(r){
2299
+ var meta=REASON_LABELS[r]||REASON_LABELS['UNKNOWN'];
2300
+ var item=document.createElement('button');item.className='tag-menu-item';item.setAttribute('data-reason-menu-item',r);
2301
+ var label=document.createElement('span');label.textContent=meta.label;label.style.color=meta.color;
2302
+ var count=document.createElement('span');count.className='tag-menu-count';count.textContent=reasonCount(r);
2303
+ item.appendChild(label);item.appendChild(count);
2304
+ menu.appendChild(item);
2305
+ });
2306
+ }
2307
+ added=true;
2308
+ }
2309
+ if(!added){
2310
+ var empty=document.createElement('div');empty.className='tag-menu-empty';empty.textContent='No filters available';menu.appendChild(empty);
2250
2311
  }
2251
2312
  menu.hidden=false;
2252
2313
  if(trigger)trigger.classList.add('active');
@@ -2261,6 +2322,7 @@ details.ai-invocation[open]>summary .native-step-chevron{transform:rotate(90deg)
2261
2322
  function clearAllFilters(){
2262
2323
  activeStatus=null;
2263
2324
  activeTags.clear();
2325
+ activeReasons.clear();
2264
2326
  document.querySelectorAll('.stat-pill').forEach(function(p){p.classList.remove('active')});
2265
2327
  renderActiveChips();
2266
2328
  closeTagMenu();
@@ -2303,8 +2365,12 @@ details.ai-invocation[open]>summary .native-step-chevron{transform:rotate(90deg)
2303
2365
  if(addTagBtn){if(tagMenuOpen()){closeTagMenu()}else{openTagMenu()}return}
2304
2366
  var tagItem=e.target.closest('[data-tag-menu-item]');
2305
2367
  if(tagItem){addTag(tagItem.getAttribute('data-tag-menu-item'));closeTagMenu();return}
2368
+ var reasonItem=e.target.closest('[data-reason-menu-item]');
2369
+ if(reasonItem){addReason(reasonItem.getAttribute('data-reason-menu-item'));closeTagMenu();return}
2306
2370
  var tagRemove=e.target.closest('[data-remove-tag]');
2307
2371
  if(tagRemove){removeTag(tagRemove.getAttribute('data-remove-tag'));return}
2372
+ var reasonRemove=e.target.closest('[data-remove-reason]');
2373
+ if(reasonRemove){removeReason(reasonRemove.getAttribute('data-remove-reason'));return}
2308
2374
  // Stat pill filter
2309
2375
  var pill=e.target.closest('.stat-pill[data-filter]');
2310
2376
  if(pill){toggleStatus(pill.getAttribute('data-filter'));return}
@@ -2349,23 +2415,29 @@ details.ai-invocation[open]>summary .native-step-chevron{transform:rotate(90deg)
2349
2415
  // Auto-expand failed/timedout/interrupted/healed tests
2350
2416
  document.querySelectorAll('.test-card.failed,.test-card.timedout,.test-card.interrupted,.test-card.healed').forEach(function(r){r.classList.add('expanded')});
2351
2417
 
2352
- // Collect the cumulative set of tags present across all test cards and
2353
- // reveal the +tag-filter controls only when at least one tag exists.
2418
+ // Collect the cumulative set of tags + diagnosis reasons present across all
2419
+ // test cards. Reveal the +filter controls when at least one of either exists.
2354
2420
  (function(){
2355
- var seen=Object.create(null);
2421
+ var seenTags=Object.create(null);
2422
+ var seenReasons=Object.create(null);
2356
2423
  document.querySelectorAll('.test-card').forEach(function(card){
2357
- var raw=card.getAttribute('data-tags');if(!raw)return;
2358
- try{var tags=JSON.parse(raw);if(Array.isArray(tags)){tags.forEach(function(t){if(typeof t==='string'&&t)seen[t]=true})}}catch(_){}
2424
+ var raw=card.getAttribute('data-tags');
2425
+ if(raw){try{var tags=JSON.parse(raw);if(Array.isArray(tags)){tags.forEach(function(t){if(typeof t==='string'&&t)seenTags[t]=true})}}catch(_){}}
2426
+ var r=card.getAttribute('data-reason');
2427
+ if(r)seenReasons[r]=true;
2359
2428
  });
2360
- allTags=Object.keys(seen).sort();
2429
+ allTags=Object.keys(seenTags).sort();
2430
+ // Preserve the REASON_LABELS declaration order rather than alphabetical —
2431
+ // they're already arranged from most-frequent/specific to UNKNOWN catch-all.
2432
+ allReasons=Object.keys(REASON_LABELS).filter(function(r){return seenReasons[r]});
2361
2433
  var controls=document.querySelector('[data-tag-filter-controls]');
2362
- if(controls&&allTags.length>0)controls.hidden=false;
2434
+ if(controls&&(allTags.length>0||allReasons.length>0))controls.hidden=false;
2363
2435
  })();
2364
2436
 
2365
- // Seed filter state from ?status=...&tag=... so shared URLs restore the
2366
- // view. Status values not in the known stat-pill set are ignored; tag
2367
- // values not present in this report are dropped so a stale URL can't
2368
- // poison the state.
2437
+ // Seed filter state from ?status=...&tag=...&reason=... so shared URLs
2438
+ // restore the view. Status values not in the known stat-pill set are
2439
+ // ignored; tag and reason values not present in this report are dropped
2440
+ // so a stale URL can't poison the state.
2369
2441
  (function(){
2370
2442
  var p=new URLSearchParams(location.search);
2371
2443
  var s=p.get('status');
@@ -2377,8 +2449,10 @@ details.ai-invocation[open]>summary .native-step-chevron{transform:rotate(90deg)
2377
2449
  }
2378
2450
  var tagSet={};allTags.forEach(function(t){tagSet[t]=true});
2379
2451
  p.getAll('tag').forEach(function(t){if(tagSet[t])activeTags.add(t)});
2380
- if(activeTags.size>0)renderActiveChips();
2381
- if(activeStatus!==null||activeTags.size>0)applyFilters();
2452
+ var reasonSet={};allReasons.forEach(function(r){reasonSet[r]=true});
2453
+ p.getAll('reason').forEach(function(r){if(reasonSet[r])activeReasons.add(r)});
2454
+ if(activeTags.size>0||activeReasons.size>0)renderActiveChips();
2455
+ if(activeStatus!==null||activeTags.size>0||activeReasons.size>0)applyFilters();
2382
2456
  })();
2383
2457
 
2384
2458
  // 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.42.0",
3
+ "version": "5.43.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",