Skip to content

Commit 93494cf

Browse files
V48 Gate 3 (implementation-only): move log-row pills inline to the right of the chevron + title
In all three pipeline-execution-log layouts (compact/desktop/mobile) the call-chain pill row no longer sits ABOVE the title text: each row is now ONE flex line — chevron, title, then the ExecutionContextPillRow flowing right (justify-end), wrapping onto following lines only when out of width, then the timestamp. This halves each row's vertical footprint. The corner icon, expand behavior, failsafe badges, tooltips, and the /deposit header tracker are unchanged. - The shared PillRow element becomes pillRowProps so each layout sizes and positions the row (flex-1 justify-end on compact/mobile; the desktop meta cluster keeps its right-aligned wrap). - Titles cap at max-w-[45%] when pills are present so the pills always get line space; title-only rows keep flex-1. - The expanded selector-group explainers pass the row's {agent, step} context so step/failsafe/generation tooltip copy names the concrete agent's output schema. - pipelineExecutionLogTelemetryUx pins the new laws: prompt/return- concrete specific copy, specific-above-generic tooltip ordering, and the pills sharing ONE flex row with (and following) the title. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 7c95393 commit 93494cf

2 files changed

Lines changed: 230 additions & 53 deletions

File tree

uapi/components/base/bitcode/execution/pipeline-execution-log.tsx

Lines changed: 51 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -970,21 +970,22 @@ function renderLogLine(
970970
logLine.type === 'tool-use' || logLine.tool ? 'tool' : 'llm',
971971
);
972972
// ONE inline, wrapping row of all call-chain pills (phase, agent, step,
973-
// failsafe, generation, tool) — each a rich-tooltip trigger.
974-
const PillRow = (
975-
<ExecutionContextPillRow
976-
phase={logLine.phase}
977-
agent={logLine.agent}
978-
step={logLine.step}
979-
failsafe={logLine.failsafe}
980-
generation={logLine.generation}
981-
tool={toolLabel}
982-
stitchIteration={logLine.stitchIteration}
983-
chunkIndex={logLine.chunkIndex}
984-
chunkSum={logLine.chunkSum}
985-
mode={rowMode}
986-
/>
987-
);
973+
// failsafe, generation, tool) — each a rich-tooltip trigger. Rendered per
974+
// layout (with a layout-specific className) to the RIGHT of the chevron +
975+
// title on the SAME line, wrapping onto following lines only when out of
976+
// width.
977+
const pillRowProps = {
978+
phase: logLine.phase,
979+
agent: logLine.agent,
980+
step: logLine.step,
981+
failsafe: logLine.failsafe,
982+
generation: logLine.generation,
983+
tool: toolLabel,
984+
stitchIteration: logLine.stitchIteration,
985+
chunkIndex: logLine.chunkIndex,
986+
chunkSum: logLine.chunkSum,
987+
mode: rowMode,
988+
};
988989

989990
const formatMeta = (m?: string) => {
990991
const v = String(m || '');
@@ -1010,7 +1011,7 @@ function renderLogLine(
10101011
if (compact) {
10111012
const RowContent = (
10121013
<div
1013-
className={`relative flex flex-col gap-1.5 w-full rounded-lg pl-7 pr-3 py-2 min-h-[46px] mb-4 last:mb-0 select-none text-[0.78rem] font-medium ${style.text} backdrop-blur-md bg-white/5 dark:bg-white/2 hover:bg-white/10 dark:hover:bg-white/10 transition-colors duration-200 border-l-2 ${style.border}`}
1014+
className={`relative flex items-center gap-1 w-full rounded-lg pl-7 pr-3 py-2 min-h-[34px] mb-4 last:mb-0 select-none text-[0.78rem] font-medium ${style.text} backdrop-blur-md bg-white/5 dark:bg-white/2 hover:bg-white/10 dark:hover:bg-white/10 transition-colors duration-200 border-l-2 ${style.border}`}
10141015
data-log-index={index}
10151016
onClick={() => toggleLine(lineId)}
10161017
draggable
@@ -1042,30 +1043,28 @@ function renderLogLine(
10421043
</span>
10431044
</TelemetryExplainerTrigger>
10441045

1045-
{/* ONE inline pill row: phase, agent, step, failsafe, generation
1046-
(+ tool), wrapping when narrow to use the horizontal space. */}
1047-
{hasPills && PillRow}
1048-
1049-
{/* Chevron + title + timestamp line */}
1050-
<div className="flex items-center gap-1 w-full min-w-0">
1051-
<ChevronRightIcon
1052-
className={`w-4 h-4 flex-shrink-0 text-current opacity-60 transition-transform duration-300 ${
1053-
expandedLines[lineId] ? 'rotate-90' : ''
1054-
}`}
1055-
/>
1056-
<span
1057-
title={logLine.text}
1058-
className="truncate flex-1 min-w-0 text-[0.82rem] leading-none m-0"
1059-
>
1060-
{logLine.text}
1061-
</span>
1046+
{/* ONE line: chevron, title, then the inline pill row (phase, agent,
1047+
step, failsafe, generation, + tool) flowing right — wrapping onto
1048+
following lines only when out of width — then the timestamp. */}
1049+
<ChevronRightIcon
1050+
className={`w-4 h-4 flex-shrink-0 text-current opacity-60 transition-transform duration-300 ${
1051+
expandedLines[lineId] ? 'rotate-90' : ''
1052+
}`}
1053+
/>
1054+
<span
1055+
title={logLine.text}
1056+
className={`truncate min-w-0 text-[0.82rem] leading-none m-0 ${hasPills ? 'max-w-[45%]' : 'flex-1'}`}
1057+
>
1058+
{logLine.text}
1059+
</span>
10621060

1063-
{logLine.timestamp && (
1064-
<span className="text-[10px] text-gray-500 flex-shrink-0 select-none ml-1">
1065-
{formatTime(logLine.timestamp)}
1066-
</span>
1067-
)}
1068-
</div>
1061+
{hasPills && <ExecutionContextPillRow {...pillRowProps} className="flex-1 justify-end" />}
1062+
1063+
{logLine.timestamp && (
1064+
<span className="text-[10px] text-gray-500 flex-shrink-0 select-none ml-1">
1065+
{formatTime(logLine.timestamp)}
1066+
</span>
1067+
)}
10691068
</div>
10701069
);
10711070

@@ -1192,7 +1191,8 @@ function renderLogLine(
11921191
{logLine.text}
11931192
</span>
11941193

1195-
{/* Meta cluster + timestamp: one inline, wrapping row of all pills */}
1194+
{/* Meta cluster + timestamp: the pill row flows right of the title
1195+
on the SAME line, wrapping only when out of width. */}
11961196
<div className="hidden laptop:flex items-center flex-wrap justify-end gap-1 laptop:max-w-[50%]">
11971197
{/* Timestamp */}
11981198
{logLine.timestamp && (
@@ -1201,12 +1201,12 @@ function renderLogLine(
12011201
</span>
12021202
)}
12031203

1204-
{hasPills && PillRow}
1204+
{hasPills && <ExecutionContextPillRow {...pillRowProps} className="justify-end" />}
12051205
</div>
12061206
</div>
12071207

12081208
{/* Mobile / narrow layout */}
1209-
<div className="laptop:hidden relative w-full pl-12 pr-3 py-2 space-y-1">
1209+
<div className="laptop:hidden relative w-full pl-12 pr-3 py-2">
12101210
{/* Floating Type Icon (circular bubble) — rich-tooltip trigger */}
12111211
<TelemetryExplainerTrigger
12121212
explainer={rowIconExplainer}
@@ -1224,11 +1224,9 @@ function renderLogLine(
12241224
</span>
12251225
</TelemetryExplainerTrigger>
12261226

1227-
{/* ONE inline pill row: phase, agent, step, failsafe, generation (+ tool) */}
1228-
{hasPills && PillRow}
1229-
1230-
{/* Chevron, title, timestamp row */}
1231-
<div className="flex items-center gap-1 w-full">
1227+
{/* ONE line: chevron, title, then the inline pill row flowing right
1228+
(wrapping onto following lines only when out of width), timestamp. */}
1229+
<div className="flex items-center gap-1 w-full min-w-0">
12321230
<ChevronRightIcon
12331231
className={`laptop:hidden w-3 h-3 flex-shrink-0 text-current opacity-60 transition-transform duration-300 ${
12341232
expandedLines[lineId] ? 'rotate-90' : ''
@@ -1237,11 +1235,13 @@ function renderLogLine(
12371235

12381236
<span
12391237
title={logLine.text}
1240-
className="text-xs font-medium truncate flex-1 min-w-0"
1238+
className={`text-xs font-medium truncate min-w-0 ${hasPills ? 'max-w-[45%]' : 'flex-1'}`}
12411239
>
12421240
{logLine.text}
12431241
</span>
12441242

1243+
{hasPills && <ExecutionContextPillRow {...pillRowProps} className="flex-1 justify-end" />}
1244+
12451245
{logLine.timestamp && (
12461246
<span className="text-[11px] text-gray-500 flex-shrink-0 select-none">
12471247
{formatTime(logLine.timestamp)}
@@ -1502,7 +1502,7 @@ function renderLogLine(
15021502
<div className="text-xs font-medium text-emerald-400 mb-1">Steps:</div>
15031503
<div className="flex flex-wrap gap-1">
15041504
{['Plan','Try','Refine','Retry'].map(s => (
1505-
<TelemetryExplainerTrigger key={s} explainer={getTelemetryPillExplainer('step', s, rowMode)}>
1505+
<TelemetryExplainerTrigger key={s} explainer={getTelemetryPillExplainer('step', s, rowMode, { agent: logLine.agent, step: logLine.step })}>
15061506
<PathPill type="step" label={s} className={s===normalizeStepName(logLine.step) ? '' : 'opacity-25'} />
15071507
</TelemetryExplainerTrigger>
15081508
))}
@@ -1518,7 +1518,7 @@ function renderLogLine(
15181518
['Chunk Then Sum', 'chunk_then_sum'],
15191519
['Stitch Until Complete', 'stitch_until_complete'],
15201520
].map(([m, rawFailsafe]) => (
1521-
<TelemetryExplainerTrigger key={m} explainer={getTelemetryPillExplainer('failsafe', rawFailsafe, rowMode)}>
1521+
<TelemetryExplainerTrigger key={m} explainer={getTelemetryPillExplainer('failsafe', rawFailsafe, rowMode, { agent: logLine.agent, step: logLine.step })}>
15221522
<PathPill type="failsafe" label={m} className={m===formatMeta(logLine.failsafe) ? '' : 'opacity-25'} />
15231523
</TelemetryExplainerTrigger>
15241524
))}
@@ -1530,7 +1530,7 @@ function renderLogLine(
15301530
<div className="text-xs font-medium text-emerald-400 mb-1">Generations:</div>
15311531
<div className="flex flex-wrap gap-1">
15321532
{['Reason','Judge','Structured Output'].map(sub => (
1533-
<TelemetryExplainerTrigger key={sub} explainer={getTelemetryPillExplainer('generation', sub, rowMode)}>
1533+
<TelemetryExplainerTrigger key={sub} explainer={getTelemetryPillExplainer('generation', sub, rowMode, { agent: logLine.agent, step: logLine.step })}>
15341534
<PathPill type="generation" label={sub} className={sub===formatMeta(logLine.generation) ? '' : 'opacity-25'} />
15351535
</TelemetryExplainerTrigger>
15361536
))}

uapi/tests/pipelineExecutionLogTelemetryUx.test.tsx

Lines changed: 179 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ jest.mock('@/components/base/bitcode/execution/FileDiffViewer', () => ({
88

99
import React from 'react';
1010
import '@testing-library/jest-dom';
11-
import { render, screen } from '@testing-library/react';
11+
import { fireEvent, render, screen } from '@testing-library/react';
1212

1313
import {
1414
FAILSAFE_SENTENCE_NAMES,
@@ -19,10 +19,25 @@ import {
1919
trimPipelineAgentName,
2020
} from '@/components/base/bitcode/execution/execution-telemetry-format';
2121
import { formatRunClock } from '@/components/base/bitcode/execution/RunClock';
22-
import { buildProcessingStallLabel } from '@/components/base/bitcode/execution/pipeline-execution-log';
22+
import {
23+
PipelineExecutionLog,
24+
buildProcessingStallLabel,
25+
} from '@/components/base/bitcode/execution/pipeline-execution-log';
2326
import { ExecutionContextPillRow } from '@/components/base/bitcode/execution/ExecutionContextPillRow';
27+
import { TelemetryExplainerTrigger } from '@/components/base/bitcode/execution/TelemetryExplainerTrigger';
28+
import {
29+
getTelemetryPillExplainer,
30+
getTelemetryRowIconExplainer,
31+
} from '@/components/base/bitcode/execution/telemetry-pill-explainers';
2432
import { buildTerminalRunActivityFromEvents } from '@/app/terminal/terminal-run-activity';
2533

34+
beforeAll(() => {
35+
(global as any).ResizeObserver = class {
36+
observe() {}
37+
disconnect() {}
38+
};
39+
});
40+
2641
// ---------------------------------------------------------------------------
2742
// Item 3 — trim the pipeline-name prefix from AGENT names (display only)
2843
// ---------------------------------------------------------------------------
@@ -254,3 +269,165 @@ describe('ExecutionContextPillRow', () => {
254269
expect(container.firstElementChild).toBeNull();
255270
});
256271
});
272+
273+
// ---------------------------------------------------------------------------
274+
// Tooltip content — the SPECIFIC section states what the exact element is
275+
// PROMPTED to do and what it RETURNS (output-schema shapes), summarized from
276+
// the real agent/step/failsafe sources; the generic type copy is separate.
277+
// ---------------------------------------------------------------------------
278+
describe('getTelemetryPillExplainer — prompt/return-concrete specific copy', () => {
279+
it('failsafe PCC names its selection input/output shapes', () => {
280+
const explainer = getTelemetryPillExplainer('failsafe', 'prepare_concise_context');
281+
expect(explainer.specific).toContain('pipeline_execution_keys');
282+
expect(explainer.specific).toContain('{selectedKeys}');
283+
expect(explainer.generic).toContain('Failsafes are the guards');
284+
});
285+
286+
it('failsafe handle-large-inputs states the budget measurement + one-vs-chunked generations', () => {
287+
const explainer = getTelemetryPillExplainer('failsafe', 'chunk_then_sum');
288+
expect(explainer.specific).toContain('request budget');
289+
expect(explainer.specific).toContain('ONE task generation');
290+
});
291+
292+
it("failsafe handle-large-outputs references the surrounding agent's output schema when context is passed", () => {
293+
const explainer = getTelemetryPillExplainer('failsafe', 'stitch_until_complete', 'deposit', {
294+
agent: 'DepositValidationAgent',
295+
step: 'try',
296+
});
297+
expect(explainer.specific).toContain("the Validation Agent's output schema");
298+
expect(explainer.specific).toContain('validation error');
299+
});
300+
301+
it('deposit agent copy summarizes the prompt purpose + the zod return shape', () => {
302+
const search = getTelemetryPillExplainer('agent', 'DepositDepositorySearchAgent', 'deposit');
303+
expect(search.specific).toContain('{guidance}');
304+
expect(search.specific).toContain('likelyReadTopics');
305+
306+
const comprehension = getTelemetryPillExplainer('agent', 'DepositInputComprehensionAgent', 'deposit');
307+
expect(comprehension.specific).toContain('{comprehension}');
308+
expect(comprehension.specific).toContain('obfuscatedPaths');
309+
310+
const synthesis = getTelemetryPillExplainer('agent', 'DepositAssetPackSynthesisAgent', 'deposit');
311+
expect(synthesis.specific).toContain('{options}');
312+
expect(synthesis.specific).toContain('patchSummary');
313+
});
314+
315+
it("PTRR step copy references the agent's output schema, sharpened by row context", () => {
316+
const withContext = getTelemetryPillExplainer('step', 'try', 'deposit', {
317+
agent: 'DepositDepositorySearchAgent',
318+
});
319+
expect(withContext.specific).toContain("the Depository Search Agent's output schema");
320+
321+
const withoutContext = getTelemetryPillExplainer('step', 'try');
322+
expect(withoutContext.specific).toContain("the agent's output schema");
323+
});
324+
325+
it('generation copy names the Thinkings return shapes', () => {
326+
expect(getTelemetryPillExplainer('generation', 'reason').specific).toContain(
327+
'{analysis, steps, conclusion, confidence}',
328+
);
329+
expect(getTelemetryPillExplainer('generation', 'judge').specific).toContain(
330+
'{quality, issues, suggestions, approved}',
331+
);
332+
expect(getTelemetryPillExplainer('generation', 'structured_output').specific).toContain(
333+
'zod output schema',
334+
);
335+
});
336+
337+
it('deposit phase copy states the concrete per-phase SDIVF jobs', () => {
338+
const discovery = getTelemetryPillExplainer('phase', 'discovery', 'deposit');
339+
expect(discovery.specific).toContain("The Depositing Pipeline's");
340+
expect(discovery.specific).toContain('codebase comprehension');
341+
expect(discovery.specific).toContain('inherent regurgitation');
342+
343+
const finish = getTelemetryPillExplainer('phase', 'finish', 'deposit');
344+
expect(finish.specific).toContain('depositor review');
345+
});
346+
347+
it('row-icon explainer keeps the specific what-this-row-is copy on the specific field', () => {
348+
expect(getTelemetryRowIconExplainer('llm').specific).toContain('This row is one LLM call');
349+
expect(getTelemetryRowIconExplainer('tool').specific).toContain('This row is one Tool use');
350+
});
351+
});
352+
353+
// ---------------------------------------------------------------------------
354+
// Tooltip ordering — the SPECIFIC section renders on TOP, the generic type
355+
// copy BELOW it.
356+
// ---------------------------------------------------------------------------
357+
describe('TelemetryExplainerTrigger — specific section above generic', () => {
358+
it('renders specific before generic in the tooltip', () => {
359+
render(
360+
<TelemetryExplainerTrigger
361+
explainer={{
362+
kicker: 'Failsafe',
363+
title: 'Prepare Concise Context',
364+
specific: 'SPECIFIC-SECTION-COPY',
365+
generic: 'GENERIC-SECTION-COPY',
366+
}}
367+
>
368+
<span>trigger</span>
369+
</TelemetryExplainerTrigger>,
370+
);
371+
372+
fireEvent.mouseEnter(screen.getByText('trigger').parentElement as HTMLElement);
373+
const tooltip = screen.getByRole('tooltip');
374+
const text = tooltip.textContent || '';
375+
expect(text.indexOf('SPECIFIC-SECTION-COPY')).toBeGreaterThanOrEqual(0);
376+
expect(text.indexOf('SPECIFIC-SECTION-COPY')).toBeLessThan(text.indexOf('GENERIC-SECTION-COPY'));
377+
});
378+
});
379+
380+
// ---------------------------------------------------------------------------
381+
// Pill placement — the pills render to the RIGHT of the chevron + title on
382+
// the SAME line (one flex row), not on a row above the title.
383+
// ---------------------------------------------------------------------------
384+
describe('PipelineExecutionLog — pills inline with the title line', () => {
385+
const line = 'LLM call observed';
386+
const outputDetails = {
387+
[line]: {
388+
type: 'generation',
389+
status: {
390+
executionState: {
391+
phase: 'discovery',
392+
agent: 'DepositDepositorySearchAgent',
393+
step: 'try',
394+
failsafe: 'prepare_concise_context',
395+
generation: 'reason',
396+
pipelineMode: 'deposit',
397+
},
398+
timestamp: '2026-07-01T00:00:05.000Z',
399+
},
400+
},
401+
};
402+
403+
it('compact layout: title and pill row share ONE flex row, pills after the title', () => {
404+
render(
405+
<PipelineExecutionLog
406+
output={`${line}\n`}
407+
isProcessing={false}
408+
error={null}
409+
outputDetails={outputDetails}
410+
onRetry={() => {}}
411+
onDismissError={() => {}}
412+
userHasScrolled={false}
413+
setUserHasScrolled={() => {}}
414+
compact
415+
/>,
416+
);
417+
418+
const title = screen.getByText(line);
419+
const pillRow = screen.getByText('DISCOVERY').closest('.flex-wrap') as HTMLElement;
420+
expect(pillRow).not.toBeNull();
421+
422+
// Same parent row — NOT a separate pill line above the title.
423+
expect(pillRow.parentElement).toBe(title.parentElement);
424+
const row = title.parentElement as HTMLElement;
425+
expect(row.className).toContain('items-center');
426+
expect(row.className).not.toContain('flex-col');
427+
428+
// Pills come AFTER the title in document order (to its right).
429+
expect(
430+
title.compareDocumentPosition(pillRow) & Node.DOCUMENT_POSITION_FOLLOWING,
431+
).toBeTruthy();
432+
});
433+
});

0 commit comments

Comments
 (0)