Skip to content

Commit 7bf7422

Browse files
feat: add TTFB timing breakdown to execution detail view (#25)
* feat: add TTFB timing breakdown to execution detail view Measure time-to-first-byte (TTFB) in the engine and display it in the TUI execution detail panel alongside total request duration. - Add ttfb field to fetchFinished engine event - Capture TTFB from events in server service layer - Update ExecutionTimingSchema with optional ttfb field - Display Timing section in TUI with TTFB and Total * chore: add changeset
1 parent 6a3f8f4 commit 7bf7422

File tree

7 files changed

+41
-6
lines changed

7 files changed

+41
-6
lines changed

.changeset/lovely-ravens-trade.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@t-req/core": patch
3+
"@t-req/app": patch
4+
---
5+
6+
Add TTFB timing measurement to execution details

packages/app/src/server/schemas.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,8 @@ export const ExecutionSourceSchema = z.object({
413413
export const ExecutionTimingSchema = z.object({
414414
startTime: z.number(),
415415
endTime: z.number().optional(),
416-
durationMs: z.number().optional()
416+
durationMs: z.number().optional(),
417+
ttfb: z.number().optional()
417418
});
418419

419420
export const ExecutionStatusSchema = z.enum(['pending', 'running', 'success', 'failed']);

packages/app/src/server/service.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ export type StoredExecution = {
158158
startTime: number;
159159
endTime?: number;
160160
durationMs?: number;
161+
ttfb?: number;
161162
};
162163
response?: {
163164
status: number;
@@ -853,6 +854,16 @@ export function createService(config: ServiceConfig) {
853854
}
854855
}
855856

857+
// Capture TTFB from fetchFinished
858+
if (event.type === 'fetchFinished' && typeof event.ttfb === 'number') {
859+
if (flow && reqExecId) {
860+
const exec = flow.executions.get(reqExecId);
861+
if (exec) {
862+
exec.timing.ttfb = event.ttfb;
863+
}
864+
}
865+
}
866+
856867
// Capture errors
857868
if (event.type === 'error') {
858869
const stage = String(event.stage ?? 'unknown');

packages/app/src/tui/components/execution-detail.tsx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,22 @@ export function ExecutionDetailView(props: ExecutionDetailProps) {
113113
<Show when={execution.reqLabel}>
114114
<text fg={rgba(theme.textMuted)}>{execution.reqLabel}</text>
115115
</Show>
116-
{/* Line 3: Duration */}
117-
<Show when={execution.timing.durationMs !== undefined}>
118-
<text fg={rgba(theme.textMuted)}>{formatDuration(execution.timing.durationMs)}</text>
116+
<Show when={execution.timing.ttfb !== undefined || execution.timing.durationMs !== undefined}>
117+
<box flexDirection="column">
118+
<text fg={rgba(theme.primary)} attributes={1}>Timing</text>
119+
<Show when={execution.timing.ttfb !== undefined}>
120+
<box flexDirection="row">
121+
<text fg={rgba(theme.textMuted)}> TTFB: </text>
122+
<text fg={rgba(theme.text)}>{formatDuration(execution.timing.ttfb)}</text>
123+
</box>
124+
</Show>
125+
<Show when={execution.timing.durationMs !== undefined}>
126+
<box flexDirection="row">
127+
<text fg={rgba(theme.textMuted)}> Total: </text>
128+
<text fg={rgba(theme.text)}>{formatDuration(execution.timing.durationMs)}</text>
129+
</box>
130+
</Show>
131+
</box>
119132
</Show>
120133
</box>
121134

packages/app/src/tui/sdk.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ export interface ExecutionDetail {
8585
startTime: number;
8686
endTime?: number;
8787
durationMs?: number;
88+
ttfb?: number;
8889
};
8990
response?: {
9091
status: number;

packages/core/src/engine/engine.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -413,13 +413,16 @@ export function createEngine(config: EngineConfig = {}): Engine {
413413
.ifDefined('proxy', options.proxy)
414414
.build();
415415

416+
const fetchStart = performance.now();
416417
response = await executeWithTransport(requestWithCookies, execOptions, transport);
418+
const ttfb = performance.now() - fetchStart;
417419

418420
emitEvent({
419421
type: 'fetchFinished',
420422
method: requestWithCookies.method,
421423
url: urlForCookies,
422-
status: response.status
424+
status: response.status,
425+
ttfb
423426
});
424427

425428
// Update cookies

packages/core/src/runtime/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export type EngineEvent =
5252
| { type: 'compileStarted' }
5353
| { type: 'compileFinished' }
5454
| { type: 'fetchStarted'; method: string; url: string }
55-
| { type: 'fetchFinished'; method: string; url: string; status: number }
55+
| { type: 'fetchFinished'; method: string; url: string; status: number; ttfb?: number }
5656
| { type: 'error'; stage: string; message: string };
5757

5858
export type EventSink = (event: EngineEvent) => void;

0 commit comments

Comments
 (0)