proteum 2.1.0 → 2.1.1

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 (83) hide show
  1. package/AGENTS.md +44 -98
  2. package/README.md +121 -7
  3. package/agents/framework/AGENTS.md +133 -886
  4. package/agents/project/AGENTS.md +70 -127
  5. package/agents/project/client/AGENTS.md +22 -93
  6. package/agents/project/client/pages/AGENTS.md +24 -26
  7. package/agents/project/server/routes/AGENTS.md +10 -8
  8. package/agents/project/server/services/AGENTS.md +22 -159
  9. package/agents/project/tests/AGENTS.md +11 -8
  10. package/cli/app/config.ts +7 -20
  11. package/cli/bin.js +8 -0
  12. package/cli/commands/command.ts +243 -0
  13. package/cli/commands/commandLocalRunner.js +198 -0
  14. package/cli/commands/deploy/web.ts +1 -2
  15. package/cli/commands/dev.ts +96 -1
  16. package/cli/commands/doctor.ts +8 -74
  17. package/cli/commands/explain.ts +8 -186
  18. package/cli/commands/trace.ts +228 -0
  19. package/cli/compiler/artifacts/commands.ts +217 -0
  20. package/cli/compiler/artifacts/manifest.ts +35 -21
  21. package/cli/compiler/artifacts/services.ts +300 -1
  22. package/cli/compiler/client/index.ts +43 -8
  23. package/cli/compiler/common/commands.ts +175 -0
  24. package/cli/compiler/common/index.ts +1 -1
  25. package/cli/compiler/common/proteumManifest.ts +15 -114
  26. package/cli/compiler/index.ts +25 -2
  27. package/cli/compiler/server/index.ts +31 -6
  28. package/cli/paths.ts +16 -1
  29. package/cli/presentation/commands.ts +59 -5
  30. package/cli/presentation/devSession.ts +5 -0
  31. package/cli/runtime/commands.ts +60 -1
  32. package/cli/tsconfig.json +4 -1
  33. package/cli/utils/check.ts +1 -1
  34. package/client/app/component.tsx +13 -9
  35. package/client/dev/profiler/index.tsx +1511 -0
  36. package/client/dev/profiler/noop.tsx +5 -0
  37. package/client/dev/profiler/runtime.noop.ts +116 -0
  38. package/client/dev/profiler/runtime.ts +840 -0
  39. package/client/services/router/components/router.tsx +30 -2
  40. package/client/services/router/index.tsx +27 -3
  41. package/client/services/router/request/api.ts +133 -17
  42. package/commands/proteum/diagnostics.ts +11 -0
  43. package/common/dev/commands.ts +50 -0
  44. package/common/dev/diagnostics.ts +298 -0
  45. package/common/dev/profiler.ts +91 -0
  46. package/common/dev/proteumManifest.ts +135 -0
  47. package/common/dev/requestTrace.ts +109 -0
  48. package/common/env/proteumEnv.ts +284 -0
  49. package/common/router/index.ts +4 -22
  50. package/docs/dev-commands.md +86 -0
  51. package/docs/request-tracing.md +122 -0
  52. package/package.json +1 -2
  53. package/server/app/commands.ts +35 -370
  54. package/server/app/commandsManager.ts +393 -0
  55. package/server/app/container/config.ts +11 -49
  56. package/server/app/container/console/index.ts +2 -3
  57. package/server/app/container/index.ts +5 -2
  58. package/server/app/container/trace/index.ts +364 -0
  59. package/server/app/devCommands.ts +192 -0
  60. package/server/app/devDiagnostics.ts +53 -0
  61. package/server/app/index.ts +27 -4
  62. package/server/services/cron/CronTask.ts +73 -5
  63. package/server/services/cron/index.ts +34 -11
  64. package/server/services/fetch/index.ts +3 -10
  65. package/server/services/prisma/index.ts +66 -4
  66. package/server/services/router/http/index.ts +151 -0
  67. package/server/services/router/index.ts +200 -12
  68. package/server/services/router/request/api.ts +30 -1
  69. package/server/services/router/response/index.ts +83 -10
  70. package/server/services/router/response/page/document.tsx +16 -0
  71. package/server/services/router/response/page/index.tsx +27 -1
  72. package/skills/clean-project-code/SKILL.md +7 -2
  73. package/test-results/.last-run.json +4 -0
  74. package/types/aliases.d.ts +6 -0
  75. package/types/global/utils.d.ts +7 -14
  76. package/Rte.zip +0 -0
  77. package/agents/project/agents.md.zip +0 -0
  78. package/doc/TODO.md +0 -71
  79. package/doc/front/router.md +0 -27
  80. package/doc/workspace/workspace.png +0 -0
  81. package/doc/workspace/workspace2.png +0 -0
  82. package/doc/workspace/workspace_26.01.22.png +0 -0
  83. package/server/services/router/http/session.ts.old +0 -40
@@ -0,0 +1,1511 @@
1
+ import React from 'react';
2
+
3
+ import {
4
+ buildDoctorBlocks,
5
+ buildExplainBlocks,
6
+ buildExplainSummaryItems,
7
+ explainSectionNames,
8
+ formatManifestLocation,
9
+ type THumanTextBlock,
10
+ } from '@common/dev/diagnostics';
11
+ import type { TDevCommandDefinition, TDevCommandExecution } from '@common/dev/commands';
12
+ import type {
13
+ TProfilerCronTask,
14
+ TProfilerNavigationSession,
15
+ TProfilerPanel,
16
+ TProfilerSessionTrace,
17
+ } from '@common/dev/profiler';
18
+ import type { TRequestTrace, TTraceCall, TTraceSummaryValue } from '@common/dev/requestTrace';
19
+
20
+ import { profilerRuntime } from './runtime';
21
+
22
+ const profilerStyles = `
23
+ .proteum-profiler {
24
+ --profiler-bg: #000000;
25
+ --profiler-bg-strong: #000000;
26
+ --profiler-surface-hover: rgba(22, 33, 48, 0.32);
27
+ --profiler-line: rgba(155, 188, 214, 0.16);
28
+ --profiler-line-strong: rgba(155, 188, 214, 0.28);
29
+ --profiler-text: #e5f2ff;
30
+ --profiler-muted: rgba(213, 228, 242, 0.64);
31
+ --profiler-brand: #8fd9ff;
32
+ --profiler-ok: #7af4b4;
33
+ --profiler-warn: #ffd369;
34
+ --profiler-error: #ff9797;
35
+ position: fixed;
36
+ inset-inline: 0;
37
+ bottom: 0;
38
+ z-index: 2147483000;
39
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
40
+ color: var(--profiler-text);
41
+ letter-spacing: 0.01em;
42
+ }
43
+
44
+ .proteum-profiler__bar,
45
+ .proteum-profiler__panel,
46
+ .proteum-profiler__handle {
47
+ position: relative;
48
+ box-sizing: border-box;
49
+ }
50
+
51
+ .proteum-profiler__bar::before,
52
+ .proteum-profiler__panel::before,
53
+ .proteum-profiler__handle::before {
54
+ display: none;
55
+ }
56
+
57
+ .proteum-profiler__bar {
58
+ display: flex;
59
+ align-items: center;
60
+ gap: 0;
61
+ min-height: 32px;
62
+ padding: 6px 10px calc(6px + env(safe-area-inset-bottom, 0px));
63
+ border-top: 1px solid var(--profiler-line-strong);
64
+ background: #000000;
65
+ backdrop-filter: none;
66
+ box-shadow: none;
67
+ overflow-x: auto;
68
+ scrollbar-width: none;
69
+ }
70
+
71
+ .proteum-profiler__bar::-webkit-scrollbar,
72
+ .proteum-profiler__panelTabs::-webkit-scrollbar {
73
+ display: none;
74
+ }
75
+
76
+ .proteum-profiler__token {
77
+ flex: 0 0 auto;
78
+ display: inline-flex;
79
+ align-items: center;
80
+ gap: 6px;
81
+ min-height: 20px;
82
+ padding: 0 10px;
83
+ border: none;
84
+ border-inline-start: 1px solid var(--profiler-line);
85
+ background: transparent;
86
+ color: var(--profiler-muted);
87
+ font-size: 11px;
88
+ line-height: 1;
89
+ letter-spacing: 0.06em;
90
+ text-transform: uppercase;
91
+ cursor: pointer;
92
+ white-space: nowrap;
93
+ }
94
+
95
+ .proteum-profiler__token:first-child {
96
+ padding-inline-start: 0;
97
+ border-inline-start: none;
98
+ }
99
+
100
+ .proteum-profiler__token:hover {
101
+ color: var(--profiler-text);
102
+ background: var(--profiler-surface-hover);
103
+ }
104
+
105
+ .proteum-profiler__token--brand {
106
+ color: var(--profiler-brand);
107
+ font-weight: 700;
108
+ }
109
+
110
+ .proteum-profiler__token--ok {
111
+ color: var(--profiler-ok);
112
+ }
113
+
114
+ .proteum-profiler__token--warn {
115
+ color: var(--profiler-warn);
116
+ }
117
+
118
+ .proteum-profiler__token--error {
119
+ color: var(--profiler-error);
120
+ }
121
+
122
+ .proteum-profiler__spacer {
123
+ flex: 1 1 auto;
124
+ min-width: 16px;
125
+ }
126
+
127
+ .proteum-profiler__handle {
128
+ position: fixed;
129
+ right: 10px;
130
+ bottom: calc(10px + env(safe-area-inset-bottom, 0px));
131
+ display: inline-flex;
132
+ align-items: center;
133
+ gap: 10px;
134
+ min-height: 30px;
135
+ padding: 0 12px;
136
+ border: 1px solid var(--profiler-line-strong);
137
+ border-radius: 0;
138
+ background: #000000;
139
+ backdrop-filter: none;
140
+ color: var(--profiler-brand);
141
+ box-shadow: none;
142
+ cursor: pointer;
143
+ font-size: 11px;
144
+ letter-spacing: 0.08em;
145
+ text-transform: uppercase;
146
+ }
147
+
148
+ .proteum-profiler__panel {
149
+ position: fixed;
150
+ inset-inline: 0;
151
+ bottom: calc(32px + env(safe-area-inset-bottom, 0px));
152
+ display: grid;
153
+ grid-template-rows: auto 1fr;
154
+ height: 50vh;
155
+ max-height: 50vh;
156
+ margin: 0;
157
+ border: 1px solid var(--profiler-line-strong);
158
+ border-bottom: none;
159
+ border-left: none;
160
+ border-right: none;
161
+ border-radius: 0;
162
+ background: #000000;
163
+ backdrop-filter: none;
164
+ box-shadow: none;
165
+ overflow: hidden;
166
+ }
167
+
168
+ .proteum-profiler__panelHeader {
169
+ display: flex;
170
+ align-items: center;
171
+ justify-content: flex-start;
172
+ gap: 12px;
173
+ padding: 12px 14px 10px;
174
+ border-bottom: 1px solid var(--profiler-line);
175
+ min-width: 0;
176
+ }
177
+
178
+ .proteum-profiler__panelTabs {
179
+ display: flex;
180
+ align-items: center;
181
+ gap: 14px;
182
+ overflow: auto;
183
+ padding: 0;
184
+ border-bottom: none;
185
+ scrollbar-width: none;
186
+ flex: 1 1 auto;
187
+ min-width: 0;
188
+ }
189
+
190
+ .proteum-profiler__pill {
191
+ display: inline-flex;
192
+ align-items: center;
193
+ gap: 6px;
194
+ min-height: 20px;
195
+ padding: 0;
196
+ border: none;
197
+ border-bottom: 1px solid transparent;
198
+ background: transparent;
199
+ font-size: 11px;
200
+ color: var(--profiler-muted);
201
+ cursor: pointer;
202
+ white-space: nowrap;
203
+ letter-spacing: 0.05em;
204
+ text-transform: uppercase;
205
+ }
206
+
207
+ .proteum-profiler__pill:hover {
208
+ color: var(--profiler-text);
209
+ border-bottom-color: var(--profiler-line-strong);
210
+ }
211
+
212
+ .proteum-profiler__pill--active {
213
+ color: var(--profiler-brand);
214
+ border-bottom-color: var(--profiler-brand);
215
+ }
216
+
217
+ .proteum-profiler__pill:disabled {
218
+ opacity: 0.44;
219
+ cursor: default;
220
+ }
221
+
222
+ .proteum-profiler__select {
223
+ flex: 0 1 280px;
224
+ min-width: 160px;
225
+ height: 28px;
226
+ padding: 0 28px 0 10px;
227
+ border: 1px solid var(--profiler-line);
228
+ background-color: #000000;
229
+ background-image:
230
+ linear-gradient(45deg, transparent 50%, var(--profiler-muted) 50%),
231
+ linear-gradient(135deg, var(--profiler-muted) 50%, transparent 50%);
232
+ background-position: calc(100% - 14px) 11px, calc(100% - 9px) 11px;
233
+ background-repeat: no-repeat;
234
+ background-size: 5px 5px;
235
+ color: var(--profiler-text);
236
+ font: inherit;
237
+ font-size: 11px;
238
+ outline: none;
239
+ appearance: none;
240
+ }
241
+
242
+ .proteum-profiler__select option {
243
+ background: #000000;
244
+ color: var(--profiler-text);
245
+ }
246
+
247
+ .proteum-profiler__panelBody {
248
+ overflow: auto;
249
+ padding: 0 14px 16px;
250
+ }
251
+
252
+ .proteum-profiler__metrics {
253
+ display: grid;
254
+ gap: 0;
255
+ padding-top: 10px;
256
+ }
257
+
258
+ .proteum-profiler__metricRow {
259
+ display: grid;
260
+ grid-template-columns: minmax(104px, 140px) 1fr;
261
+ gap: 12px;
262
+ padding: 8px 0;
263
+ border-top: 1px solid var(--profiler-line);
264
+ align-items: start;
265
+ }
266
+
267
+ .proteum-profiler__metricRow:first-child {
268
+ border-top: none;
269
+ }
270
+
271
+ .proteum-profiler__metricLabel {
272
+ color: var(--profiler-muted);
273
+ font-size: 10px;
274
+ letter-spacing: 0.08em;
275
+ text-transform: uppercase;
276
+ }
277
+
278
+ .proteum-profiler__metricValue {
279
+ font-size: 12px;
280
+ line-height: 1.45;
281
+ word-break: break-word;
282
+ }
283
+
284
+ .proteum-profiler__section {
285
+ display: grid;
286
+ gap: 8px;
287
+ padding: 12px 0 0;
288
+ border-top: 1px solid var(--profiler-line);
289
+ }
290
+
291
+ .proteum-profiler__sectionHeader {
292
+ display: flex;
293
+ align-items: center;
294
+ justify-content: space-between;
295
+ gap: 12px;
296
+ }
297
+
298
+ .proteum-profiler__actions {
299
+ display: flex;
300
+ align-items: center;
301
+ gap: 10px;
302
+ flex-wrap: wrap;
303
+ }
304
+
305
+ .proteum-profiler__panelHeader .proteum-profiler__actions {
306
+ flex: 0 0 auto;
307
+ margin-left: auto;
308
+ }
309
+
310
+ .proteum-profiler__sectionTitle {
311
+ font-size: 11px;
312
+ font-weight: 700;
313
+ color: var(--profiler-brand);
314
+ letter-spacing: 0.08em;
315
+ text-transform: uppercase;
316
+ }
317
+
318
+ .proteum-profiler__list {
319
+ display: grid;
320
+ gap: 0;
321
+ }
322
+
323
+ .proteum-profiler__list > .proteum-profiler__row:first-child {
324
+ border-top: none;
325
+ padding-top: 2px;
326
+ }
327
+
328
+ .proteum-profiler__row {
329
+ display: grid;
330
+ gap: 4px;
331
+ padding: 8px 0;
332
+ border-top: 1px solid var(--profiler-line);
333
+ }
334
+
335
+ .proteum-profiler__row--interactive {
336
+ width: 100%;
337
+ appearance: none;
338
+ background: transparent;
339
+ border-inline: none;
340
+ border-bottom: none;
341
+ border-radius: 0;
342
+ text-align: left;
343
+ color: inherit;
344
+ cursor: pointer;
345
+ }
346
+
347
+ .proteum-profiler__row--interactive:hover {
348
+ background: var(--profiler-surface-hover);
349
+ }
350
+
351
+ .proteum-profiler__rowHeader {
352
+ display: flex;
353
+ align-items: flex-start;
354
+ justify-content: space-between;
355
+ gap: 10px;
356
+ font-size: 11px;
357
+ line-height: 1.45;
358
+ }
359
+
360
+ .proteum-profiler__mono {
361
+ font-family: inherit;
362
+ font-size: 11px;
363
+ line-height: 1.5;
364
+ }
365
+
366
+ .proteum-profiler__muted {
367
+ color: var(--profiler-muted);
368
+ }
369
+
370
+ .proteum-profiler__pre {
371
+ margin: 0;
372
+ white-space: pre-wrap;
373
+ word-break: break-word;
374
+ padding-top: 8px;
375
+ border-top: 1px solid var(--profiler-line);
376
+ }
377
+
378
+ .proteum-profiler__detail {
379
+ display: grid;
380
+ gap: 10px;
381
+ padding: 10px 0 14px;
382
+ border-top: 1px dashed var(--profiler-line);
383
+ }
384
+
385
+ .proteum-profiler__detailLine {
386
+ display: grid;
387
+ gap: 4px;
388
+ }
389
+
390
+ .proteum-profiler__detailLabel {
391
+ color: var(--profiler-muted);
392
+ font-size: 10px;
393
+ letter-spacing: 0.08em;
394
+ text-transform: uppercase;
395
+ }
396
+
397
+ .proteum-profiler__tags {
398
+ display: flex;
399
+ flex-wrap: wrap;
400
+ gap: 8px;
401
+ }
402
+
403
+ .proteum-profiler__tag {
404
+ display: inline-flex;
405
+ align-items: center;
406
+ min-height: 0;
407
+ padding: 0;
408
+ font-size: 11px;
409
+ color: var(--profiler-muted);
410
+ }
411
+
412
+ .proteum-profiler__tag::before,
413
+ .proteum-profiler__tag::after {
414
+ color: var(--profiler-line-strong);
415
+ }
416
+
417
+ .proteum-profiler__tag::before {
418
+ content: '[';
419
+ }
420
+
421
+ .proteum-profiler__tag::after {
422
+ content: ']';
423
+ }
424
+
425
+ .proteum-profiler__empty {
426
+ padding: 12px 0;
427
+ border-top: 1px dashed var(--profiler-line);
428
+ color: var(--profiler-muted);
429
+ }
430
+
431
+ @media (max-width: 900px) {
432
+ .proteum-profiler__panel {
433
+ height: 50vh;
434
+ max-height: 50vh;
435
+ margin: 0;
436
+ border-left: 0;
437
+ border-right: 0;
438
+ border-radius: 0;
439
+ }
440
+
441
+ .proteum-profiler__bar {
442
+ padding-inline: 8px;
443
+ }
444
+
445
+ .proteum-profiler__panelHeader,
446
+ .proteum-profiler__panelTabs,
447
+ .proteum-profiler__panelBody {
448
+ padding-inline: 10px;
449
+ }
450
+
451
+ .proteum-profiler__panelTabs {
452
+ gap: 10px;
453
+ }
454
+
455
+ .proteum-profiler__metricRow {
456
+ grid-template-columns: minmax(90px, 110px) 1fr;
457
+ }
458
+
459
+ .proteum-profiler__select {
460
+ min-width: 132px;
461
+ }
462
+ }
463
+ `;
464
+
465
+ type TSessionSummary = {
466
+ apiAsyncCount: number;
467
+ apiSyncCount: number;
468
+ errorCount: number;
469
+ primaryTrace?: TProfilerSessionTrace;
470
+ renderMs?: number;
471
+ routeLabel: string;
472
+ ssrPayloadBytes?: number;
473
+ statusLabel: string;
474
+ totalMs?: number;
475
+ };
476
+ type TProfilerState = ReturnType<typeof profilerRuntime.getState>;
477
+
478
+ const panelLabels: Record<TProfilerPanel, string> = {
479
+ summary: 'Summary',
480
+ timeline: 'Timeline',
481
+ routing: 'Routing',
482
+ controller: 'Controller',
483
+ ssr: 'SSR',
484
+ api: 'API',
485
+ explain: 'Explain',
486
+ doctor: 'Doctor',
487
+ commands: 'Commands',
488
+ cron: 'Cron',
489
+ errors: 'Errors',
490
+ };
491
+
492
+ const getSelectedSession = (sessions: TProfilerNavigationSession[], selectedSessionId?: string, currentSessionId?: string) =>
493
+ sessions.find((session) => session.id === selectedSessionId) ||
494
+ sessions.find((session) => session.id === currentSessionId) ||
495
+ sessions[sessions.length - 1];
496
+
497
+ const getSessionSelectorLabel = (session: TProfilerNavigationSession) => truncate(session.path || session.url || session.label, 56);
498
+ const truncate = (value: string, max = 96) => (value.length <= max ? value : `${value.slice(0, max)}...`);
499
+ const readNumber = (value: TTraceSummaryValue | undefined) => (typeof value === 'number' ? value : undefined);
500
+ const readString = (value: TTraceSummaryValue | undefined) => (typeof value === 'string' ? value : undefined);
501
+ const formatDuration = (value?: number) => (value === undefined ? 'pending' : `${Math.round(value)} ms`);
502
+ const formatBytes = (value?: number) => (value === undefined ? 'n/a' : `${(value / 1024).toFixed(value >= 1024 ? 1 : 2)} KB`);
503
+ const formatTimestamp = (value?: string) => {
504
+ if (!value) return 'never';
505
+ const date = new Date(value);
506
+ return Number.isNaN(date.valueOf()) ? value : date.toLocaleString();
507
+ };
508
+ const formatCronFrequency = (task: TProfilerCronTask) =>
509
+ task.frequency.kind === 'cron' ? task.frequency.value : `once at ${formatTimestamp(task.frequency.value)}`;
510
+ const formatStructuredValue = (value: unknown) => {
511
+ try {
512
+ return JSON.stringify(value, null, 2);
513
+ } catch (error) {
514
+ return String(value);
515
+ }
516
+ };
517
+
518
+ const renderSummaryValue = (value: TTraceSummaryValue | undefined): string => {
519
+ if (value === undefined) return '';
520
+ if (value === null) return 'null';
521
+ if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') return String(value);
522
+ if (value.kind === 'undefined') return 'undefined';
523
+ if (value.kind === 'redacted') return `[redacted: ${value.reason}]`;
524
+ if (value.kind === 'error') return `${value.name}: ${value.message}`;
525
+ if (value.kind === 'array') return `Array(${value.length})`;
526
+ if (value.kind === 'object') return `${value.constructorName} { ${Object.keys(value.entries).join(', ')} }`;
527
+ if (value.kind === 'buffer') return `Buffer(${value.byteLength})`;
528
+ if ('value' in value) return String(value.value);
529
+ if ('size' in value) return String(value.size);
530
+ if ('name' in value) return value.name;
531
+ return JSON.stringify(value);
532
+ };
533
+
534
+ const toSummaryJsonValue = (value: TTraceSummaryValue | undefined): unknown => {
535
+ if (value === undefined) return 'undefined';
536
+ if (value === null) return null;
537
+ if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') return value;
538
+ if (value.kind === 'undefined') return 'undefined';
539
+ if (value.kind === 'redacted') return `[redacted: ${value.reason}]`;
540
+ if (value.kind === 'bigint') return `${value.value}n`;
541
+ if (value.kind === 'symbol') return value.value;
542
+ if (value.kind === 'function') return `[Function ${value.name}]`;
543
+ if (value.kind === 'date') return value.value;
544
+ if (value.kind === 'error') return { name: value.name, message: value.message, stack: value.stack };
545
+ if (value.kind === 'buffer') return `[Buffer ${value.byteLength} bytes]`;
546
+ if (value.kind === 'map') return `[Map(${value.size})]`;
547
+ if (value.kind === 'set') return `[Set(${value.size})]`;
548
+ if (value.kind === 'array') {
549
+ const items = value.items.map((item) => toSummaryJsonValue(item));
550
+ if (value.truncated) items.push(`... ${Math.max(0, value.length - value.items.length)} more item(s)`);
551
+ return items;
552
+ }
553
+
554
+ const objectValue: Record<string, unknown> = {};
555
+ for (const [key, entry] of Object.entries(value.entries)) objectValue[key] = toSummaryJsonValue(entry);
556
+ if (value.truncated) objectValue.__truncated = `${Math.max(0, value.keys.length - Object.keys(value.entries).length)} more key(s)`;
557
+ return objectValue;
558
+ };
559
+
560
+ const formatSummaryJson = (value: TTraceSummaryValue | undefined) => {
561
+ if (value === undefined) return 'undefined';
562
+ if (typeof value === 'object' && value !== null && 'kind' in value && value.kind === 'undefined') return 'undefined';
563
+ return JSON.stringify(toSummaryJsonValue(value), null, 2);
564
+ };
565
+
566
+ const formatSummaryLiteral = (value: TTraceSummaryValue | undefined, depth = 1): string => {
567
+ if (value === undefined) return '';
568
+ if (value === null) return 'null';
569
+ if (typeof value === 'string') return JSON.stringify(value);
570
+ if (typeof value === 'number' || typeof value === 'boolean') return String(value);
571
+ if (depth <= 0) return renderSummaryValue(value);
572
+ if (value.kind === 'undefined') return 'undefined';
573
+ if (value.kind === 'redacted') return `[redacted: ${value.reason}]`;
574
+ if (value.kind === 'bigint') return `${value.value}n`;
575
+ if (value.kind === 'symbol') return value.value;
576
+ if (value.kind === 'function') return `[Function ${value.name}]`;
577
+ if (value.kind === 'date') return JSON.stringify(value.value);
578
+ if (value.kind === 'error') return `${value.name}(${JSON.stringify(value.message)})`;
579
+ if (value.kind === 'buffer') return `Buffer(${value.byteLength})`;
580
+ if (value.kind === 'map') return `Map(${value.size})`;
581
+ if (value.kind === 'set') return `Set(${value.size})`;
582
+ if (value.kind === 'array') {
583
+ const items = value.items.slice(0, 4).map((item) => formatSummaryLiteral(item, depth - 1));
584
+ if (value.truncated || value.length > items.length) items.push('...');
585
+ return `[${items.join(', ')}]`;
586
+ }
587
+
588
+ const entries = Object.entries(value.entries)
589
+ .slice(0, 4)
590
+ .map(([key, entry]) => `${key}: ${formatSummaryLiteral(entry, depth - 1)}`);
591
+ if (value.truncated || value.keys.length > entries.length) entries.push('...');
592
+ return `{ ${entries.join(', ')} }`;
593
+ };
594
+
595
+ const getApiReferenceName = (method: string, path: string, fallbackLabel?: string) => {
596
+ if (path.startsWith('/api/')) return path.slice('/api/'.length).split('/').filter(Boolean).join('.');
597
+
598
+ const rawName = `${method} ${path}`.trim();
599
+ if (rawName) return rawName;
600
+ return fallbackLabel || 'request';
601
+ };
602
+
603
+ const formatApiReference = (method: string, path: string, requestData?: TTraceSummaryValue, fallbackLabel?: string) => {
604
+ const args = formatSummaryLiteral(requestData, 1);
605
+ return `${getApiReferenceName(method, path, fallbackLabel)}(${truncate(args, 112)})`;
606
+ };
607
+
608
+ const getTraceRequestData = (trace: TRequestTrace | undefined) =>
609
+ trace?.events.find((event) => event.type === 'request.start')?.details.data;
610
+
611
+ const getTraceResultData = (trace: TRequestTrace | undefined) =>
612
+ [...findTraceEvents(trace, ['controller.result'])]
613
+ .reverse()
614
+ .find((event) => event.details.kind === 'json' && event.details.data !== undefined)?.details.data;
615
+
616
+ const findTraceEvents = (trace: TRequestTrace | undefined, eventTypes: string[]) =>
617
+ trace?.events.filter((event) => eventTypes.includes(event.type)) || [];
618
+
619
+ const getSummary = (session: TProfilerNavigationSession): TSessionSummary => {
620
+ const primaryTrace =
621
+ session.traces.find((trace) => trace.kind === 'initial-root' && trace.trace) ||
622
+ session.traces.find((trace) => trace.kind === 'navigation-data' && trace.trace) ||
623
+ session.traces.find((trace) => trace.trace);
624
+ const trace = primaryTrace?.trace;
625
+ const syncCalls = session.traces.flatMap((traceItem) =>
626
+ traceItem.trace?.calls.filter((call) => call.origin === 'ssr-fetcher' || call.origin === 'api-batch-fetcher') || [],
627
+ );
628
+ const asyncCount = session.traces.filter((traceItem) => traceItem.kind === 'async').length;
629
+ const errorCount =
630
+ session.steps.filter((step) => step.status === 'error').length +
631
+ session.traces.filter((traceItem) => traceItem.status === 'error').length +
632
+ syncCalls.filter((call) => call.errorMessage || (call.statusCode !== undefined && call.statusCode >= 400)).length;
633
+ const renderStart = trace?.events.find((event) => event.type === 'render.start');
634
+ const renderEnd = trace?.events.find((event) => event.type === 'render.end');
635
+ const localRender = [...session.steps].reverse().find((step) => step.label === 'Render' && step.durationMs !== undefined);
636
+ const ssrPayload = trace?.events.find((event) => event.type === 'ssr.payload');
637
+ const routeLabel = session.routeLabel || readString(renderStart?.details.routeId) || session.path;
638
+
639
+ return {
640
+ apiAsyncCount: asyncCount,
641
+ apiSyncCount: syncCalls.length,
642
+ errorCount,
643
+ primaryTrace,
644
+ renderMs:
645
+ renderStart && renderEnd
646
+ ? Math.max(0, renderEnd.elapsedMs - renderStart.elapsedMs)
647
+ : localRender?.durationMs,
648
+ routeLabel,
649
+ ssrPayloadBytes: readNumber(ssrPayload?.details.serializedBytes),
650
+ statusLabel: session.kind === 'client-navigation' ? 'NAV' : trace ? `${trace.statusCode || 'pending'} ${trace.method}` : 'SSR',
651
+ totalMs: session.kind === 'client-navigation' ? session.durationMs : trace?.durationMs ?? session.durationMs,
652
+ };
653
+ };
654
+
655
+ const StatusToken = ({ label, onClick, tone = 'ok' }: { label: string; onClick: () => void; tone?: 'ok' | 'warn' | 'error' }) => (
656
+ <button className={`proteum-profiler__token proteum-profiler__token--${tone}`} onClick={onClick} type="button">
657
+ {label}
658
+ </button>
659
+ );
660
+
661
+ const SummaryRow = ({ label, value }: { label: string; value: React.ReactNode }) => (
662
+ <div className="proteum-profiler__metricRow">
663
+ <div className="proteum-profiler__metricLabel">{label}</div>
664
+ <div className="proteum-profiler__metricValue">{value}</div>
665
+ </div>
666
+ );
667
+
668
+ const ApiRequestEntry = ({
669
+ durationMs,
670
+ errorMessage,
671
+ finishedAt,
672
+ label,
673
+ method,
674
+ path,
675
+ requestData,
676
+ result,
677
+ startedAt,
678
+ statusCode,
679
+ statusLabel,
680
+ tags,
681
+ }: {
682
+ durationMs?: number;
683
+ errorMessage?: string;
684
+ finishedAt?: string;
685
+ label?: string;
686
+ method: string;
687
+ path: string;
688
+ requestData?: TTraceSummaryValue;
689
+ result?: TTraceSummaryValue;
690
+ startedAt: string;
691
+ statusCode?: number;
692
+ statusLabel?: string;
693
+ tags: string[];
694
+ }) => {
695
+ const [isOpen, setOpen] = React.useState(false);
696
+ const statusText = statusCode !== undefined ? String(statusCode) : statusLabel || 'pending';
697
+
698
+ return (
699
+ <>
700
+ <button className="proteum-profiler__row proteum-profiler__row--interactive" onClick={() => setOpen((current) => !current)} type="button">
701
+ <div className="proteum-profiler__rowHeader">
702
+ <strong>{formatApiReference(method, path, requestData, label)}</strong>
703
+ <span className="proteum-profiler__mono proteum-profiler__muted">
704
+ {formatDuration(durationMs)} | {statusText}
705
+ </span>
706
+ </div>
707
+ {method || path ? <div className="proteum-profiler__mono proteum-profiler__muted">{method} {path}</div> : null}
708
+ <div className="proteum-profiler__tags">
709
+ {tags.map((tag) => (
710
+ <span className="proteum-profiler__tag" key={`${label || method}:${path}:${tag}`}>
711
+ {tag}
712
+ </span>
713
+ ))}
714
+ {errorMessage ? <span className="proteum-profiler__tag">{truncate(errorMessage, 72)}</span> : null}
715
+ </div>
716
+ </button>
717
+
718
+ {isOpen ? (
719
+ <div className="proteum-profiler__detail">
720
+ <div className="proteum-profiler__detailLine">
721
+ <div className="proteum-profiler__detailLabel">Performance</div>
722
+ <div className="proteum-profiler__mono">
723
+ duration={formatDuration(durationMs)} | status={statusText} | started={formatTimestamp(startedAt)}
724
+ {finishedAt ? ` | finished=${formatTimestamp(finishedAt)}` : ''}
725
+ </div>
726
+ </div>
727
+ <div className="proteum-profiler__detailLine">
728
+ <div className="proteum-profiler__detailLabel">Arguments</div>
729
+ <pre className="proteum-profiler__mono proteum-profiler__pre">{formatSummaryJson(requestData)}</pre>
730
+ </div>
731
+ <div className="proteum-profiler__detailLine">
732
+ <div className="proteum-profiler__detailLabel">Result</div>
733
+ <pre className="proteum-profiler__mono proteum-profiler__pre">{formatSummaryJson(result)}</pre>
734
+ </div>
735
+ {errorMessage ? (
736
+ <div className="proteum-profiler__detailLine">
737
+ <div className="proteum-profiler__detailLabel">Error</div>
738
+ <div className="proteum-profiler__mono">{errorMessage}</div>
739
+ </div>
740
+ ) : null}
741
+ </div>
742
+ ) : null}
743
+ </>
744
+ );
745
+ };
746
+
747
+ const TraceRows = ({ trace }: { trace: TRequestTrace }) => (
748
+ <div className="proteum-profiler__section">
749
+ <div className="proteum-profiler__sectionHeader">
750
+ <div className="proteum-profiler__sectionTitle">
751
+ {trace.method} {trace.path}
752
+ </div>
753
+ <div className="proteum-profiler__mono proteum-profiler__muted">{trace.id}</div>
754
+ </div>
755
+
756
+ {trace.calls.length > 0 && (
757
+ <div className="proteum-profiler__list">
758
+ {trace.calls.map((call) => (
759
+ <div className="proteum-profiler__row" key={call.id}>
760
+ <div className="proteum-profiler__rowHeader">
761
+ <strong>
762
+ {call.label} {call.method ? `(${call.method} ${call.path})` : ''}
763
+ </strong>
764
+ <span className="proteum-profiler__mono proteum-profiler__muted">
765
+ {formatDuration(call.durationMs)}
766
+ {call.statusCode !== undefined ? ` | ${call.statusCode}` : ''}
767
+ </span>
768
+ </div>
769
+ <div className="proteum-profiler__tags">
770
+ <span className="proteum-profiler__tag">{call.origin}</span>
771
+ {call.fetcherId ? <span className="proteum-profiler__tag">fetcher:{call.fetcherId}</span> : null}
772
+ {call.requestDataKeys.map((key) => (
773
+ <span className="proteum-profiler__tag" key={`${call.id}:req:${key}`}>
774
+ req:{key}
775
+ </span>
776
+ ))}
777
+ {call.resultKeys.map((key) => (
778
+ <span className="proteum-profiler__tag" key={`${call.id}:res:${key}`}>
779
+ res:{key}
780
+ </span>
781
+ ))}
782
+ {call.errorMessage ? <span className="proteum-profiler__tag">{truncate(call.errorMessage, 72)}</span> : null}
783
+ </div>
784
+ </div>
785
+ ))}
786
+ </div>
787
+ )}
788
+
789
+ <div className="proteum-profiler__list">
790
+ {trace.events.map((event) => (
791
+ <div className="proteum-profiler__row" key={`${trace.id}:${event.index}`}>
792
+ <div className="proteum-profiler__rowHeader">
793
+ <strong>{event.type}</strong>
794
+ <span className="proteum-profiler__mono proteum-profiler__muted">{formatDuration(event.elapsedMs)}</span>
795
+ </div>
796
+ <div className="proteum-profiler__tags">
797
+ {Object.entries(event.details).map(([key, value]) => (
798
+ <span className="proteum-profiler__tag" key={`${trace.id}:${event.index}:${key}`}>
799
+ {key}:{truncate(renderSummaryValue(value), 72)}
800
+ </span>
801
+ ))}
802
+ </div>
803
+ </div>
804
+ ))}
805
+ </div>
806
+ </div>
807
+ );
808
+
809
+ const SimpleSection = ({ empty, rows, title }: { empty: string; rows: Array<{ key: string; title: string; value: string }>; title: string }) => (
810
+ <div className="proteum-profiler__section">
811
+ <div className="proteum-profiler__sectionTitle">{title}</div>
812
+ {rows.length === 0 ? (
813
+ <div className="proteum-profiler__empty">{empty}</div>
814
+ ) : (
815
+ <div className="proteum-profiler__list">
816
+ {rows.map((row) => (
817
+ <div className="proteum-profiler__row" key={row.key}>
818
+ <div className="proteum-profiler__rowHeader">
819
+ <strong>{row.title}</strong>
820
+ </div>
821
+ <div className="proteum-profiler__mono">{row.value}</div>
822
+ </div>
823
+ ))}
824
+ </div>
825
+ )}
826
+ </div>
827
+ );
828
+
829
+ const TextBlocks = ({ blocks }: { blocks: THumanTextBlock[] }) => (
830
+ <>
831
+ {blocks.map((block) => (
832
+ <div className="proteum-profiler__section" key={block.title}>
833
+ <div className="proteum-profiler__sectionTitle">{block.title}</div>
834
+ {block.items.length === 0 ? (
835
+ <div className="proteum-profiler__empty">{block.empty || 'none'}</div>
836
+ ) : (
837
+ <div className="proteum-profiler__list">
838
+ {block.items.map((item, index) => (
839
+ <div className="proteum-profiler__row" key={`${block.title}:${index}`}>
840
+ <div className="proteum-profiler__mono">{item}</div>
841
+ </div>
842
+ ))}
843
+ </div>
844
+ )}
845
+ </div>
846
+ ))}
847
+ </>
848
+ );
849
+
850
+ const renderPanel = (panel: TProfilerPanel, session: TProfilerNavigationSession, summary: TSessionSummary, state: TProfilerState) => {
851
+ const primaryTrace = summary.primaryTrace?.trace;
852
+
853
+ if (panel === 'summary') {
854
+ return (
855
+ <div className="proteum-profiler__metrics">
856
+ <SummaryRow label="Session" value={session.label} />
857
+ <SummaryRow label="Status" value={summary.statusLabel} />
858
+ <SummaryRow label="Duration" value={formatDuration(summary.totalMs)} />
859
+ <SummaryRow label="Route" value={summary.routeLabel} />
860
+ <SummaryRow
861
+ label="SSR"
862
+ value={
863
+ summary.ssrPayloadBytes !== undefined
864
+ ? `${formatDuration(summary.renderMs)} | ${formatBytes(summary.ssrPayloadBytes)}`
865
+ : formatDuration(summary.renderMs)
866
+ }
867
+ />
868
+ <SummaryRow label="API" value={`sync ${summary.apiSyncCount} / async ${summary.apiAsyncCount}`} />
869
+ <SummaryRow label="Errors" value={String(summary.errorCount)} />
870
+ <SummaryRow label="Request" value={session.requestId || 'client-only'} />
871
+ </div>
872
+ );
873
+ }
874
+
875
+ if (panel === 'timeline') {
876
+ return (
877
+ <div className="proteum-profiler__section">
878
+ <div className="proteum-profiler__sectionTitle">Navigation steps</div>
879
+ <div className="proteum-profiler__list">
880
+ {session.steps.map((step) => (
881
+ <div className="proteum-profiler__row" key={step.id}>
882
+ <div className="proteum-profiler__rowHeader">
883
+ <strong>{step.label}</strong>
884
+ <span className="proteum-profiler__mono proteum-profiler__muted">{formatDuration(step.durationMs)}</span>
885
+ </div>
886
+ <div className="proteum-profiler__tags">
887
+ <span className="proteum-profiler__tag">{step.status}</span>
888
+ {Object.entries(step.details || {}).map(([key, value]) => (
889
+ <span className="proteum-profiler__tag" key={`${step.id}:${key}`}>
890
+ {key}:{String(value)}
891
+ </span>
892
+ ))}
893
+ {step.errorMessage ? <span className="proteum-profiler__tag">{truncate(step.errorMessage, 72)}</span> : null}
894
+ </div>
895
+ </div>
896
+ ))}
897
+ </div>
898
+
899
+ {session.traces.map((trace) =>
900
+ trace.trace ? (
901
+ <TraceRows key={trace.id} trace={trace.trace} />
902
+ ) : (
903
+ <div className="proteum-profiler__row" key={trace.id}>
904
+ <div className="proteum-profiler__rowHeader">
905
+ <strong>{trace.label}</strong>
906
+ <span className="proteum-profiler__mono proteum-profiler__muted">{trace.status}</span>
907
+ </div>
908
+ <div className="proteum-profiler__mono">{trace.method} {trace.path}</div>
909
+ </div>
910
+ ),
911
+ )}
912
+ </div>
913
+ );
914
+ }
915
+
916
+ if (panel === 'routing') {
917
+ return (
918
+ <SimpleSection
919
+ empty="No routing data captured yet."
920
+ rows={findTraceEvents(primaryTrace, [
921
+ 'resolve.start',
922
+ 'resolve.controller-route',
923
+ 'resolve.route-match',
924
+ 'resolve.routes-evaluated',
925
+ 'resolve.not-found',
926
+ ]).map((event) => ({
927
+ key: `${event.index}:${event.type}`,
928
+ title: event.type,
929
+ value: Object.entries(event.details)
930
+ .map(([key, value]) => `${key}=${renderSummaryValue(value)}`)
931
+ .join(' '),
932
+ }))}
933
+ title="Routing"
934
+ />
935
+ );
936
+ }
937
+
938
+ if (panel === 'controller') {
939
+ return (
940
+ <SimpleSection
941
+ empty="No controller data captured yet."
942
+ rows={findTraceEvents(primaryTrace, ['controller.start', 'controller.result', 'setup.options', 'context.create']).map(
943
+ (event) => ({
944
+ key: `${event.index}:${event.type}`,
945
+ title: event.type,
946
+ value: Object.entries(event.details)
947
+ .map(([key, value]) => `${key}=${renderSummaryValue(value)}`)
948
+ .join(' '),
949
+ }),
950
+ )}
951
+ title="Controller"
952
+ />
953
+ );
954
+ }
955
+
956
+ if (panel === 'ssr') {
957
+ return (
958
+ <SimpleSection
959
+ empty="No SSR data captured for this session."
960
+ rows={findTraceEvents(primaryTrace, ['page.data', 'ssr.payload', 'render.start', 'render.end']).map((event) => ({
961
+ key: `${event.index}:${event.type}`,
962
+ title: event.type,
963
+ value: Object.entries(event.details)
964
+ .map(([key, value]) => `${key}=${renderSummaryValue(value)}`)
965
+ .join(' '),
966
+ }))}
967
+ title="SSR"
968
+ />
969
+ );
970
+ }
971
+
972
+ if (panel === 'api') {
973
+ const syncCalls = session.traces.flatMap((trace) =>
974
+ trace.trace?.calls.filter((call) => call.origin !== 'client-async') || [],
975
+ );
976
+ const asyncTraces = session.traces.filter((trace) => trace.kind === 'async');
977
+
978
+ return (
979
+ <div className="proteum-profiler__section">
980
+ <div className="proteum-profiler__sectionTitle">Synchronous calls</div>
981
+ {syncCalls.length === 0 ? (
982
+ <div className="proteum-profiler__empty">No synchronous SSR or batched API calls captured.</div>
983
+ ) : (
984
+ <div className="proteum-profiler__list">
985
+ {syncCalls.map((call: TTraceCall) => (
986
+ <ApiRequestEntry
987
+ durationMs={call.durationMs}
988
+ errorMessage={call.errorMessage}
989
+ finishedAt={call.finishedAt}
990
+ key={call.id}
991
+ label={call.label}
992
+ method={call.method}
993
+ path={call.path}
994
+ requestData={call.requestData}
995
+ result={call.result}
996
+ startedAt={call.startedAt}
997
+ statusCode={call.statusCode}
998
+ tags={[
999
+ call.origin,
1000
+ ...(call.fetcherId ? [`fetcher:${call.fetcherId}`] : []),
1001
+ ...call.requestDataKeys.map((key) => `arg:${key}`),
1002
+ ...call.resultKeys.map((key) => `res:${key}`),
1003
+ ]}
1004
+ />
1005
+ ))}
1006
+ </div>
1007
+ )}
1008
+
1009
+ <div className="proteum-profiler__sectionTitle">Async requests</div>
1010
+ {asyncTraces.length === 0 ? (
1011
+ <div className="proteum-profiler__empty">No async API calls captured.</div>
1012
+ ) : (
1013
+ <div className="proteum-profiler__list">
1014
+ {asyncTraces.map((trace) => (
1015
+ <ApiRequestEntry
1016
+ durationMs={trace.durationMs}
1017
+ errorMessage={trace.errorMessage || trace.trace?.errorMessage}
1018
+ finishedAt={trace.finishedAt}
1019
+ key={trace.id}
1020
+ label={trace.label}
1021
+ method={trace.method}
1022
+ path={trace.path}
1023
+ requestData={getTraceRequestData(trace.trace)}
1024
+ result={getTraceResultData(trace.trace)}
1025
+ startedAt={trace.startedAt}
1026
+ statusCode={trace.trace?.statusCode}
1027
+ statusLabel={trace.status}
1028
+ tags={[trace.status, ...(trace.requestId ? [`request:${trace.requestId}`] : [])]}
1029
+ />
1030
+ ))}
1031
+ </div>
1032
+ )}
1033
+ </div>
1034
+ );
1035
+ }
1036
+
1037
+ if (panel === 'explain') {
1038
+ const explain = state.explain;
1039
+ const blocks = explain.manifest
1040
+ ? [
1041
+ { title: 'Overview', items: buildExplainSummaryItems(explain.manifest) },
1042
+ ...buildExplainBlocks(explain.manifest, [...explainSectionNames]),
1043
+ ]
1044
+ : [];
1045
+
1046
+ return (
1047
+ <div className="proteum-profiler__section">
1048
+ <div className="proteum-profiler__sectionHeader">
1049
+ <div className="proteum-profiler__sectionTitle">Explain</div>
1050
+ <div className="proteum-profiler__actions">
1051
+ <button className="proteum-profiler__pill" onClick={() => void profilerRuntime.refreshExplain()} type="button">
1052
+ Refresh
1053
+ </button>
1054
+ </div>
1055
+ </div>
1056
+
1057
+ {explain.errorMessage ? (
1058
+ <div className="proteum-profiler__row">
1059
+ <div className="proteum-profiler__rowHeader">
1060
+ <strong>Last explain panel error</strong>
1061
+ </div>
1062
+ <div className="proteum-profiler__mono">{explain.errorMessage}</div>
1063
+ </div>
1064
+ ) : null}
1065
+
1066
+ {explain.status === 'loading' && !explain.manifest ? (
1067
+ <div className="proteum-profiler__empty">Loading explain data...</div>
1068
+ ) : !explain.manifest ? (
1069
+ <div className="proteum-profiler__empty">No explain manifest is available.</div>
1070
+ ) : (
1071
+ <>
1072
+ <div className="proteum-profiler__row">
1073
+ <div className="proteum-profiler__rowHeader">
1074
+ <strong>Manifest snapshot</strong>
1075
+ <span className="proteum-profiler__mono proteum-profiler__muted">
1076
+ {explain.lastLoadedAt ? formatTimestamp(explain.lastLoadedAt) : 'Not loaded'}
1077
+ </span>
1078
+ </div>
1079
+ <div className="proteum-profiler__mono">
1080
+ Same manifest-backed sections as `proteum explain`, rendered from the shared diagnostics contract.
1081
+ </div>
1082
+ </div>
1083
+ <TextBlocks blocks={blocks} />
1084
+ </>
1085
+ )}
1086
+ </div>
1087
+ );
1088
+ }
1089
+
1090
+ if (panel === 'doctor') {
1091
+ const doctor = state.doctor;
1092
+ const doctorRows =
1093
+ doctor.response?.diagnostics.map((diagnostic, index) => ({
1094
+ key: `${diagnostic.code}:${index}`,
1095
+ title: `[${diagnostic.level}] ${diagnostic.code}`,
1096
+ value: `${diagnostic.message} source=${diagnostic.filepath}${formatManifestLocation(
1097
+ diagnostic.sourceLocation?.line,
1098
+ diagnostic.sourceLocation?.column,
1099
+ )}${diagnostic.relatedFilepaths?.length ? ` related=${diagnostic.relatedFilepaths.join(',')}` : ''}`,
1100
+ })) || [];
1101
+ const doctorBlocks = state.explain.manifest ? buildDoctorBlocks(state.explain.manifest) : [];
1102
+
1103
+ return (
1104
+ <div className="proteum-profiler__section">
1105
+ <div className="proteum-profiler__sectionHeader">
1106
+ <div className="proteum-profiler__sectionTitle">Doctor</div>
1107
+ <div className="proteum-profiler__actions">
1108
+ <button className="proteum-profiler__pill" onClick={() => void profilerRuntime.refreshDoctor()} type="button">
1109
+ Refresh
1110
+ </button>
1111
+ </div>
1112
+ </div>
1113
+
1114
+ {doctor.errorMessage ? (
1115
+ <div className="proteum-profiler__row">
1116
+ <div className="proteum-profiler__rowHeader">
1117
+ <strong>Last doctor panel error</strong>
1118
+ </div>
1119
+ <div className="proteum-profiler__mono">{doctor.errorMessage}</div>
1120
+ </div>
1121
+ ) : null}
1122
+
1123
+ {doctor.status === 'loading' && !doctor.response ? (
1124
+ <div className="proteum-profiler__empty">Loading doctor diagnostics...</div>
1125
+ ) : !doctor.response ? (
1126
+ <div className="proteum-profiler__empty">No doctor diagnostics are available.</div>
1127
+ ) : (
1128
+ <>
1129
+ <div className="proteum-profiler__metrics">
1130
+ <SummaryRow label="Errors" value={String(doctor.response.summary.errors)} />
1131
+ <SummaryRow label="Warnings" value={String(doctor.response.summary.warnings)} />
1132
+ <SummaryRow label="Strict" value={doctor.response.summary.strictFailed ? 'failed' : 'ok'} />
1133
+ <SummaryRow
1134
+ label="Refreshed"
1135
+ value={doctor.lastLoadedAt ? formatTimestamp(doctor.lastLoadedAt) : 'Not loaded'}
1136
+ />
1137
+ </div>
1138
+ {doctorBlocks.length > 0 ? (
1139
+ <TextBlocks blocks={doctorBlocks} />
1140
+ ) : (
1141
+ <SimpleSection empty="No manifest diagnostics were found." rows={doctorRows} title="Diagnostics" />
1142
+ )}
1143
+ </>
1144
+ )}
1145
+ </div>
1146
+ );
1147
+ }
1148
+
1149
+ if (panel === 'commands') {
1150
+ const commandsState = state.commands;
1151
+
1152
+ return (
1153
+ <div className="proteum-profiler__section">
1154
+ <div className="proteum-profiler__sectionHeader">
1155
+ <div className="proteum-profiler__sectionTitle">Available commands</div>
1156
+ <div className="proteum-profiler__actions">
1157
+ <button className="proteum-profiler__pill" onClick={() => void profilerRuntime.refreshCommands()} type="button">
1158
+ Refresh
1159
+ </button>
1160
+ </div>
1161
+ </div>
1162
+
1163
+ <div className="proteum-profiler__row">
1164
+ <div className="proteum-profiler__rowHeader">
1165
+ <strong>Dev commands</strong>
1166
+ <span className="proteum-profiler__mono proteum-profiler__muted">
1167
+ {commandsState.commands.length} command{commandsState.commands.length === 1 ? '' : 's'}
1168
+ </span>
1169
+ </div>
1170
+ <div className="proteum-profiler__mono">
1171
+ Commands live under /commands, extend the Proteum Commands class, and run only in a dev context.
1172
+ </div>
1173
+ </div>
1174
+
1175
+ {commandsState.errorMessage ? (
1176
+ <div className="proteum-profiler__row">
1177
+ <div className="proteum-profiler__rowHeader">
1178
+ <strong>Last command panel error</strong>
1179
+ </div>
1180
+ <div className="proteum-profiler__mono">{commandsState.errorMessage}</div>
1181
+ </div>
1182
+ ) : null}
1183
+
1184
+ {commandsState.status === 'loading' && commandsState.commands.length === 0 ? (
1185
+ <div className="proteum-profiler__empty">Loading commands...</div>
1186
+ ) : commandsState.commands.length === 0 ? (
1187
+ <div className="proteum-profiler__empty">No commands are registered for this app.</div>
1188
+ ) : (
1189
+ <div className="proteum-profiler__list">
1190
+ {commandsState.commands.map((command: TDevCommandDefinition) => {
1191
+ const execution = commandsState.executions[command.path] as TDevCommandExecution | undefined;
1192
+ return (
1193
+ <div className="proteum-profiler__row" key={command.path}>
1194
+ <div className="proteum-profiler__rowHeader">
1195
+ <strong>{command.path}</strong>
1196
+ <div className="proteum-profiler__actions">
1197
+ <span className="proteum-profiler__mono proteum-profiler__muted">
1198
+ {execution ? formatTimestamp(execution.finishedAt) : 'Never run'}
1199
+ </span>
1200
+ <button
1201
+ className="proteum-profiler__pill"
1202
+ onClick={() => void profilerRuntime.runCommand(command.path)}
1203
+ type="button"
1204
+ >
1205
+ Run now
1206
+ </button>
1207
+ </div>
1208
+ </div>
1209
+
1210
+ <div className="proteum-profiler__tags">
1211
+ <span className="proteum-profiler__tag">{command.className}</span>
1212
+ <span className="proteum-profiler__tag">{command.methodName}</span>
1213
+ <span className="proteum-profiler__tag">{command.scope}</span>
1214
+ {execution ? <span className="proteum-profiler__tag">{execution.status}</span> : null}
1215
+ {execution ? (
1216
+ <span className="proteum-profiler__tag">{formatDuration(execution.durationMs)}</span>
1217
+ ) : null}
1218
+ {execution?.errorMessage ? (
1219
+ <span className="proteum-profiler__tag">{truncate(execution.errorMessage, 72)}</span>
1220
+ ) : null}
1221
+ </div>
1222
+
1223
+ <div className="proteum-profiler__mono proteum-profiler__muted">
1224
+ source {command.filepath}:{command.sourceLocation.line}:{command.sourceLocation.column}
1225
+ {commandsState.lastLoadedAt
1226
+ ? ` | refreshed ${formatTimestamp(commandsState.lastLoadedAt)}`
1227
+ : ''}
1228
+ </div>
1229
+
1230
+ {execution ? (
1231
+ <div className="proteum-profiler__section">
1232
+ <div className="proteum-profiler__sectionTitle">Last result</div>
1233
+ <pre className="proteum-profiler__mono proteum-profiler__pre">
1234
+ {execution.result?.json !== undefined
1235
+ ? formatStructuredValue(execution.result.json)
1236
+ : execution.result
1237
+ ? formatStructuredValue(execution.result.summary)
1238
+ : execution.errorMessage || 'undefined'}
1239
+ </pre>
1240
+ </div>
1241
+ ) : null}
1242
+ </div>
1243
+ );
1244
+ })}
1245
+ </div>
1246
+ )}
1247
+ </div>
1248
+ );
1249
+ }
1250
+
1251
+ if (panel === 'cron') {
1252
+ const cron = state.cron;
1253
+
1254
+ return (
1255
+ <div className="proteum-profiler__section">
1256
+ <div className="proteum-profiler__sectionHeader">
1257
+ <div className="proteum-profiler__sectionTitle">Registered tasks</div>
1258
+ <div className="proteum-profiler__actions">
1259
+ <button className="proteum-profiler__pill" onClick={() => void profilerRuntime.refreshCronTasks()} type="button">
1260
+ Refresh
1261
+ </button>
1262
+ </div>
1263
+ </div>
1264
+
1265
+ <div className="proteum-profiler__row">
1266
+ <div className="proteum-profiler__rowHeader">
1267
+ <strong>Dev mode</strong>
1268
+ <span className="proteum-profiler__mono proteum-profiler__muted">
1269
+ {cron.tasks.length} task{cron.tasks.length === 1 ? '' : 's'}
1270
+ </span>
1271
+ </div>
1272
+ <div className="proteum-profiler__mono">
1273
+ Automatic execution is disabled in dev. Registered cron tasks stay visible here and only run when
1274
+ triggered manually.
1275
+ </div>
1276
+ </div>
1277
+
1278
+ {cron.errorMessage ? (
1279
+ <div className="proteum-profiler__row">
1280
+ <div className="proteum-profiler__rowHeader">
1281
+ <strong>Last cron panel error</strong>
1282
+ </div>
1283
+ <div className="proteum-profiler__mono">{cron.errorMessage}</div>
1284
+ </div>
1285
+ ) : null}
1286
+
1287
+ {cron.status === 'loading' && cron.tasks.length === 0 ? (
1288
+ <div className="proteum-profiler__empty">Loading cron tasks...</div>
1289
+ ) : cron.tasks.length === 0 ? (
1290
+ <div className="proteum-profiler__empty">No cron tasks are registered for this app.</div>
1291
+ ) : (
1292
+ <div className="proteum-profiler__list">
1293
+ {cron.tasks.map((task) => (
1294
+ <div className="proteum-profiler__row" key={task.name}>
1295
+ <div className="proteum-profiler__rowHeader">
1296
+ <strong>{task.name}</strong>
1297
+ <div className="proteum-profiler__actions">
1298
+ <span className="proteum-profiler__mono proteum-profiler__muted">
1299
+ {task.running
1300
+ ? 'Running...'
1301
+ : task.lastRunFinishedAt
1302
+ ? formatTimestamp(task.lastRunFinishedAt)
1303
+ : 'Never run'}
1304
+ </span>
1305
+ <button
1306
+ className="proteum-profiler__pill"
1307
+ disabled={task.running}
1308
+ onClick={() => void profilerRuntime.runCronTask(task.name)}
1309
+ type="button"
1310
+ >
1311
+ {task.running ? 'Running...' : 'Run now'}
1312
+ </button>
1313
+ </div>
1314
+ </div>
1315
+
1316
+ <div className="proteum-profiler__tags">
1317
+ <span className="proteum-profiler__tag">schedule:{truncate(formatCronFrequency(task), 64)}</span>
1318
+ <span className="proteum-profiler__tag">
1319
+ next:{task.nextInvocation ? formatTimestamp(task.nextInvocation) : 'none'}
1320
+ </span>
1321
+ <span className="proteum-profiler__tag">autoexec:{task.autoexec ? 'yes' : 'no'}</span>
1322
+ <span className="proteum-profiler__tag">
1323
+ automatic:{task.automaticExecution ? 'enabled' : 'disabled in dev'}
1324
+ </span>
1325
+ <span className="proteum-profiler__tag">runs:{task.runCount}</span>
1326
+ {task.lastTrigger ? <span className="proteum-profiler__tag">trigger:{task.lastTrigger}</span> : null}
1327
+ {task.lastRunStatus ? <span className="proteum-profiler__tag">{task.lastRunStatus}</span> : null}
1328
+ {task.lastRunDurationMs !== undefined ? (
1329
+ <span className="proteum-profiler__tag">{formatDuration(task.lastRunDurationMs)}</span>
1330
+ ) : null}
1331
+ {task.lastErrorMessage ? (
1332
+ <span className="proteum-profiler__tag">{truncate(task.lastErrorMessage, 72)}</span>
1333
+ ) : null}
1334
+ </div>
1335
+
1336
+ <div className="proteum-profiler__mono proteum-profiler__muted">
1337
+ registered {formatTimestamp(task.registeredAt)}
1338
+ {cron.lastLoadedAt ? ` | refreshed ${formatTimestamp(cron.lastLoadedAt)}` : ''}
1339
+ </div>
1340
+ </div>
1341
+ ))}
1342
+ </div>
1343
+ )}
1344
+ </div>
1345
+ );
1346
+ }
1347
+
1348
+ const errorRows = [
1349
+ ...session.steps
1350
+ .filter((step) => step.status === 'error')
1351
+ .map((step) => ({ key: step.id, title: step.label, value: step.errorMessage || 'Step failed' })),
1352
+ ...session.traces
1353
+ .filter((trace) => trace.status === 'error')
1354
+ .map((trace) => ({ key: trace.id, title: trace.label, value: trace.errorMessage || 'Request failed' })),
1355
+ ...findTraceEvents(primaryTrace, ['error']).map((event) => ({
1356
+ key: `${event.index}:error`,
1357
+ title: event.type,
1358
+ value: Object.entries(event.details)
1359
+ .map(([key, value]) => `${key}=${renderSummaryValue(value)}`)
1360
+ .join(' '),
1361
+ })),
1362
+ ];
1363
+
1364
+ return <SimpleSection empty="No errors captured." rows={errorRows} title="Errors" />;
1365
+ };
1366
+
1367
+ export default function DevProfiler() {
1368
+ const [state, setState] = React.useState(() => profilerRuntime.getState());
1369
+
1370
+ React.useEffect(() => profilerRuntime.subscribe(() => setState(profilerRuntime.getState())), []);
1371
+ React.useEffect(() => {
1372
+ void profilerRuntime.refreshCommands();
1373
+ void profilerRuntime.refreshCronTasks();
1374
+ }, []);
1375
+
1376
+ React.useEffect(() => {
1377
+ const onKeyDown = (event: KeyboardEvent) => {
1378
+ if (event.key === 'Escape' && profilerRuntime.getState().uiState === 'expanded') {
1379
+ profilerRuntime.setUiState('minimized');
1380
+ }
1381
+ };
1382
+
1383
+ window.addEventListener('keydown', onKeyDown);
1384
+ return () => window.removeEventListener('keydown', onKeyDown);
1385
+ }, []);
1386
+
1387
+ if (!window.dev) return null;
1388
+
1389
+ const session = getSelectedSession(state.sessions, state.selectedSessionId, state.currentSessionId);
1390
+ if (!session) return null;
1391
+
1392
+ const summary = getSummary(session);
1393
+ const tone = summary.errorCount > 0 ? 'error' : (summary.totalMs || 0) > 500 ? 'warn' : 'ok';
1394
+ const primaryTrace = summary.primaryTrace?.trace;
1395
+ const minimizedLabel =
1396
+ session.kind === 'client-navigation'
1397
+ ? session.label
1398
+ : primaryTrace
1399
+ ? `${primaryTrace.statusCode || 'pending'} ${primaryTrace.method} ${primaryTrace.path}`
1400
+ : session.label;
1401
+ const recentSessions = state.sessions.slice(-6).reverse();
1402
+
1403
+ return (
1404
+ <div className="proteum-profiler">
1405
+ <style dangerouslySetInnerHTML={{ __html: profilerStyles }} />
1406
+
1407
+ {state.uiState === 'pinned-handle' ? (
1408
+ <button
1409
+ className="proteum-profiler__handle"
1410
+ onClick={() => profilerRuntime.setUiState('minimized')}
1411
+ type="button"
1412
+ >
1413
+ Proteum Profiler
1414
+ </button>
1415
+ ) : (
1416
+ <>
1417
+ {state.uiState === 'expanded' ? (
1418
+ <div className="proteum-profiler__panel">
1419
+ <div className="proteum-profiler__panelHeader">
1420
+ <select
1421
+ aria-label="Profiler path selector"
1422
+ className="proteum-profiler__select"
1423
+ onChange={(event) => profilerRuntime.selectSession(event.currentTarget.value)}
1424
+ value={session.id}
1425
+ >
1426
+ {recentSessions.map((recentSession) => (
1427
+ <option key={recentSession.id} value={recentSession.id}>
1428
+ {getSessionSelectorLabel(recentSession)}
1429
+ </option>
1430
+ ))}
1431
+ </select>
1432
+
1433
+ <div className="proteum-profiler__panelTabs">
1434
+ {(Object.keys(panelLabels) as TProfilerPanel[]).map((panel) => (
1435
+ <button
1436
+ className={`proteum-profiler__pill ${
1437
+ state.activePanel === panel ? 'proteum-profiler__pill--active' : ''
1438
+ }`}
1439
+ key={panel}
1440
+ onClick={() => profilerRuntime.openPanel(panel)}
1441
+ type="button"
1442
+ >
1443
+ {panelLabels[panel]}
1444
+ </button>
1445
+ ))}
1446
+ </div>
1447
+
1448
+ <div className="proteum-profiler__actions">
1449
+ <button className="proteum-profiler__pill" onClick={() => profilerRuntime.setUiState('minimized')} type="button">
1450
+ Collapse
1451
+ </button>
1452
+ <button className="proteum-profiler__pill" onClick={() => profilerRuntime.setUiState('pinned-handle')} type="button">
1453
+ Hide
1454
+ </button>
1455
+ </div>
1456
+ </div>
1457
+
1458
+ <div className="proteum-profiler__panelBody">{renderPanel(state.activePanel, session, summary, state)}</div>
1459
+ </div>
1460
+ ) : null}
1461
+
1462
+ <div className="proteum-profiler__bar">
1463
+ <button
1464
+ className="proteum-profiler__token proteum-profiler__token--brand"
1465
+ onClick={() => profilerRuntime.openPanel('summary')}
1466
+ type="button"
1467
+ >
1468
+ Proteum
1469
+ </button>
1470
+ <StatusToken label={truncate(minimizedLabel, 56)} onClick={() => profilerRuntime.openPanel('summary')} tone={tone} />
1471
+ <StatusToken
1472
+ label={formatDuration(summary.totalMs)}
1473
+ onClick={() => profilerRuntime.openPanel('summary')}
1474
+ tone={tone}
1475
+ />
1476
+ <StatusToken
1477
+ label={truncate(summary.routeLabel, 28)}
1478
+ onClick={() => profilerRuntime.openPanel('routing')}
1479
+ tone="ok"
1480
+ />
1481
+ <StatusToken
1482
+ label={
1483
+ summary.ssrPayloadBytes !== undefined
1484
+ ? `${formatDuration(summary.renderMs)} ${formatBytes(summary.ssrPayloadBytes)}`
1485
+ : formatDuration(summary.renderMs)
1486
+ }
1487
+ onClick={() => profilerRuntime.openPanel('ssr')}
1488
+ tone="ok"
1489
+ />
1490
+ <StatusToken
1491
+ label={`API ${summary.apiSyncCount} / ${summary.apiAsyncCount}`}
1492
+ onClick={() => profilerRuntime.openPanel('api')}
1493
+ tone={summary.apiAsyncCount > 0 || summary.apiSyncCount > 0 ? 'ok' : 'warn'}
1494
+ />
1495
+ {summary.errorCount > 0 ? (
1496
+ <StatusToken
1497
+ label={`${summary.errorCount} error${summary.errorCount === 1 ? '' : 's'}`}
1498
+ onClick={() => profilerRuntime.openPanel('errors')}
1499
+ tone="error"
1500
+ />
1501
+ ) : null}
1502
+ <div className="proteum-profiler__spacer" />
1503
+ <button className="proteum-profiler__token" onClick={() => profilerRuntime.setUiState('pinned-handle')} type="button">
1504
+ Hide
1505
+ </button>
1506
+ </div>
1507
+ </>
1508
+ )}
1509
+ </div>
1510
+ );
1511
+ }