@beignet/devtools 0.0.2 → 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 +97 -0
  2. package/README.md +117 -71
  3. package/dist/events.d.ts +16 -4
  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 +314 -163
  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 +18 -2
  38. package/dist/watchers.js.map +1 -1
  39. package/package.json +22 -2
  40. package/src/events.ts +32 -2
  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 +314 -163
  49. package/src/watchers.ts +20 -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/dist/ui.js CHANGED
@@ -1,4 +1,5 @@
1
- import { devtoolsNotFoundResponse, isDevtoolsRouteEnabled, } from "./access";
1
+ import { devtoolsNotFoundResponse, isDevtoolsRouteEnabled, } from "./access.js";
2
+ import { devtoolsPanelModelBrowserSource } from "./ui-model.js";
2
3
  /**
3
4
  * Devtools UI handler
4
5
  *
@@ -85,6 +86,22 @@ button.danger:hover{border-color:rgba(225,29,72,.24);background:#fff1f2}
85
86
  .detail-title{color:var(--text);font-family:var(--font-sans);font-size:12px;font-weight:650;margin-bottom:6px}
86
87
  .correlated{display:flex;flex-direction:column;gap:6px}
87
88
  .correlated-row{display:flex;gap:7px;align-items:flex-start;color:var(--text-muted)}
89
+ .request-map{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px;margin-bottom:12px}
90
+ .request-fact{border:1px solid var(--border);border-radius:7px;background:var(--surface);padding:9px 10px;min-width:0}
91
+ .request-fact-label{font-size:10px;color:var(--text-muted);text-transform:uppercase;letter-spacing:.1em}
92
+ .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}
93
+ .detail-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px;margin-bottom:10px}
94
+ .lifecycle{display:flex;flex-direction:column;gap:7px}
95
+ .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}
96
+ .phase-name{font-family:var(--font-mono);font-size:11px;color:var(--text)}
97
+ .phase-summary{color:var(--text-muted);font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
98
+ .phase-meta{font-family:var(--font-mono);font-size:10px;color:var(--text-dim)}
99
+ .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}
100
+ .owner-framework,.owner-provider,.owner-devtools{color:var(--red);background:#fff1f2;border-color:#ffe4e6}
101
+ .owner-route{color:var(--green);background:#ecfdf5;border-color:#d1fae5}
102
+ .owner-transport,.owner-client{color:var(--blue);background:#eff6ff;border-color:#dbeafe}
103
+ .owner-job,.owner-schedule,.owner-outbox{color:var(--orange);background:#fff7ed;border-color:#fed7aa}
104
+ .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}
88
105
  .focus-panel{display:flex;flex-direction:column}
89
106
  .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)}
90
107
  .focus-title{font-size:15px;line-height:1.2;font-weight:720;color:var(--text)}
@@ -94,7 +111,7 @@ button.danger:hover{border-color:rgba(225,29,72,.24);background:#fff1f2}
94
111
  .focus-metric-label{font-size:10px;color:var(--text-muted);text-transform:uppercase;letter-spacing:.1em}
95
112
  .focus-metric-value{font-family:var(--font-mono);font-size:15px;color:var(--text);margin-top:3px}
96
113
  .event-table{display:flex;flex-direction:column}
97
- .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}
114
+ .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}
98
115
  .table-row:last-child{border-bottom:0}
99
116
  .table-row:hover{background:var(--surface)}
100
117
  .table-row.open{background:#f8fafc}
@@ -102,8 +119,31 @@ button.danger:hover{border-color:rgba(225,29,72,.24);background:#fff1f2}
102
119
  .table-cell strong{display:block;color:var(--text);font-size:13px;font-weight:650;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
103
120
  .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}
104
121
  .table-summary{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
122
+ .table-correlation{display:flex;flex-wrap:wrap;gap:4px}
123
+ .table-empty{font-family:var(--font-mono);font-size:10px;color:var(--text-dim)}
105
124
  .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}
106
125
  .table-row.open .table-detail{display:block}
126
+ .table-detail-note{font-family:var(--font-sans);font-size:12px;color:var(--text-muted);margin-bottom:8px;white-space:normal}
127
+ .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}
128
+ .result-failed,.result-blocked,.result-deadLettered{color:var(--red);background:#fff1f2;border-color:#ffe4e6}
129
+ .result-retryScheduled{color:var(--orange);background:#fff7ed;border-color:#fed7aa}
130
+ .result-delivered,.result-completed,.result-allowed,.result-hit,.result-signed_in{color:var(--green);background:#ecfdf5;border-color:#d1fae5}
131
+ .error-board{display:flex;flex-direction:column}
132
+ .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)}
133
+ .error-owner-card{border:1px solid var(--border);border-radius:7px;background:var(--surface);padding:9px 10px;min-width:0}
134
+ .error-owner-card strong{display:block;font-family:var(--font-mono);font-size:15px;color:var(--text)}
135
+ .error-owner-card span{display:block;color:var(--text-muted);font-size:10px;text-transform:uppercase;letter-spacing:.1em;margin-top:2px}
136
+ .error-group{border-bottom:1px solid var(--border)}
137
+ .error-group:last-child{border-bottom:0}
138
+ .error-group-head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 14px;background:var(--surface)}
139
+ .error-group-title{display:flex;align-items:center;gap:7px;font-size:13px;font-weight:700;color:var(--text)}
140
+ .error-group-count{font-family:var(--font-mono);font-size:11px;color:var(--text-muted)}
141
+ .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}
142
+ .error-row:hover{background:var(--surface)}
143
+ .error-row.open{background:#f8fafc}
144
+ .error-message{min-width:0;color:var(--text);font-size:13px;font-weight:650;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
145
+ .error-context{font-family:var(--font-mono);font-size:11px;color:var(--text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
146
+ .error-related{display:flex;flex-direction:column;gap:5px;margin-top:8px}
107
147
  .empty{text-align:center;padding:58px 16px;color:var(--text-dim);font-size:13px;background:var(--bg)}
108
148
  .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}
109
149
  .badge-request{background:#eff6ff;color:var(--blue);border-color:#dbeafe}
@@ -111,6 +151,7 @@ button.danger:hover{border-color:rgba(225,29,72,.24);background:#fff1f2}
111
151
  .badge-usecase{background:#f5f3ff;color:var(--purple);border-color:#ede9fe}
112
152
  .badge-eventBus{background:#ecfdf5;color:var(--green);border-color:#d1fae5}
113
153
  .badge-job{background:#fff7ed;color:var(--orange);border-color:#fed7aa}
154
+ .badge-outbox{background:#fefce8;color:#a16207;border-color:#fef08a}
114
155
  .badge-schedule{background:#f0f9ff;color:#0369a1;border-color:#bae6fd}
115
156
  .badge-provider{background:#f8fafc;color:var(--gray);border-color:var(--border)}
116
157
  .badge-custom{background:#f0fdfa;color:#0f766e;border-color:#ccfbf1}
@@ -122,6 +163,7 @@ button.danger:hover{border-color:rgba(225,29,72,.24);background:#fff1f2}
122
163
  .badge-notifications{background:#f5f3ff;color:#6d28d9;border-color:#ddd6fe}
123
164
  .badge-rateLimit{background:#fffbeb;color:#b45309;border-color:#fde68a}
124
165
  .badge-storage{background:#f0fdf4;color:#15803d;border-color:#bbf7d0}
166
+ .badge-uploads{background:#f0f9ff;color:#0369a1;border-color:#bae6fd}
125
167
  .request-id{font-family:var(--font-mono);font-size:10px;color:var(--text-muted);padding:2px 5px;background:var(--surface-2);border:1px solid var(--border);border-radius:4px}
126
168
  .trace-id{font-family:var(--font-mono);font-size:10px;color:var(--accent-strong);padding:2px 5px;background:#eef2ff;border:1px solid rgba(79,70,229,.16);border-radius:4px}
127
169
  @media(max-width:760px){
@@ -136,9 +178,14 @@ button.danger:hover{border-color:rgba(225,29,72,.24);background:#fff1f2}
136
178
  .event-time{min-width:0}
137
179
  .event-summary{white-space:normal}
138
180
  .request-detail{grid-template-columns:1fr}
181
+ .request-map{grid-template-columns:repeat(2,minmax(0,1fr))}
182
+ .detail-grid{grid-template-columns:repeat(2,minmax(0,1fr))}
183
+ .phase-row{grid-template-columns:1fr;gap:5px}
139
184
  .focus-head{flex-direction:column}
140
185
  .focus-metrics{grid-template-columns:repeat(2,minmax(0,1fr));min-width:0;width:100%}
141
186
  .table-row{grid-template-columns:1fr;gap:6px}
187
+ .error-owner-strip{grid-template-columns:repeat(2,minmax(0,1fr))}
188
+ .error-row{grid-template-columns:1fr;gap:6px}
142
189
  .table-detail{grid-column:auto}
143
190
  }
144
191
  </style>
@@ -149,7 +196,7 @@ button.danger:hover{border-color:rgba(225,29,72,.24);background:#fff1f2}
149
196
  <div>
150
197
  <div class="brand"><span class="mark">B</span><span>Beignet</span></div>
151
198
  <h1>Beignet <span>Devtools</span></h1>
152
- <div class="subtitle">A local timeline for requests, use cases, errors, jobs, schedules, and provider activity.</div>
199
+ <div class="subtitle">A local timeline for requests, use cases, errors, jobs, outbox delivery, schedules, and provider activity.</div>
153
200
  </div>
154
201
  <div class="controls">
155
202
  <span class="status"><span class="dot" id="statusDot"></span><span id="statusText">Connecting</span></span>
@@ -204,11 +251,13 @@ button.danger:hover{border-color:rgba(225,29,72,.24);background:#fff1f2}
204
251
  {id:"errors",label:"Errors",type:"error",watcher:"errors"},
205
252
  {id:"events",label:"Events",type:"eventBus",watcher:"eventBus"},
206
253
  {id:"jobs",label:"Jobs",type:"job",watcher:"jobs"},
254
+ {id:"outbox",label:"Outbox",watcher:"outbox",watcherName:"outbox"},
207
255
  {id:"schedules",label:"Schedules",type:"schedule",watcher:"schedules"},
208
256
  {id:"providers",label:"Providers",type:"provider",watcher:"providers"},
209
257
  {id:"db",label:"Database",type:"custom",watcher:"db",watcherName:"db"},
210
258
  {id:"cache",label:"Cache",type:"custom",watcher:"cache",watcherName:"cache"},
211
259
  {id:"storage",label:"Storage",type:"custom",watcher:"storage",watcherName:"storage"},
260
+ {id:"uploads",label:"Uploads",type:"custom",watcher:"uploads",watcherName:"uploads"},
212
261
  {id:"mail",label:"Mail",type:"custom",watcher:"mail",watcherName:"mail"},
213
262
  {id:"notifications",label:"Notifications",type:"custom",watcher:"notifications",watcherName:"notifications"},
214
263
  {id:"auth",label:"Auth",type:"custom",watcher:"auth",watcherName:"auth"},
@@ -216,8 +265,9 @@ button.danger:hover{border-color:rgba(225,29,72,.24);background:#fff1f2}
216
265
  {id:"rateLimit",label:"Rate limits",type:"custom",watcher:"rateLimit",watcherName:"rateLimit"},
217
266
  {id:"custom",label:"Custom",type:"custom",watcher:"custom"}
218
267
  ];
219
- const FOCUS_PANEL_TABS={events:true,jobs:true,schedules:true,db:true,cache:true,storage:true,mail:true,notifications:true,auth:true,audit:true,rateLimit:true};
220
- const BUILT_IN_WATCHERS={requests:true,errors:true,useCases:true,eventBus:true,jobs:true,schedules:true,providers:true,db:true,cache:true,storage:true,mail:true,notifications:true,auth:true,audit:true,rateLimit:true,custom:true};
268
+ 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};
269
+ 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};
270
+ const ERROR_OWNERS=["route","framework","provider","job","schedule","outbox","client","devtools","unknown"];
221
271
  let events=[];
222
272
  let watchers=[];
223
273
  let activeTab=localStorage.getItem("beignet-devtools-tab")||"timeline";
@@ -284,6 +334,7 @@ button.danger:hover{border-color:rgba(225,29,72,.24);background:#fff1f2}
284
334
  case "usecase": return e.name+" ("+e.phase+")"+(e.durationMs!==undefined?" "+e.durationMs+"ms":"");
285
335
  case "eventBus": return e.eventName||"Domain event";
286
336
  case "job": return e.jobName+" -> "+e.status;
337
+ case "outbox": return e.messageName+" ("+e.messageKind+") -> "+e.status;
287
338
  case "schedule": return e.scheduleName+" -> "+e.status;
288
339
  case "provider": return e.providerName+" -> "+e.action;
289
340
  case "custom": return (e.label||e.name||"Custom event")+(e.summary?" - "+e.summary:"");
@@ -292,6 +343,7 @@ button.danger:hover{border-color:rgba(225,29,72,.24);background:#fff1f2}
292
343
  }
293
344
 
294
345
  function tabMatchesEvent(tab,event){
346
+ if(tab.id==="errors")return event.type==="error"||failureFor(event);
295
347
  if(tab.type&&event.type!==tab.type)return false;
296
348
  if(tab.watcherName&&event.watcher!==tab.watcherName)return false;
297
349
  return true;
@@ -305,6 +357,7 @@ button.danger:hover{border-color:rgba(225,29,72,.24);background:#fff1f2}
305
357
  case "usecase": return "useCases";
306
358
  case "eventBus": return "eventBus";
307
359
  case "job": return "jobs";
360
+ case "outbox": return "outbox";
308
361
  case "schedule": return "schedules";
309
362
  case "provider": return "providers";
310
363
  case "custom": return "custom";
@@ -323,6 +376,7 @@ button.danger:hover{border-color:rgba(225,29,72,.24);background:#fff1f2}
323
376
  case "notifications": return "notifications";
324
377
  case "rateLimit": return "rate limit";
325
378
  case "storage": return "storage";
379
+ case "uploads": return "uploads";
326
380
  default: return "custom";
327
381
  }
328
382
  }
@@ -340,6 +394,7 @@ button.danger:hover{border-color:rgba(225,29,72,.24);background:#fff1f2}
340
394
  case "notifications": return "notifications";
341
395
  case "rateLimit": return "rateLimit";
342
396
  case "storage": return "storage";
397
+ case "uploads": return "uploads";
343
398
  default: return "custom";
344
399
  }
345
400
  }
@@ -380,6 +435,9 @@ button.danger:hover{border-color:rgba(225,29,72,.24);background:#fff1f2}
380
435
  event.summary,
381
436
  event.eventName,
382
437
  event.jobName,
438
+ event.messageId,
439
+ event.messageKind,
440
+ event.messageName,
383
441
  event.providerName,
384
442
  event.action,
385
443
  jsonSearch(event.details)
@@ -415,6 +473,164 @@ button.danger:hover{border-color:rgba(225,29,72,.24);background:#fff1f2}
415
473
  });
416
474
  }
417
475
 
476
+ function eventTimeValue(event){
477
+ const time=Date.parse(event.timestamp||"");
478
+ return Number.isFinite(time)?time:0;
479
+ }
480
+
481
+ function responseOwnerFor(event){
482
+ const details=detailsObject(event);
483
+ const response=details.response&&typeof details.response==="object"?details.response:{};
484
+ return event.responseOwner||event.owner||response.owner||"unknown";
485
+ }
486
+
487
+ function ownerClass(owner){
488
+ return "owner-"+String(owner||"unknown").replace(/[^a-zA-Z0-9_-]/g,"");
489
+ }
490
+
491
+ function ownerPill(owner){
492
+ const value=owner||"unknown";
493
+ return '<span class="owner-pill '+esc(ownerClass(value))+'">'+esc(value)+'</span>';
494
+ }
495
+
496
+ function categoryFor(event){
497
+ const watcher=inferredWatcher(event);
498
+ if(event.type==="request")return"HTTP";
499
+ if(event.type==="usecase")return"Use case";
500
+ if(event.type==="error")return"Error";
501
+ if(event.type==="provider")return"Provider";
502
+ if(event.type==="eventBus")return"Domain event";
503
+ if(event.type==="outbox")return"Outbox";
504
+ if(event.type==="job")return"Job";
505
+ if(event.type==="schedule")return"Schedule";
506
+ if(["db","cache","storage","uploads","mail","notifications","auth","audit","rateLimit"].indexOf(watcher)>=0)return"Provider work";
507
+ return watcher==="custom"?"Custom":"Provider work";
508
+ }
509
+
510
+ function ownerForEvent(event){
511
+ if(event.owner)return event.owner;
512
+ const watcher=inferredWatcher(event);
513
+ if(event.type==="error")return responseOwnerFor(event);
514
+ if(event.type==="provider")return"provider";
515
+ if(event.type==="job")return"job";
516
+ if(event.type==="schedule")return"schedule";
517
+ if(event.type==="outbox")return"outbox";
518
+ if(["db","cache","storage","uploads","mail","notifications","auth","audit","rateLimit"].indexOf(watcher)>=0)return"provider";
519
+ if(event.type==="request")return responseOwnerFor(event);
520
+ return"route";
521
+ }
522
+
523
+ function relatedEventsFor(event){
524
+ return events.filter(function(candidate){
525
+ if(candidate.id===event.id)return false;
526
+ if(event.traceId&&candidate.traceId===event.traceId)return true;
527
+ return Boolean(event.requestId)&&candidate.requestId===event.requestId;
528
+ });
529
+ }
530
+
531
+ function requestForEvent(event){
532
+ return relatedEventsFor(event).filter(function(candidate){return candidate.type==="request"}).sort(function(a,b){return eventTimeValue(b)-eventTimeValue(a)})[0];
533
+ }
534
+
535
+ function statusForEvent(event){
536
+ if(event.status!==undefined)return event.status;
537
+ const request=requestForEvent(event);
538
+ if(request&&request.status!==undefined)return request.status;
539
+ const details=detailsObject(event);
540
+ const response=details.response&&typeof details.response==="object"?details.response:{};
541
+ return response.status||"";
542
+ }
543
+
544
+ function contractForEvent(event){
545
+ const request=requestForEvent(event);
546
+ return event.contractName||event.useCaseName||(request&&request.contractName)||"";
547
+ }
548
+
549
+ function pathForEvent(event){
550
+ const request=requestForEvent(event);
551
+ return event.path||(request&&request.path)||"";
552
+ }
553
+
554
+ function correlationFor(event){
555
+ const rid=event.requestId?'<span class="request-id">'+esc(event.requestId)+'</span>':"";
556
+ const trace=event.traceId?'<span class="trace-id">trace '+esc(event.traceId.slice(0,8))+'</span>':"";
557
+ return rid+trace||'<span class="table-empty">n/a</span>';
558
+ }
559
+
560
+ function unsafeMetadataPaths(value,path){
561
+ if(!value||typeof value!=="object")return[];
562
+ return Object.entries(value).flatMap(function(entry){
563
+ const key=entry[0];
564
+ const nested=entry[1];
565
+ const next=path.concat(key);
566
+ const lower=key.toLowerCase();
567
+ 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;
568
+ if(sensitive&&nested!=="[redacted]")return[next.join(".")];
569
+ return unsafeMetadataPaths(nested,next);
570
+ });
571
+ }
572
+
573
+ function requestFact(label,value){
574
+ const display=value===undefined||value===null||value===""?"n/a":value;
575
+ 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>';
576
+ }
577
+
578
+ function detailGrid(items){
579
+ return '<div class="detail-grid">'+items.map(function(item){return requestFact(item[0],item[1])}).join("")+'</div>';
580
+ }
581
+
582
+ function requestOverview(request,related){
583
+ return '<div class="request-map">'+
584
+ requestFact("Route",request.contractName||"unmatched")+
585
+ requestFact("Method",request.method||"")+
586
+ requestFact("Path",request.path||"")+
587
+ requestFact("Status",request.status||"")+
588
+ requestFact("Duration",request.durationMs!==undefined?request.durationMs+"ms":"")+
589
+ requestFact("Owner",responseOwnerFor(request))+
590
+ requestFact("Trace",request.traceId||"")+
591
+ requestFact("Related",related.length)+
592
+ '</div>';
593
+ }
594
+
595
+ function requestLifecycleRows(request,related){
596
+ const rows=[request].concat(related).sort(function(a,b){return eventTimeValue(a)-eventTimeValue(b)});
597
+ return rows.map(function(event){
598
+ const duration=durationFor(event);
599
+ const durationLabel=duration===undefined?"":duration+"ms";
600
+ return '<div class="phase-row">'+
601
+ '<div>'+eventBadge(event)+'</div>'+
602
+ '<div><strong class="phase-name">'+esc(categoryFor(event))+'</strong><div class="phase-meta">'+esc(durationLabel||inferredWatcher(event))+'</div></div>'+
603
+ '<div class="phase-summary" title="'+esc(summarize(event))+'">'+esc(summarize(event))+'</div>'+
604
+ '<div>'+ownerPill(ownerForEvent(event))+'</div>'+
605
+ '</div>';
606
+ }).join("");
607
+ }
608
+
609
+ function groupedRelatedRows(related){
610
+ const groups=["Use case","Provider work","Domain event","Outbox","Job","Schedule","Error","Provider","Custom"];
611
+ return groups.map(function(group){
612
+ const groupEvents=related.filter(function(event){return categoryFor(event)===group});
613
+ if(!groupEvents.length)return"";
614
+ const rows=groupEvents.slice().sort(function(a,b){return eventTimeValue(a)-eventTimeValue(b)}).map(function(event){
615
+ return '<div class="correlated-row">'+eventBadge(event)+'<span>'+esc(summarize(event))+'</span>'+ownerPill(ownerForEvent(event))+'</div>';
616
+ }).join("");
617
+ return '<section class="detail-section"><div class="detail-title">'+esc(group)+'</div><div class="correlated">'+rows+'</div></section>';
618
+ }).join("");
619
+ }
620
+
621
+ function redactionNote(request,related){
622
+ const all=[request].concat(related);
623
+ const redacted=all.some(function(event){return hasRedactedValue(event)});
624
+ const unsafe=Array.from(new Set(all.flatMap(function(event){return unsafeMetadataPaths(event,[])}))).slice(0,4);
625
+ if(unsafe.length){
626
+ return '<div class="redaction-note">Unsafe-looking metadata keys remain visible: '+esc(unsafe.join(", "))+'</div>';
627
+ }
628
+ if(redacted){
629
+ return '<div class="redaction-note">Sensitive metadata was redacted before storage.</div>';
630
+ }
631
+ return '<div class="redaction-note">No redacted fields detected in the stored events for this request.</div>';
632
+ }
633
+
418
634
  function updateStats(){
419
635
  $statTotal.textContent=String(events.length);
420
636
  $statRequests.textContent=String(events.filter(function(event){return event.type==="request"}).length);
@@ -487,178 +703,49 @@ button.danger:hover{border-color:rgba(225,29,72,.24);background:#fff1f2}
487
703
  function renderRequests(list){
488
704
  return list.slice().reverse().map(function(request){
489
705
  const related=correlatedEvents(request);
490
- const relatedHtml=related.length
491
- ? related.slice().reverse().map(function(event){
492
- return '<div class="correlated-row">'+eventBadge(event)+'<span>'+esc(summarize(event))+'</span></div>';
493
- }).join("")
494
- : '<div class="empty">No correlated events for this request.</div>';
495
706
  const detail='<div class="request-detail">'+
496
- '<section class="detail-section"><div class="detail-title">Request</div><pre>'+json(request)+'</pre></section>'+
497
- '<section class="detail-section"><div class="detail-title">Correlated events</div><div class="correlated">'+relatedHtml+'</div></section>'+
707
+ '<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>'+
708
+ '<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>'+
498
709
  '</div>';
499
710
  return eventRow(request,detail);
500
711
  }).join("");
501
712
  }
502
713
 
503
- function detailsObject(event){
504
- return event.details&&typeof event.details==="object"&&!Array.isArray(event.details)?event.details:{};
505
- }
506
-
507
- function detailValue(event,key){
508
- return detailsObject(event)[key];
509
- }
510
-
511
- function durationFor(event){
512
- const detailDuration=detailValue(event,"durationMs");
513
- const duration=event.durationMs!==undefined?event.durationMs:detailDuration;
514
- return typeof duration==="number"&&Number.isFinite(duration)?duration:undefined;
515
- }
516
-
517
- function providerFor(event){
518
- const details=detailsObject(event);
519
- return details.providerName||details.provider||details.portName||"";
520
- }
521
-
522
- function failureFor(event){
523
- const details=detailsObject(event);
524
- if(event.type==="error")return true;
525
- if(typeof event.status==="string")return event.status==="failed";
526
- if(typeof event.name==="string"&&event.name.indexOf("failed")>=0)return true;
527
- return Boolean(details.error)||details.allowed===false;
528
- }
529
-
530
- function averageDuration(list){
531
- const durations=list.map(durationFor).filter(function(value){return value!==undefined});
532
- if(!durations.length)return "n/a";
533
- const total=durations.reduce(function(sum,value){return sum+value},0);
534
- return Math.round(total/durations.length)+"ms";
535
- }
536
-
537
- function distinctCount(list,selector){
538
- const values=new Set();
539
- list.forEach(function(event){
540
- const value=selector(event);
541
- if(value!==undefined&&value!==null&&String(value)!=="")values.add(String(value));
542
- });
543
- return values.size;
544
- }
545
-
546
714
  function metric(label,value){
547
715
  return '<div class="focus-metric"><div class="focus-metric-label">'+esc(label)+'</div><div class="focus-metric-value">'+esc(value)+'</div></div>';
548
716
  }
549
717
 
550
- function panelMeta(tab){
551
- switch(tab.id){
552
- case "events": return {title:"Domain events",subtitle:"Published facts and event bus activity"};
553
- case "jobs": return {title:"Jobs",subtitle:"Background job lifecycle"};
554
- case "schedules": return {title:"Schedules",subtitle:"Cron and scheduled task runs"};
555
- case "db": return {title:"Database",subtitle:"Queries and database provider diagnostics"};
556
- case "cache": return {title:"Cache",subtitle:"Reads, writes, deletes, and hit rates"};
557
- case "storage": return {title:"Storage",subtitle:"Object storage operations"};
558
- case "mail": return {title:"Mail",subtitle:"Delivery attempts and provider results"};
559
- case "notifications": return {title:"Notifications",subtitle:"Notification intent and channel delivery"};
560
- case "auth": return {title:"Auth",subtitle:"Session and authentication activity"};
561
- case "audit": return {title:"Audit",subtitle:"Durable activity records emitted by application code"};
562
- case "rateLimit": return {title:"Rate limits",subtitle:"Allowed, blocked, and failed checks"};
563
- default: return {title:tab.label,subtitle:"Subsystem activity"};
564
- }
565
- }
718
+ ${devtoolsPanelModelBrowserSource()}
566
719
 
720
+ function summarize(e){return summarizeDevtoolsEvent(e);}
721
+ function inferredWatcher(event){return inferDevtoolsWatcher(event);}
722
+ function eventBadgeLabel(event){return devtoolsEventBadgeLabel(event);}
723
+ function eventBadgeClass(event){return devtoolsEventBadgeClass(event);}
724
+ function durationFor(event){return devtoolsEventDuration(event);}
725
+ function providerFor(event){return devtoolsEventProvider(event);}
726
+ function failureFor(event){return devtoolsEventFailure(event);}
727
+ function hasRedactedValue(value){return hasDevtoolsRedactedValue(value);}
728
+ function panelMeta(tab){return devtoolsPanelMeta(tab.id,tab.label);}
567
729
  function panelMetrics(tab,list){
568
- switch(tab.id){
569
- case "events":
570
- return [
571
- metric("Events",list.length),
572
- metric("Names",distinctCount(list,function(event){return event.eventName})),
573
- metric("Traces",distinctCount(list,function(event){return event.traceId})),
574
- metric("Requests",distinctCount(list,function(event){return event.requestId}))
575
- ].join("");
576
- case "jobs":
577
- return [
578
- metric("Jobs",list.length),
579
- metric("Failed",list.filter(failureFor).length),
580
- metric("Names",distinctCount(list,function(event){return event.jobName})),
581
- metric("Providers",distinctCount(list,providerFor))
582
- ].join("");
583
- case "schedules":
584
- return [
585
- metric("Runs",list.length),
586
- metric("Failed",list.filter(failureFor).length),
587
- metric("Names",distinctCount(list,function(event){return event.scheduleName})),
588
- metric("Cron",distinctCount(list,function(event){return event.cron}))
589
- ].join("");
590
- case "cache":
591
- return [
592
- metric("Operations",list.length),
593
- metric("Hits",list.filter(function(event){return detailValue(event,"hit")===true}).length),
594
- metric("Failures",list.filter(failureFor).length),
595
- metric("Avg",averageDuration(list))
596
- ].join("");
597
- case "auth":
598
- return [
599
- metric("Events",list.length),
600
- metric("Authenticated",list.filter(function(event){return detailValue(event,"authenticated")===true}).length),
601
- metric("Failures",list.filter(failureFor).length),
602
- metric("Avg",averageDuration(list))
603
- ].join("");
604
- case "audit":
605
- return [
606
- metric("Entries",list.length),
607
- metric("Failures",list.filter(function(event){return detailValue(event,"outcome")==="failure"}).length),
608
- metric("Actors",distinctCount(list,function(event){const actor=detailValue(event,"actor");return actor&&actor.id})),
609
- metric("Resources",distinctCount(list,function(event){const resource=detailValue(event,"resource");return resource&&resource.id}))
610
- ].join("");
611
- case "rateLimit":
612
- return [
613
- metric("Checks",list.length),
614
- metric("Allowed",list.filter(function(event){return detailValue(event,"allowed")===true}).length),
615
- metric("Blocked",list.filter(function(event){return detailValue(event,"allowed")===false}).length),
616
- metric("Avg",averageDuration(list))
617
- ].join("");
618
- default:
619
- return [
620
- metric("Events",list.length),
621
- metric("Failures",list.filter(failureFor).length),
622
- metric("Providers",distinctCount(list,providerFor)),
623
- metric("Avg",averageDuration(list))
624
- ].join("");
625
- }
730
+ return devtoolsPanelMetrics(tab.id,list).map(function(item){
731
+ return metric(item.label,item.value);
732
+ }).join("");
626
733
  }
627
-
628
- function primaryFor(event){
629
- switch(event.type){
630
- case "eventBus": return event.eventName||"Domain event";
631
- case "job": return event.jobName||"Job";
632
- case "schedule": return event.scheduleName||"Schedule";
633
- case "custom": return event.label||event.name||"Custom event";
634
- default: return summarize(event);
635
- }
734
+ function primaryFor(event){return devtoolsPanelPrimary(tabFor(activeTab).id,event);}
735
+ function secondaryFor(event){return devtoolsPanelSecondary(tabFor(activeTab).id,event);}
736
+ function resultFor(event){return devtoolsPanelResult(event);}
737
+ function tableDetailFor(tab,event){
738
+ const detail=devtoolsPanelDetail(tab.id,event);
739
+ return detailGrid(detail.fields.map(function(item){return [item.label,item.value]}))+
740
+ '<div class="table-detail-note">'+esc(detail.note)+'</div><pre>'+json(event)+'</pre>';
636
741
  }
637
742
 
638
- function secondaryFor(event){
639
- const details=detailsObject(event);
640
- if(details.operation)return details.operation;
641
- if(event.type==="job")return event.status;
642
- if(event.type==="schedule")return event.status;
643
- if(event.type==="eventBus")return event.requestId||event.traceId||"";
644
- if(event.name)return event.name;
645
- return inferredWatcher(event);
743
+ function resultChip(value){
744
+ const normalized=String(value||"").replace(/[^a-zA-Z0-9_-]/g,"_");
745
+ return '<span class="result-chip result-'+esc(normalized)+'">'+esc(value||"n/a")+'</span>';
646
746
  }
647
747
 
648
- function resultFor(event){
649
- const details=detailsObject(event);
650
- if(details.allowed===true)return "allowed";
651
- if(details.allowed===false)return "blocked";
652
- if(details.hit===true)return "hit";
653
- if(details.hit===false)return "miss";
654
- if(details.authenticated===true)return "signed in";
655
- if(details.authenticated===false)return "guest";
656
- if(event.status)return event.status;
657
- if(details.error)return "failed";
658
- return providerFor(event)||inferredWatcher(event);
659
- }
660
-
661
- function tableRow(event){
748
+ function tableRow(tab,event){
662
749
  const time=event.timestamp?new Date(event.timestamp).toLocaleTimeString():"";
663
750
  const duration=durationFor(event);
664
751
  const durationLabel=duration===undefined?"":duration+"ms";
@@ -667,15 +754,16 @@ button.danger:hover{border-color:rgba(225,29,72,.24);background:#fff1f2}
667
754
  '<div class="table-cell">'+esc(time)+'</div>'+
668
755
  '<div class="table-cell"><strong>'+esc(primaryFor(event))+'</strong><code>'+esc(secondaryFor(event))+'</code></div>'+
669
756
  '<div class="table-cell table-summary">'+esc(summarize(event))+'</div>'+
757
+ '<div class="table-cell table-correlation">'+correlationFor(event)+'</div>'+
670
758
  '<div class="table-cell">'+esc(durationLabel)+'</div>'+
671
- '<div class="table-cell">'+esc(resultFor(event)||provider)+'</div>'+
672
- '<pre class="table-detail">'+json(event)+'</pre>'+
759
+ '<div class="table-cell">'+resultChip(resultFor(event)||provider)+'</div>'+
760
+ '<div class="table-detail">'+tableDetailFor(tab,event)+'</div>'+
673
761
  '</article>';
674
762
  }
675
763
 
676
764
  function renderFocusPanel(tab,list){
677
765
  const meta=panelMeta(tab);
678
- const rows=list.slice().reverse().map(tableRow).join("");
766
+ const rows=list.slice().reverse().map(function(event){return tableRow(tab,event)}).join("");
679
767
  return '<section class="focus-panel">'+
680
768
  '<div class="focus-head">'+
681
769
  '<div><h2 class="focus-title">'+esc(meta.title)+'</h2><div class="focus-subtitle">'+esc(meta.subtitle)+'</div></div>'+
@@ -685,6 +773,65 @@ button.danger:hover{border-color:rgba(225,29,72,.24);background:#fff1f2}
685
773
  '</section>';
686
774
  }
687
775
 
776
+ function renderErrorOwners(list){
777
+ const owners=ERROR_OWNERS.map(function(owner){
778
+ return {owner:owner,count:list.filter(function(event){return ownerForEvent(event)===owner}).length};
779
+ }).filter(function(item){return item.count>0});
780
+ const cards=(owners.length?owners:[{owner:"none",count:0}]).map(function(item){
781
+ return '<div class="error-owner-card"><strong>'+esc(item.count)+'</strong><span>'+esc(item.owner)+'</span></div>';
782
+ }).join("");
783
+ return '<div class="error-owner-strip">'+cards+'</div>';
784
+ }
785
+
786
+ function renderErrorRelated(event){
787
+ const related=relatedEventsFor(event).filter(function(candidate){return candidate.type!=="error"}).slice().sort(function(a,b){return eventTimeValue(a)-eventTimeValue(b)}).slice(0,8);
788
+ if(!related.length)return '<div class="empty">No related request, provider, or side-effect events.</div>';
789
+ return '<div class="error-related">'+related.map(function(candidate){
790
+ return '<div class="correlated-row">'+eventBadge(candidate)+'<span>'+esc(summarize(candidate))+'</span>'+ownerPill(ownerForEvent(candidate))+'</div>';
791
+ }).join("")+'</div>';
792
+ }
793
+
794
+ function failureMessageFor(event){
795
+ if(event.message)return event.message;
796
+ const details=detailsObject(event);
797
+ const detailError=details.error;
798
+ if(typeof detailError==="string"&&detailError)return detailError;
799
+ if(detailError&&typeof detailError==="object"){
800
+ if(typeof detailError.message==="string")return detailError.message;
801
+ return jsonSearch(detailError);
802
+ }
803
+ return summarize(event)||"Unknown failure";
804
+ }
805
+
806
+ function renderErrorRow(event){
807
+ const time=event.timestamp?new Date(event.timestamp).toLocaleTimeString():"";
808
+ const status=statusForEvent(event);
809
+ const route=[contractForEvent(event),pathForEvent(event)].filter(Boolean).join(" ");
810
+ const relatedCount=relatedEventsFor(event).length;
811
+ const message=failureMessageFor(event);
812
+ return '<article class="error-row" onclick="this.classList.toggle(\\'open\\')">'+
813
+ '<div class="table-cell">'+esc(time)+'</div>'+
814
+ '<div><div class="error-message" title="'+esc(message)+'">'+esc(message)+'</div><div class="error-context">'+esc(route||event.useCaseName||primaryFor(event)||"unmatched")+'</div></div>'+
815
+ '<div>'+ownerPill(ownerForEvent(event))+'</div>'+
816
+ '<div class="table-cell table-correlation">'+correlationFor(event)+'</div>'+
817
+ '<div class="table-cell">'+resultChip(status||relatedCount+" related")+'</div>'+
818
+ '<div class="table-detail">'+renderErrorRelated(event)+'<pre>'+json(event)+'</pre></div>'+
819
+ '</article>';
820
+ }
821
+
822
+ function renderErrorsPanel(list){
823
+ const rowsByOwner=ERROR_OWNERS.map(function(owner){
824
+ const ownerEvents=list.filter(function(event){return ownerForEvent(event)===owner});
825
+ if(!ownerEvents.length)return"";
826
+ const rows=ownerEvents.slice().reverse().map(renderErrorRow).join("");
827
+ return '<section class="error-group">'+
828
+ '<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>'+
829
+ rows+
830
+ '</section>';
831
+ }).join("");
832
+ return '<section class="error-board">'+renderErrorOwners(list)+rowsByOwner+'</section>';
833
+ }
834
+
688
835
  function render(){
689
836
  updateStats();
690
837
  renderTabs();
@@ -701,6 +848,10 @@ button.danger:hover{border-color:rgba(225,29,72,.24);background:#fff1f2}
701
848
  $content.innerHTML=renderRequests(list);
702
849
  return;
703
850
  }
851
+ if(activeTab==="errors"){
852
+ $content.innerHTML=renderErrorsPanel(list);
853
+ return;
854
+ }
704
855
  if(FOCUS_PANEL_TABS[tab.id]){
705
856
  $content.innerHTML=renderFocusPanel(tab,list);
706
857
  return;