@beignet/devtools 0.0.3 → 0.0.4

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.
Files changed (64) hide show
  1. package/CHANGELOG.md +79 -0
  2. package/README.md +116 -71
  3. package/dist/events.d.ts +15 -3
  4. package/dist/events.d.ts.map +1 -1
  5. package/dist/events.js +1 -0
  6. package/dist/events.js.map +1 -1
  7. package/dist/index.d.ts +13 -15
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +11 -13
  10. package/dist/index.js.map +1 -1
  11. package/dist/persistence.d.ts +1 -1
  12. package/dist/persistence.d.ts.map +1 -1
  13. package/dist/provider-instrumentation.d.ts +1 -1
  14. package/dist/provider-instrumentation.d.ts.map +1 -1
  15. package/dist/provider.d.ts +5 -5
  16. package/dist/provider.d.ts.map +1 -1
  17. package/dist/provider.js +10 -5
  18. package/dist/provider.js.map +1 -1
  19. package/dist/redaction.d.ts +1 -1
  20. package/dist/redaction.d.ts.map +1 -1
  21. package/dist/redaction.js +1 -0
  22. package/dist/redaction.js.map +1 -1
  23. package/dist/routes.d.ts +2 -2
  24. package/dist/routes.d.ts.map +1 -1
  25. package/dist/routes.js +3 -3
  26. package/dist/routes.js.map +1 -1
  27. package/dist/ui-model.d.ts +38 -0
  28. package/dist/ui-model.d.ts.map +1 -0
  29. package/dist/ui-model.js +1039 -0
  30. package/dist/ui-model.js.map +1 -0
  31. package/dist/ui.d.ts +1 -1
  32. package/dist/ui.d.ts.map +1 -1
  33. package/dist/ui.js +310 -164
  34. package/dist/ui.js.map +1 -1
  35. package/dist/watchers.d.ts +2 -2
  36. package/dist/watchers.d.ts.map +1 -1
  37. package/dist/watchers.js +10 -2
  38. package/dist/watchers.js.map +1 -1
  39. package/package.json +22 -2
  40. package/src/events.ts +25 -1
  41. package/src/index.ts +13 -15
  42. package/src/persistence.ts +1 -1
  43. package/src/provider-instrumentation.ts +1 -1
  44. package/src/provider.ts +12 -7
  45. package/src/redaction.ts +2 -1
  46. package/src/routes.ts +4 -4
  47. package/src/ui-model.ts +1233 -0
  48. package/src/ui.ts +310 -164
  49. package/src/watchers.ts +12 -3
  50. package/dist/audit.d.ts +0 -31
  51. package/dist/audit.d.ts.map +0 -1
  52. package/dist/audit.js +0 -55
  53. package/dist/audit.js.map +0 -1
  54. package/dist/instrumentation.d.ts +0 -114
  55. package/dist/instrumentation.d.ts.map +0 -1
  56. package/dist/instrumentation.js +0 -303
  57. package/dist/instrumentation.js.map +0 -1
  58. package/dist/trace-context.d.ts +0 -80
  59. package/dist/trace-context.d.ts.map +0 -1
  60. package/dist/trace-context.js +0 -92
  61. package/dist/trace-context.js.map +0 -1
  62. package/src/audit.ts +0 -92
  63. package/src/instrumentation.ts +0 -491
  64. package/src/trace-context.ts +0 -166
package/src/ui.ts CHANGED
@@ -2,7 +2,8 @@ import {
2
2
  type DevtoolsRouteAccessOptions,
3
3
  devtoolsNotFoundResponse,
4
4
  isDevtoolsRouteEnabled,
5
- } from "./access";
5
+ } from "./access.js";
6
+ import { devtoolsPanelModelBrowserSource } from "./ui-model.js";
6
7
 
7
8
  /**
8
9
  * Devtools UI handler
@@ -98,6 +99,22 @@ button.danger:hover{border-color:rgba(225,29,72,.24);background:#fff1f2}
98
99
  .detail-title{color:var(--text);font-family:var(--font-sans);font-size:12px;font-weight:650;margin-bottom:6px}
99
100
  .correlated{display:flex;flex-direction:column;gap:6px}
100
101
  .correlated-row{display:flex;gap:7px;align-items:flex-start;color:var(--text-muted)}
102
+ .request-map{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px;margin-bottom:12px}
103
+ .request-fact{border:1px solid var(--border);border-radius:7px;background:var(--surface);padding:9px 10px;min-width:0}
104
+ .request-fact-label{font-size:10px;color:var(--text-muted);text-transform:uppercase;letter-spacing:.1em}
105
+ .request-fact-value{font-family:var(--font-mono);font-size:12px;color:var(--text);margin-top:4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
106
+ .detail-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px;margin-bottom:10px}
107
+ .lifecycle{display:flex;flex-direction:column;gap:7px}
108
+ .phase-row{display:grid;grid-template-columns:108px minmax(130px,.8fr) minmax(180px,1.2fr) 92px;gap:8px;align-items:start;border:1px solid var(--border);border-radius:7px;background:var(--bg);padding:8px}
109
+ .phase-name{font-family:var(--font-mono);font-size:11px;color:var(--text)}
110
+ .phase-summary{color:var(--text-muted);font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
111
+ .phase-meta{font-family:var(--font-mono);font-size:10px;color:var(--text-dim)}
112
+ .owner-pill{display:inline-block;border:1px solid var(--border);border-radius:999px;background:var(--surface-2);color:var(--text-muted);font-family:var(--font-mono);font-size:10px;padding:2px 6px;line-height:1.4}
113
+ .owner-framework,.owner-provider,.owner-devtools{color:var(--red);background:#fff1f2;border-color:#ffe4e6}
114
+ .owner-route{color:var(--green);background:#ecfdf5;border-color:#d1fae5}
115
+ .owner-transport,.owner-client{color:var(--blue);background:#eff6ff;border-color:#dbeafe}
116
+ .owner-job,.owner-schedule,.owner-outbox{color:var(--orange);background:#fff7ed;border-color:#fed7aa}
117
+ .redaction-note{border:1px solid var(--border);border-radius:7px;background:var(--surface);padding:8px 10px;color:var(--text-muted);font-size:12px;margin-top:8px}
101
118
  .focus-panel{display:flex;flex-direction:column}
102
119
  .focus-head{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;padding:14px;border-bottom:1px solid var(--border);background:var(--bg)}
103
120
  .focus-title{font-size:15px;line-height:1.2;font-weight:720;color:var(--text)}
@@ -107,7 +124,7 @@ button.danger:hover{border-color:rgba(225,29,72,.24);background:#fff1f2}
107
124
  .focus-metric-label{font-size:10px;color:var(--text-muted);text-transform:uppercase;letter-spacing:.1em}
108
125
  .focus-metric-value{font-family:var(--font-mono);font-size:15px;color:var(--text);margin-top:3px}
109
126
  .event-table{display:flex;flex-direction:column}
110
- .table-row{display:grid;grid-template-columns:86px minmax(140px,.9fr) minmax(220px,1.4fr) 96px 112px;gap:10px;align-items:start;padding:11px 14px;border-bottom:1px solid var(--border);cursor:pointer;transition:background .1s}
127
+ .table-row{display:grid;grid-template-columns:86px minmax(140px,.8fr) minmax(210px,1.2fr) minmax(130px,.8fr) 78px 104px;gap:10px;align-items:start;padding:11px 14px;border-bottom:1px solid var(--border);cursor:pointer;transition:background .1s}
111
128
  .table-row:last-child{border-bottom:0}
112
129
  .table-row:hover{background:var(--surface)}
113
130
  .table-row.open{background:#f8fafc}
@@ -115,8 +132,31 @@ button.danger:hover{border-color:rgba(225,29,72,.24);background:#fff1f2}
115
132
  .table-cell strong{display:block;color:var(--text);font-size:13px;font-weight:650;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
116
133
  .table-cell code{font-family:var(--font-mono);font-size:11px;color:var(--text-muted);background:var(--surface-2);border:1px solid var(--border);border-radius:4px;padding:1px 4px}
117
134
  .table-summary{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
135
+ .table-correlation{display:flex;flex-wrap:wrap;gap:4px}
136
+ .table-empty{font-family:var(--font-mono);font-size:10px;color:var(--text-dim)}
118
137
  .table-detail{display:none;grid-column:1/-1;margin-top:2px;padding-top:10px;border-top:1px solid var(--border);font-family:var(--font-mono);font-size:11px;color:var(--text-muted);line-height:1.7;white-space:pre-wrap;word-break:break-word}
119
138
  .table-row.open .table-detail{display:block}
139
+ .table-detail-note{font-family:var(--font-sans);font-size:12px;color:var(--text-muted);margin-bottom:8px;white-space:normal}
140
+ .result-chip{display:inline-block;border:1px solid var(--border);border-radius:5px;background:var(--surface);font-family:var(--font-mono);font-size:10px;color:var(--text-muted);padding:2px 5px}
141
+ .result-failed,.result-blocked,.result-deadLettered{color:var(--red);background:#fff1f2;border-color:#ffe4e6}
142
+ .result-retryScheduled{color:var(--orange);background:#fff7ed;border-color:#fed7aa}
143
+ .result-delivered,.result-completed,.result-allowed,.result-hit,.result-signed_in{color:var(--green);background:#ecfdf5;border-color:#d1fae5}
144
+ .error-board{display:flex;flex-direction:column}
145
+ .error-owner-strip{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px;padding:14px;border-bottom:1px solid var(--border);background:var(--bg)}
146
+ .error-owner-card{border:1px solid var(--border);border-radius:7px;background:var(--surface);padding:9px 10px;min-width:0}
147
+ .error-owner-card strong{display:block;font-family:var(--font-mono);font-size:15px;color:var(--text)}
148
+ .error-owner-card span{display:block;color:var(--text-muted);font-size:10px;text-transform:uppercase;letter-spacing:.1em;margin-top:2px}
149
+ .error-group{border-bottom:1px solid var(--border)}
150
+ .error-group:last-child{border-bottom:0}
151
+ .error-group-head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 14px;background:var(--surface)}
152
+ .error-group-title{display:flex;align-items:center;gap:7px;font-size:13px;font-weight:700;color:var(--text)}
153
+ .error-group-count{font-family:var(--font-mono);font-size:11px;color:var(--text-muted)}
154
+ .error-row{display:grid;grid-template-columns:86px minmax(170px,1fr) 90px minmax(150px,.9fr) minmax(140px,.8fr);gap:10px;align-items:start;padding:11px 14px;border-top:1px solid var(--border);cursor:pointer;transition:background .1s}
155
+ .error-row:hover{background:var(--surface)}
156
+ .error-row.open{background:#f8fafc}
157
+ .error-message{min-width:0;color:var(--text);font-size:13px;font-weight:650;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
158
+ .error-context{font-family:var(--font-mono);font-size:11px;color:var(--text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
159
+ .error-related{display:flex;flex-direction:column;gap:5px;margin-top:8px}
120
160
  .empty{text-align:center;padding:58px 16px;color:var(--text-dim);font-size:13px;background:var(--bg)}
121
161
  .badge{display:inline-block;font-size:10px;font-weight:650;padding:2px 6px;border-radius:4px;text-transform:uppercase;letter-spacing:.06em;line-height:1.4;border:1px solid transparent}
122
162
  .badge-request{background:#eff6ff;color:var(--blue);border-color:#dbeafe}
@@ -124,6 +164,7 @@ button.danger:hover{border-color:rgba(225,29,72,.24);background:#fff1f2}
124
164
  .badge-usecase{background:#f5f3ff;color:var(--purple);border-color:#ede9fe}
125
165
  .badge-eventBus{background:#ecfdf5;color:var(--green);border-color:#d1fae5}
126
166
  .badge-job{background:#fff7ed;color:var(--orange);border-color:#fed7aa}
167
+ .badge-outbox{background:#fefce8;color:#a16207;border-color:#fef08a}
127
168
  .badge-schedule{background:#f0f9ff;color:#0369a1;border-color:#bae6fd}
128
169
  .badge-provider{background:#f8fafc;color:var(--gray);border-color:var(--border)}
129
170
  .badge-custom{background:#f0fdfa;color:#0f766e;border-color:#ccfbf1}
@@ -150,9 +191,14 @@ button.danger:hover{border-color:rgba(225,29,72,.24);background:#fff1f2}
150
191
  .event-time{min-width:0}
151
192
  .event-summary{white-space:normal}
152
193
  .request-detail{grid-template-columns:1fr}
194
+ .request-map{grid-template-columns:repeat(2,minmax(0,1fr))}
195
+ .detail-grid{grid-template-columns:repeat(2,minmax(0,1fr))}
196
+ .phase-row{grid-template-columns:1fr;gap:5px}
153
197
  .focus-head{flex-direction:column}
154
198
  .focus-metrics{grid-template-columns:repeat(2,minmax(0,1fr));min-width:0;width:100%}
155
199
  .table-row{grid-template-columns:1fr;gap:6px}
200
+ .error-owner-strip{grid-template-columns:repeat(2,minmax(0,1fr))}
201
+ .error-row{grid-template-columns:1fr;gap:6px}
156
202
  .table-detail{grid-column:auto}
157
203
  }
158
204
  </style>
@@ -163,7 +209,7 @@ button.danger:hover{border-color:rgba(225,29,72,.24);background:#fff1f2}
163
209
  <div>
164
210
  <div class="brand"><span class="mark">B</span><span>Beignet</span></div>
165
211
  <h1>Beignet <span>Devtools</span></h1>
166
- <div class="subtitle">A local timeline for requests, use cases, errors, jobs, schedules, and provider activity.</div>
212
+ <div class="subtitle">A local timeline for requests, use cases, errors, jobs, outbox delivery, schedules, and provider activity.</div>
167
213
  </div>
168
214
  <div class="controls">
169
215
  <span class="status"><span class="dot" id="statusDot"></span><span id="statusText">Connecting</span></span>
@@ -218,6 +264,7 @@ button.danger:hover{border-color:rgba(225,29,72,.24);background:#fff1f2}
218
264
  {id:"errors",label:"Errors",type:"error",watcher:"errors"},
219
265
  {id:"events",label:"Events",type:"eventBus",watcher:"eventBus"},
220
266
  {id:"jobs",label:"Jobs",type:"job",watcher:"jobs"},
267
+ {id:"outbox",label:"Outbox",watcher:"outbox",watcherName:"outbox"},
221
268
  {id:"schedules",label:"Schedules",type:"schedule",watcher:"schedules"},
222
269
  {id:"providers",label:"Providers",type:"provider",watcher:"providers"},
223
270
  {id:"db",label:"Database",type:"custom",watcher:"db",watcherName:"db"},
@@ -231,8 +278,9 @@ button.danger:hover{border-color:rgba(225,29,72,.24);background:#fff1f2}
231
278
  {id:"rateLimit",label:"Rate limits",type:"custom",watcher:"rateLimit",watcherName:"rateLimit"},
232
279
  {id:"custom",label:"Custom",type:"custom",watcher:"custom"}
233
280
  ];
234
- const FOCUS_PANEL_TABS={events:true,jobs:true,schedules:true,db:true,cache:true,storage:true,uploads:true,mail:true,notifications:true,auth:true,audit:true,rateLimit:true};
235
- const BUILT_IN_WATCHERS={requests:true,errors:true,useCases:true,eventBus:true,jobs:true,schedules:true,providers:true,db:true,cache:true,storage:true,uploads:true,mail:true,notifications:true,auth:true,audit:true,rateLimit:true,custom:true};
281
+ const FOCUS_PANEL_TABS={events:true,jobs:true,outbox:true,schedules:true,db:true,cache:true,storage:true,uploads:true,mail:true,notifications:true,auth:true,audit:true,rateLimit:true};
282
+ const BUILT_IN_WATCHERS={requests:true,errors:true,useCases:true,eventBus:true,jobs:true,outbox:true,schedules:true,providers:true,db:true,cache:true,storage:true,uploads:true,mail:true,notifications:true,auth:true,audit:true,rateLimit:true,custom:true};
283
+ const ERROR_OWNERS=["route","framework","provider","job","schedule","outbox","client","devtools","unknown"];
236
284
  let events=[];
237
285
  let watchers=[];
238
286
  let activeTab=localStorage.getItem("beignet-devtools-tab")||"timeline";
@@ -299,6 +347,7 @@ button.danger:hover{border-color:rgba(225,29,72,.24);background:#fff1f2}
299
347
  case "usecase": return e.name+" ("+e.phase+")"+(e.durationMs!==undefined?" "+e.durationMs+"ms":"");
300
348
  case "eventBus": return e.eventName||"Domain event";
301
349
  case "job": return e.jobName+" -> "+e.status;
350
+ case "outbox": return e.messageName+" ("+e.messageKind+") -> "+e.status;
302
351
  case "schedule": return e.scheduleName+" -> "+e.status;
303
352
  case "provider": return e.providerName+" -> "+e.action;
304
353
  case "custom": return (e.label||e.name||"Custom event")+(e.summary?" - "+e.summary:"");
@@ -307,6 +356,7 @@ button.danger:hover{border-color:rgba(225,29,72,.24);background:#fff1f2}
307
356
  }
308
357
 
309
358
  function tabMatchesEvent(tab,event){
359
+ if(tab.id==="errors")return event.type==="error"||failureFor(event);
310
360
  if(tab.type&&event.type!==tab.type)return false;
311
361
  if(tab.watcherName&&event.watcher!==tab.watcherName)return false;
312
362
  return true;
@@ -320,6 +370,7 @@ button.danger:hover{border-color:rgba(225,29,72,.24);background:#fff1f2}
320
370
  case "usecase": return "useCases";
321
371
  case "eventBus": return "eventBus";
322
372
  case "job": return "jobs";
373
+ case "outbox": return "outbox";
323
374
  case "schedule": return "schedules";
324
375
  case "provider": return "providers";
325
376
  case "custom": return "custom";
@@ -397,6 +448,9 @@ button.danger:hover{border-color:rgba(225,29,72,.24);background:#fff1f2}
397
448
  event.summary,
398
449
  event.eventName,
399
450
  event.jobName,
451
+ event.messageId,
452
+ event.messageKind,
453
+ event.messageName,
400
454
  event.providerName,
401
455
  event.action,
402
456
  jsonSearch(event.details)
@@ -432,6 +486,164 @@ button.danger:hover{border-color:rgba(225,29,72,.24);background:#fff1f2}
432
486
  });
433
487
  }
434
488
 
489
+ function eventTimeValue(event){
490
+ const time=Date.parse(event.timestamp||"");
491
+ return Number.isFinite(time)?time:0;
492
+ }
493
+
494
+ function responseOwnerFor(event){
495
+ const details=detailsObject(event);
496
+ const response=details.response&&typeof details.response==="object"?details.response:{};
497
+ return event.responseOwner||event.owner||response.owner||"unknown";
498
+ }
499
+
500
+ function ownerClass(owner){
501
+ return "owner-"+String(owner||"unknown").replace(/[^a-zA-Z0-9_-]/g,"");
502
+ }
503
+
504
+ function ownerPill(owner){
505
+ const value=owner||"unknown";
506
+ return '<span class="owner-pill '+esc(ownerClass(value))+'">'+esc(value)+'</span>';
507
+ }
508
+
509
+ function categoryFor(event){
510
+ const watcher=inferredWatcher(event);
511
+ if(event.type==="request")return"HTTP";
512
+ if(event.type==="usecase")return"Use case";
513
+ if(event.type==="error")return"Error";
514
+ if(event.type==="provider")return"Provider";
515
+ if(event.type==="eventBus")return"Domain event";
516
+ if(event.type==="outbox")return"Outbox";
517
+ if(event.type==="job")return"Job";
518
+ if(event.type==="schedule")return"Schedule";
519
+ if(["db","cache","storage","uploads","mail","notifications","auth","audit","rateLimit"].indexOf(watcher)>=0)return"Provider work";
520
+ return watcher==="custom"?"Custom":"Provider work";
521
+ }
522
+
523
+ function ownerForEvent(event){
524
+ if(event.owner)return event.owner;
525
+ const watcher=inferredWatcher(event);
526
+ if(event.type==="error")return responseOwnerFor(event);
527
+ if(event.type==="provider")return"provider";
528
+ if(event.type==="job")return"job";
529
+ if(event.type==="schedule")return"schedule";
530
+ if(event.type==="outbox")return"outbox";
531
+ if(["db","cache","storage","uploads","mail","notifications","auth","audit","rateLimit"].indexOf(watcher)>=0)return"provider";
532
+ if(event.type==="request")return responseOwnerFor(event);
533
+ return"route";
534
+ }
535
+
536
+ function relatedEventsFor(event){
537
+ return events.filter(function(candidate){
538
+ if(candidate.id===event.id)return false;
539
+ if(event.traceId&&candidate.traceId===event.traceId)return true;
540
+ return Boolean(event.requestId)&&candidate.requestId===event.requestId;
541
+ });
542
+ }
543
+
544
+ function requestForEvent(event){
545
+ return relatedEventsFor(event).filter(function(candidate){return candidate.type==="request"}).sort(function(a,b){return eventTimeValue(b)-eventTimeValue(a)})[0];
546
+ }
547
+
548
+ function statusForEvent(event){
549
+ if(event.status!==undefined)return event.status;
550
+ const request=requestForEvent(event);
551
+ if(request&&request.status!==undefined)return request.status;
552
+ const details=detailsObject(event);
553
+ const response=details.response&&typeof details.response==="object"?details.response:{};
554
+ return response.status||"";
555
+ }
556
+
557
+ function contractForEvent(event){
558
+ const request=requestForEvent(event);
559
+ return event.contractName||event.useCaseName||(request&&request.contractName)||"";
560
+ }
561
+
562
+ function pathForEvent(event){
563
+ const request=requestForEvent(event);
564
+ return event.path||(request&&request.path)||"";
565
+ }
566
+
567
+ function correlationFor(event){
568
+ const rid=event.requestId?'<span class="request-id">'+esc(event.requestId)+'</span>':"";
569
+ const trace=event.traceId?'<span class="trace-id">trace '+esc(event.traceId.slice(0,8))+'</span>':"";
570
+ return rid+trace||'<span class="table-empty">n/a</span>';
571
+ }
572
+
573
+ function unsafeMetadataPaths(value,path){
574
+ if(!value||typeof value!=="object")return[];
575
+ return Object.entries(value).flatMap(function(entry){
576
+ const key=entry[0];
577
+ const nested=entry[1];
578
+ const next=path.concat(key);
579
+ const lower=key.toLowerCase();
580
+ const sensitive=lower==="authorization"||lower==="cookie"||lower==="set-cookie"||lower.indexOf("token")>=0||lower.indexOf("password")>=0||lower.indexOf("secret")>=0||lower.indexOf("credential")>=0;
581
+ if(sensitive&&nested!=="[redacted]")return[next.join(".")];
582
+ return unsafeMetadataPaths(nested,next);
583
+ });
584
+ }
585
+
586
+ function requestFact(label,value){
587
+ const display=value===undefined||value===null||value===""?"n/a":value;
588
+ return '<div class="request-fact"><div class="request-fact-label">'+esc(label)+'</div><div class="request-fact-value" title="'+esc(display)+'">'+esc(display)+'</div></div>';
589
+ }
590
+
591
+ function detailGrid(items){
592
+ return '<div class="detail-grid">'+items.map(function(item){return requestFact(item[0],item[1])}).join("")+'</div>';
593
+ }
594
+
595
+ function requestOverview(request,related){
596
+ return '<div class="request-map">'+
597
+ requestFact("Route",request.contractName||"unmatched")+
598
+ requestFact("Method",request.method||"")+
599
+ requestFact("Path",request.path||"")+
600
+ requestFact("Status",request.status||"")+
601
+ requestFact("Duration",request.durationMs!==undefined?request.durationMs+"ms":"")+
602
+ requestFact("Owner",responseOwnerFor(request))+
603
+ requestFact("Trace",request.traceId||"")+
604
+ requestFact("Related",related.length)+
605
+ '</div>';
606
+ }
607
+
608
+ function requestLifecycleRows(request,related){
609
+ const rows=[request].concat(related).sort(function(a,b){return eventTimeValue(a)-eventTimeValue(b)});
610
+ return rows.map(function(event){
611
+ const duration=durationFor(event);
612
+ const durationLabel=duration===undefined?"":duration+"ms";
613
+ return '<div class="phase-row">'+
614
+ '<div>'+eventBadge(event)+'</div>'+
615
+ '<div><strong class="phase-name">'+esc(categoryFor(event))+'</strong><div class="phase-meta">'+esc(durationLabel||inferredWatcher(event))+'</div></div>'+
616
+ '<div class="phase-summary" title="'+esc(summarize(event))+'">'+esc(summarize(event))+'</div>'+
617
+ '<div>'+ownerPill(ownerForEvent(event))+'</div>'+
618
+ '</div>';
619
+ }).join("");
620
+ }
621
+
622
+ function groupedRelatedRows(related){
623
+ const groups=["Use case","Provider work","Domain event","Outbox","Job","Schedule","Error","Provider","Custom"];
624
+ return groups.map(function(group){
625
+ const groupEvents=related.filter(function(event){return categoryFor(event)===group});
626
+ if(!groupEvents.length)return"";
627
+ const rows=groupEvents.slice().sort(function(a,b){return eventTimeValue(a)-eventTimeValue(b)}).map(function(event){
628
+ return '<div class="correlated-row">'+eventBadge(event)+'<span>'+esc(summarize(event))+'</span>'+ownerPill(ownerForEvent(event))+'</div>';
629
+ }).join("");
630
+ return '<section class="detail-section"><div class="detail-title">'+esc(group)+'</div><div class="correlated">'+rows+'</div></section>';
631
+ }).join("");
632
+ }
633
+
634
+ function redactionNote(request,related){
635
+ const all=[request].concat(related);
636
+ const redacted=all.some(function(event){return hasRedactedValue(event)});
637
+ const unsafe=Array.from(new Set(all.flatMap(function(event){return unsafeMetadataPaths(event,[])}))).slice(0,4);
638
+ if(unsafe.length){
639
+ return '<div class="redaction-note">Unsafe-looking metadata keys remain visible: '+esc(unsafe.join(", "))+'</div>';
640
+ }
641
+ if(redacted){
642
+ return '<div class="redaction-note">Sensitive metadata was redacted before storage.</div>';
643
+ }
644
+ return '<div class="redaction-note">No redacted fields detected in the stored events for this request.</div>';
645
+ }
646
+
435
647
  function updateStats(){
436
648
  $statTotal.textContent=String(events.length);
437
649
  $statRequests.textContent=String(events.filter(function(event){return event.type==="request"}).length);
@@ -504,179 +716,49 @@ button.danger:hover{border-color:rgba(225,29,72,.24);background:#fff1f2}
504
716
  function renderRequests(list){
505
717
  return list.slice().reverse().map(function(request){
506
718
  const related=correlatedEvents(request);
507
- const relatedHtml=related.length
508
- ? related.slice().reverse().map(function(event){
509
- return '<div class="correlated-row">'+eventBadge(event)+'<span>'+esc(summarize(event))+'</span></div>';
510
- }).join("")
511
- : '<div class="empty">No correlated events for this request.</div>';
512
719
  const detail='<div class="request-detail">'+
513
- '<section class="detail-section"><div class="detail-title">Request</div><pre>'+json(request)+'</pre></section>'+
514
- '<section class="detail-section"><div class="detail-title">Correlated events</div><div class="correlated">'+relatedHtml+'</div></section>'+
720
+ '<section class="detail-section"><div class="detail-title">Request lifecycle</div>'+requestOverview(request,related)+'<div class="lifecycle">'+requestLifecycleRows(request,related)+'</div>'+redactionNote(request,related)+'</section>'+
721
+ '<section class="detail-section"><div class="detail-title">Correlated activity</div>'+(related.length?groupedRelatedRows(related):'<div class="empty">No correlated events for this request.</div>')+'<pre>'+json(request)+'</pre></section>'+
515
722
  '</div>';
516
723
  return eventRow(request,detail);
517
724
  }).join("");
518
725
  }
519
726
 
520
- function detailsObject(event){
521
- return event.details&&typeof event.details==="object"&&!Array.isArray(event.details)?event.details:{};
522
- }
523
-
524
- function detailValue(event,key){
525
- return detailsObject(event)[key];
526
- }
527
-
528
- function durationFor(event){
529
- const detailDuration=detailValue(event,"durationMs");
530
- const duration=event.durationMs!==undefined?event.durationMs:detailDuration;
531
- return typeof duration==="number"&&Number.isFinite(duration)?duration:undefined;
532
- }
533
-
534
- function providerFor(event){
535
- const details=detailsObject(event);
536
- return details.providerName||details.provider||details.portName||"";
537
- }
538
-
539
- function failureFor(event){
540
- const details=detailsObject(event);
541
- if(event.type==="error")return true;
542
- if(typeof event.status==="string")return event.status==="failed";
543
- if(typeof event.name==="string"&&event.name.indexOf("failed")>=0)return true;
544
- return Boolean(details.error)||details.allowed===false;
545
- }
546
-
547
- function averageDuration(list){
548
- const durations=list.map(durationFor).filter(function(value){return value!==undefined});
549
- if(!durations.length)return "n/a";
550
- const total=durations.reduce(function(sum,value){return sum+value},0);
551
- return Math.round(total/durations.length)+"ms";
552
- }
553
-
554
- function distinctCount(list,selector){
555
- const values=new Set();
556
- list.forEach(function(event){
557
- const value=selector(event);
558
- if(value!==undefined&&value!==null&&String(value)!=="")values.add(String(value));
559
- });
560
- return values.size;
561
- }
562
-
563
727
  function metric(label,value){
564
728
  return '<div class="focus-metric"><div class="focus-metric-label">'+esc(label)+'</div><div class="focus-metric-value">'+esc(value)+'</div></div>';
565
729
  }
566
730
 
567
- function panelMeta(tab){
568
- switch(tab.id){
569
- case "events": return {title:"Domain events",subtitle:"Published facts and event bus activity"};
570
- case "jobs": return {title:"Jobs",subtitle:"Background job lifecycle"};
571
- case "schedules": return {title:"Schedules",subtitle:"Cron and scheduled task runs"};
572
- case "db": return {title:"Database",subtitle:"Queries and database provider diagnostics"};
573
- case "cache": return {title:"Cache",subtitle:"Reads, writes, deletes, and hit rates"};
574
- case "storage": return {title:"Storage",subtitle:"Object storage operations"};
575
- case "uploads": return {title:"Uploads",subtitle:"Upload preparation, signing, and completion"};
576
- case "mail": return {title:"Mail",subtitle:"Delivery attempts and provider results"};
577
- case "notifications": return {title:"Notifications",subtitle:"Notification intent and channel delivery"};
578
- case "auth": return {title:"Auth",subtitle:"Session and authentication activity"};
579
- case "audit": return {title:"Audit",subtitle:"Durable activity records emitted by application code"};
580
- case "rateLimit": return {title:"Rate limits",subtitle:"Allowed, blocked, and failed checks"};
581
- default: return {title:tab.label,subtitle:"Subsystem activity"};
582
- }
583
- }
731
+ ${devtoolsPanelModelBrowserSource()}
584
732
 
733
+ function summarize(e){return summarizeDevtoolsEvent(e);}
734
+ function inferredWatcher(event){return inferDevtoolsWatcher(event);}
735
+ function eventBadgeLabel(event){return devtoolsEventBadgeLabel(event);}
736
+ function eventBadgeClass(event){return devtoolsEventBadgeClass(event);}
737
+ function durationFor(event){return devtoolsEventDuration(event);}
738
+ function providerFor(event){return devtoolsEventProvider(event);}
739
+ function failureFor(event){return devtoolsEventFailure(event);}
740
+ function hasRedactedValue(value){return hasDevtoolsRedactedValue(value);}
741
+ function panelMeta(tab){return devtoolsPanelMeta(tab.id,tab.label);}
585
742
  function panelMetrics(tab,list){
586
- switch(tab.id){
587
- case "events":
588
- return [
589
- metric("Events",list.length),
590
- metric("Names",distinctCount(list,function(event){return event.eventName})),
591
- metric("Traces",distinctCount(list,function(event){return event.traceId})),
592
- metric("Requests",distinctCount(list,function(event){return event.requestId}))
593
- ].join("");
594
- case "jobs":
595
- return [
596
- metric("Jobs",list.length),
597
- metric("Failed",list.filter(failureFor).length),
598
- metric("Names",distinctCount(list,function(event){return event.jobName})),
599
- metric("Providers",distinctCount(list,providerFor))
600
- ].join("");
601
- case "schedules":
602
- return [
603
- metric("Runs",list.length),
604
- metric("Failed",list.filter(failureFor).length),
605
- metric("Names",distinctCount(list,function(event){return event.scheduleName})),
606
- metric("Cron",distinctCount(list,function(event){return event.cron}))
607
- ].join("");
608
- case "cache":
609
- return [
610
- metric("Operations",list.length),
611
- metric("Hits",list.filter(function(event){return detailValue(event,"hit")===true}).length),
612
- metric("Failures",list.filter(failureFor).length),
613
- metric("Avg",averageDuration(list))
614
- ].join("");
615
- case "auth":
616
- return [
617
- metric("Events",list.length),
618
- metric("Authenticated",list.filter(function(event){return detailValue(event,"authenticated")===true}).length),
619
- metric("Failures",list.filter(failureFor).length),
620
- metric("Avg",averageDuration(list))
621
- ].join("");
622
- case "audit":
623
- return [
624
- metric("Entries",list.length),
625
- metric("Failures",list.filter(function(event){return detailValue(event,"outcome")==="failure"}).length),
626
- metric("Actors",distinctCount(list,function(event){const actor=detailValue(event,"actor");return actor&&actor.id})),
627
- metric("Resources",distinctCount(list,function(event){const resource=detailValue(event,"resource");return resource&&resource.id}))
628
- ].join("");
629
- case "rateLimit":
630
- return [
631
- metric("Checks",list.length),
632
- metric("Allowed",list.filter(function(event){return detailValue(event,"allowed")===true}).length),
633
- metric("Blocked",list.filter(function(event){return detailValue(event,"allowed")===false}).length),
634
- metric("Avg",averageDuration(list))
635
- ].join("");
636
- default:
637
- return [
638
- metric("Events",list.length),
639
- metric("Failures",list.filter(failureFor).length),
640
- metric("Providers",distinctCount(list,providerFor)),
641
- metric("Avg",averageDuration(list))
642
- ].join("");
643
- }
743
+ return devtoolsPanelMetrics(tab.id,list).map(function(item){
744
+ return metric(item.label,item.value);
745
+ }).join("");
644
746
  }
645
-
646
- function primaryFor(event){
647
- switch(event.type){
648
- case "eventBus": return event.eventName||"Domain event";
649
- case "job": return event.jobName||"Job";
650
- case "schedule": return event.scheduleName||"Schedule";
651
- case "custom": return event.label||event.name||"Custom event";
652
- default: return summarize(event);
653
- }
747
+ function primaryFor(event){return devtoolsPanelPrimary(tabFor(activeTab).id,event);}
748
+ function secondaryFor(event){return devtoolsPanelSecondary(tabFor(activeTab).id,event);}
749
+ function resultFor(event){return devtoolsPanelResult(event);}
750
+ function tableDetailFor(tab,event){
751
+ const detail=devtoolsPanelDetail(tab.id,event);
752
+ return detailGrid(detail.fields.map(function(item){return [item.label,item.value]}))+
753
+ '<div class="table-detail-note">'+esc(detail.note)+'</div><pre>'+json(event)+'</pre>';
654
754
  }
655
755
 
656
- function secondaryFor(event){
657
- const details=detailsObject(event);
658
- if(details.operation)return details.operation;
659
- if(event.type==="job")return event.status;
660
- if(event.type==="schedule")return event.status;
661
- if(event.type==="eventBus")return event.requestId||event.traceId||"";
662
- if(event.name)return event.name;
663
- return inferredWatcher(event);
756
+ function resultChip(value){
757
+ const normalized=String(value||"").replace(/[^a-zA-Z0-9_-]/g,"_");
758
+ return '<span class="result-chip result-'+esc(normalized)+'">'+esc(value||"n/a")+'</span>';
664
759
  }
665
760
 
666
- function resultFor(event){
667
- const details=detailsObject(event);
668
- if(details.allowed===true)return "allowed";
669
- if(details.allowed===false)return "blocked";
670
- if(details.hit===true)return "hit";
671
- if(details.hit===false)return "miss";
672
- if(details.authenticated===true)return "signed in";
673
- if(details.authenticated===false)return "guest";
674
- if(event.status)return event.status;
675
- if(details.error)return "failed";
676
- return providerFor(event)||inferredWatcher(event);
677
- }
678
-
679
- function tableRow(event){
761
+ function tableRow(tab,event){
680
762
  const time=event.timestamp?new Date(event.timestamp).toLocaleTimeString():"";
681
763
  const duration=durationFor(event);
682
764
  const durationLabel=duration===undefined?"":duration+"ms";
@@ -685,15 +767,16 @@ button.danger:hover{border-color:rgba(225,29,72,.24);background:#fff1f2}
685
767
  '<div class="table-cell">'+esc(time)+'</div>'+
686
768
  '<div class="table-cell"><strong>'+esc(primaryFor(event))+'</strong><code>'+esc(secondaryFor(event))+'</code></div>'+
687
769
  '<div class="table-cell table-summary">'+esc(summarize(event))+'</div>'+
770
+ '<div class="table-cell table-correlation">'+correlationFor(event)+'</div>'+
688
771
  '<div class="table-cell">'+esc(durationLabel)+'</div>'+
689
- '<div class="table-cell">'+esc(resultFor(event)||provider)+'</div>'+
690
- '<pre class="table-detail">'+json(event)+'</pre>'+
772
+ '<div class="table-cell">'+resultChip(resultFor(event)||provider)+'</div>'+
773
+ '<div class="table-detail">'+tableDetailFor(tab,event)+'</div>'+
691
774
  '</article>';
692
775
  }
693
776
 
694
777
  function renderFocusPanel(tab,list){
695
778
  const meta=panelMeta(tab);
696
- const rows=list.slice().reverse().map(tableRow).join("");
779
+ const rows=list.slice().reverse().map(function(event){return tableRow(tab,event)}).join("");
697
780
  return '<section class="focus-panel">'+
698
781
  '<div class="focus-head">'+
699
782
  '<div><h2 class="focus-title">'+esc(meta.title)+'</h2><div class="focus-subtitle">'+esc(meta.subtitle)+'</div></div>'+
@@ -703,6 +786,65 @@ button.danger:hover{border-color:rgba(225,29,72,.24);background:#fff1f2}
703
786
  '</section>';
704
787
  }
705
788
 
789
+ function renderErrorOwners(list){
790
+ const owners=ERROR_OWNERS.map(function(owner){
791
+ return {owner:owner,count:list.filter(function(event){return ownerForEvent(event)===owner}).length};
792
+ }).filter(function(item){return item.count>0});
793
+ const cards=(owners.length?owners:[{owner:"none",count:0}]).map(function(item){
794
+ return '<div class="error-owner-card"><strong>'+esc(item.count)+'</strong><span>'+esc(item.owner)+'</span></div>';
795
+ }).join("");
796
+ return '<div class="error-owner-strip">'+cards+'</div>';
797
+ }
798
+
799
+ function renderErrorRelated(event){
800
+ const related=relatedEventsFor(event).filter(function(candidate){return candidate.type!=="error"}).slice().sort(function(a,b){return eventTimeValue(a)-eventTimeValue(b)}).slice(0,8);
801
+ if(!related.length)return '<div class="empty">No related request, provider, or side-effect events.</div>';
802
+ return '<div class="error-related">'+related.map(function(candidate){
803
+ return '<div class="correlated-row">'+eventBadge(candidate)+'<span>'+esc(summarize(candidate))+'</span>'+ownerPill(ownerForEvent(candidate))+'</div>';
804
+ }).join("")+'</div>';
805
+ }
806
+
807
+ function failureMessageFor(event){
808
+ if(event.message)return event.message;
809
+ const details=detailsObject(event);
810
+ const detailError=details.error;
811
+ if(typeof detailError==="string"&&detailError)return detailError;
812
+ if(detailError&&typeof detailError==="object"){
813
+ if(typeof detailError.message==="string")return detailError.message;
814
+ return jsonSearch(detailError);
815
+ }
816
+ return summarize(event)||"Unknown failure";
817
+ }
818
+
819
+ function renderErrorRow(event){
820
+ const time=event.timestamp?new Date(event.timestamp).toLocaleTimeString():"";
821
+ const status=statusForEvent(event);
822
+ const route=[contractForEvent(event),pathForEvent(event)].filter(Boolean).join(" ");
823
+ const relatedCount=relatedEventsFor(event).length;
824
+ const message=failureMessageFor(event);
825
+ return '<article class="error-row" onclick="this.classList.toggle(\\'open\\')">'+
826
+ '<div class="table-cell">'+esc(time)+'</div>'+
827
+ '<div><div class="error-message" title="'+esc(message)+'">'+esc(message)+'</div><div class="error-context">'+esc(route||event.useCaseName||primaryFor(event)||"unmatched")+'</div></div>'+
828
+ '<div>'+ownerPill(ownerForEvent(event))+'</div>'+
829
+ '<div class="table-cell table-correlation">'+correlationFor(event)+'</div>'+
830
+ '<div class="table-cell">'+resultChip(status||relatedCount+" related")+'</div>'+
831
+ '<div class="table-detail">'+renderErrorRelated(event)+'<pre>'+json(event)+'</pre></div>'+
832
+ '</article>';
833
+ }
834
+
835
+ function renderErrorsPanel(list){
836
+ const rowsByOwner=ERROR_OWNERS.map(function(owner){
837
+ const ownerEvents=list.filter(function(event){return ownerForEvent(event)===owner});
838
+ if(!ownerEvents.length)return"";
839
+ const rows=ownerEvents.slice().reverse().map(renderErrorRow).join("");
840
+ return '<section class="error-group">'+
841
+ '<div class="error-group-head"><div class="error-group-title">'+ownerPill(owner)+'<span>'+esc(owner)+' failures</span></div><div class="error-group-count">'+esc(ownerEvents.length)+' errors</div></div>'+
842
+ rows+
843
+ '</section>';
844
+ }).join("");
845
+ return '<section class="error-board">'+renderErrorOwners(list)+rowsByOwner+'</section>';
846
+ }
847
+
706
848
  function render(){
707
849
  updateStats();
708
850
  renderTabs();
@@ -719,6 +861,10 @@ button.danger:hover{border-color:rgba(225,29,72,.24);background:#fff1f2}
719
861
  $content.innerHTML=renderRequests(list);
720
862
  return;
721
863
  }
864
+ if(activeTab==="errors"){
865
+ $content.innerHTML=renderErrorsPanel(list);
866
+ return;
867
+ }
722
868
  if(FOCUS_PANEL_TABS[tab.id]){
723
869
  $content.innerHTML=renderFocusPanel(tab,list);
724
870
  return;