Skip to content

feat(logs): trace span tree rewrite with resizable split, provider icons, and execution improvements#4292

Open
waleedlatif1 wants to merge 16 commits intostagingfrom
waleedlatif1/trace-spans-rewrite
Open

feat(logs): trace span tree rewrite with resizable split, provider icons, and execution improvements#4292
waleedlatif1 wants to merge 16 commits intostagingfrom
waleedlatif1/trace-spans-rewrite

Conversation

@waleedlatif1
Copy link
Copy Markdown
Collaborator

Summary

  • Rewrote trace span pipeline: per-segment enrichment (model, provider, tokens, cost, TTFT, tool I/O), streaming segment extension, recursive iteration grouping for nested loops/parallels
  • New two-pane trace view: hierarchical span tree on the left with keyboard navigation, detail pane on the right showing input/output/thinking/tool calls/metadata per span
  • Resizable split between tree and detail panes (default 360px, drag to resize)
  • Gantt bars on each tree row share a single time axis across all depths; fixed bar invisibility for late-run spans (clamp offset to ensure MIN_BAR_PCT always visible)
  • Provider logos on model child spans (OpenAI, Anthropic, etc.) with luminance-based icon contrast for light backgrounds
  • emcn Badge for status indicators, ghost variant for jump-to-error button
  • Resizable right panel in workflow snapshot preview (280–600px)
  • Removed double X button in modal snapshot via showBlockCloseButton prop
  • Scroll-into-view in logs only triggers on keyboard nav, not click
  • Credentials manager: toast feedback, allow editing conflict rows, fix disabled visibility, use design tokens
  • emcn import cleanup: subpath imports → barrel across trace-view, log-details, execution-snapshot

Type of Change

  • Bug fix
  • New feature

Testing

Tested manually with runs containing agents, loops, parallels, nested workflows, and error paths.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

waleedlatif1 and others added 14 commits April 24, 2026 15:53
…ion enrichment

Unify tool calls under span.children, capture dual-clock timing, and
surface per-iteration model content (assistant text, thinking, tool
calls, finish reason, tokens, cost, ttft, provider, errors) across all
12 LLM providers. UI renders the new fields on model child spans; old
logs degrade gracefully since every field is optional.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Wrap log-details drawer in Overview | Trace tabs; Overview unchanged
- New TraceView with hierarchical tree on the left and detail pane on the right
- Keyboard nav, span filter, expand/collapse all
- Bump min drawer width 400->600 and clamp persisted widths on rehydrate

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Brings PR #4181 inline: persists workflowInput on successful runs,
adds useRetryExecution mutation (streaming read-one-chunk-and-cancel),
Retry entrypoints in the row context menu and the detail sidebar, and
extractRetryInput with fallback to starter block state for older logs.
Also surfaces the captured input in a new "Workflow Input" section
above Workflow Output in the detail Overview tab, guarded so older
logs without the field don't render an empty block.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…oped keyboard nav

- Pad tree rows from panel edge so the root chevron isn't visually clipped.
- Key DetailCodeSection by label so collapse state belongs to the section
  purpose, preventing isOpen from leaking across span changes when positional
  slots happened to align.
- Ignore log-to-log arrow-key nav while the Trace tab is active so TraceView
  owns span navigation; filter inputs keep native caret movement.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Chevron at depth 0 and the timeline bars now sit on the same 14px left/right
grid as the trace view's header strip and the rest of the log details panel,
removing the stagger where bars extended further left than chevrons and the
chevron appeared cramped against the panel edge.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Overview tab's scroll container (SModalTabsContent) was wrapped around a
non-overflow inner div that held the scrollAreaRef, so the scroll-reset on
log change targeted a non-scrolling element. Collapse the wrapper into the
Tabs.Content element itself and move the ref there. Add min-h-0 to the
Trace detail pane wrapper so its scrolling child can shrink inside the
horizontal-flex row.
Tailwind's `.flex` utility overrides the UA `[hidden]` rule, so applying
`flex` to SModalTabsContent caused the inactive Overview panel to still
participate in the Tabs flex column and push the Trace view down. Keep
SModalTabsContent as a plain overflow container (no `flex` class) with
the scroll ref on it, and restore the inner flex-col wrapper for the
Overview content so it still stacks with gap spacing.
- Tree pane now has top padding so the first row has breathing room
  under the header strip instead of sitting flush against the border.
- DetailCodeSection dropped its wrapper `overflow-hidden`. Per CSS, a
  flex item with `overflow: hidden` resolves `min-height: auto` to `0`,
  so when Input and Output were both expanded the flex algorithm
  shrank each section below its content, cutting off rows. Without the
  clip, sections size to content and the surrounding pane's
  `overflow-y-auto` takes over.
- Selected span row now scrolls into view on selection change, so
  arrow-key navigation always keeps the active row visible in the
  tree pane.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…, cleanup

- Resizable tree/detail split in trace view (default 360px, drag to resize)
- Resizable right panel in preview snapshot (280–600px)
- Fix Gantt bar invisibility for late-run spans (clamp offsetPct to 100-MIN_BAR_PCT)
- Propagate model+provider to child model spans in span-factory for correct icons
- Fix icon contrast on light provider backgrounds (luminance-based color class)
- Replace custom status badges with emcn Badge component
- Lighten jump-to-error button to ghost variant
- Remove double X button in modal snapshot (showBlockCloseButton prop)
- Fix emcn subpath imports → barrel in trace-view, log-details, execution-snapshot
- Fix hover: → hover-hover: on resize handles
- Add body style cleanup on resize unmount
- Fix React Query key factory naming (stats/stat convention)
- Remove unnecessary useCallback/useMemo in preview and execution-snapshot
- logs: only scroll-into-view on keyboard nav, not on click selection
- resource: stable scrollbar gutter, wider first column
- credentials: toast success/error feedback, remove useMemo for personalEnvData,
  allow editing conflict rows, fix disabled state visibility, use --text-error token
- integrations: use --text-error token for error state
- input: increase right padding (px-2 → pl-2 pr-3)
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 24, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Apr 25, 2026 8:25am

Request Review

@cursor
Copy link
Copy Markdown

cursor Bot commented Apr 24, 2026

PR Summary

Medium Risk
Moderate risk due to a large rewrite of trace-span generation/rendering and changes to executor logging callbacks/timestamps, which could affect log accuracy and UI navigation. Retry execution adds new mutation paths and relies on fetching log details/input extraction, so edge cases in legacy logs or permissions could cause runtime errors.

Overview
Adds a new two-pane TraceView for execution logs (tree + detail split, keyboard navigation, filtering, resizable panes) and wires it into LogDetails via an Overview/Trace tab layout.

Upgrades trace-span rendering to show additional per-span data (e.g. thinking/tool calls, provider/model metadata, tokens/cost/TTFT/throughput, explicit error type/message) and normalizes child/tool spans handling; also updates Copilot log tools to extract tool calls from both legacy and new span shapes.

Introduces retry execution from both the log row context menu and sidebar (fetches log detail, derives retry input with extractRetryInput, triggers /execute and shows toasts), plus small UX tweaks (scroll-into-view only on keyboard navigation, z-index/scrollbar/layout adjustments, and credentials manager toast + conflict-row edit behavior).

On the backend/executor side, improves block logging timing consistency and makes onBlockStart/onBlockComplete progress callbacks fire async (non-blocking), and extends execution log data to persist workflowInput and richer provider timing/cost/token types.

Reviewed by Cursor Bugbot for commit fbcee22. Configure here.

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit fbcee22. Configure here.

})
}
}
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fire-and-forget callbacks create cost tracking race condition

High Severity

The fireBlockCompleteCallback changed from awaiting onBlockComplete to fire-and-forget (void promise.catch(…)). The onBlockComplete handler in logging-session.ts accumulates costs on this.accumulatedCost after an internal await this.trackProgressWrite(…). Since the callback is no longer awaited, the workflow can finish and read accumulatedCost before the last block's cost accumulation completes — resulting in incomplete cost/token data in the final execution log. This is especially impactful for short workflows or single-block executions where the cost could be entirely missing.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit fbcee22. Configure here.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 24, 2026

Greptile Summary

This PR rewrites the trace span pipeline end-to-end: a new span-factory converts BlockLogs into richly-annotated TraceSpans (per-segment model/tool enrichment, token/cost/TTFT metadata), a new iteration-grouping module recursively nests loop/parallel spans, and a new 1,176-line TraceView component replaces the old flat list with a resizable two-pane tree+detail view with Gantt bars, provider logos, and keyboard navigation. The block executor's start/complete callbacks are changed from await to fire-and-forget, and workflowInput is persisted to executionData to support retry.

Confidence Score: 5/5

Safe to merge; all remaining findings are P2 style/robustness suggestions with no data-loss or crash risk on the happy path.

No P0 or P1 issues found. The three P2 comments cover: (1) a fragile heuristic in a legacy-log fallback that only affects old-format logs, (2) a fire-and-forget DB backpressure concern that is intentional per the PR description, and (3) a name-pattern false-positive in iteration grouping that requires a deliberately unusual block name to trigger.

apps/sim/app/workspace/[workspaceId]/logs/utils.ts (extractRetryInput heuristic), apps/sim/lib/logs/execution/trace-spans/iteration-grouping.ts (name-match classification)

Important Files Changed

Filename Overview
apps/sim/app/workspace/[workspaceId]/logs/components/log-details/components/trace-view/trace-view.tsx New 1,176-line component implementing the two-pane hierarchical trace viewer with resizable split, keyboard navigation, Gantt bars, and provider icons; complex but well-structured.
apps/sim/executor/execution/block-executor.ts Callbacks changed from awaited to fire-and-forget (void .catch); improves wall-clock accuracy but removes DB backpressure — see comment.
apps/sim/lib/logs/execution/trace-spans/iteration-grouping.ts New module with recursive algorithm for grouping loop/parallel iteration spans; name-match heuristic can misclassify user-named blocks ending with "(iteration N)".
apps/sim/lib/logs/execution/trace-spans/span-factory.ts New module converting BlockLogs into TraceSpans with per-segment enrichment, tool I/O merging, legacy shape guards, and streaming segment extension — well-guarded.
apps/sim/providers/trace-enrichment.ts New shared enrichment helper for OpenAI-compatible providers; extracts reasoning, tool calls, tokens, and cost per iteration segment cleanly.
apps/sim/app/workspace/[workspaceId]/logs/utils.ts Adds extractRetryInput helper; heuristic for old-log fallback is fragile and could return wrong block output for failed runs.
apps/sim/lib/logs/types.ts TraceSpan extended with per-segment fields (thinking, modelToolCalls, finishReason, ttft, provider, toolCallId); TokenInfo aliased to BlockTokens for consolidation.
apps/sim/executor/types.ts Adds well-typed ProviderTimingSegment, BlockTokens, BlockToolCall, IterationToolCall structs; NormalizedBlockOutput typed more strictly.
apps/sim/lib/logs/execution/trace-spans/trace-spans.ts Refactored from monolithic 865-line file to thin orchestrator delegating to span-factory and iteration-grouping; filterHiddenOutputKeys preserved.
apps/sim/stores/logs/utils.ts Default and minimum panel width increased from 400px to 600px to accommodate the new two-pane trace view.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[BlockExecutor.executeBlock] -->|captures startedAt/startTime| B[createBlockLog]
    B --> C[fireBlockStartCallback\nvoid .catch]
    C -->|async, non-blocking| D[(DB: progress marker)]
    A --> E[execute block]
    E -->|success| F[fireBlockCompleteCallback\nvoid .catch]
    E -->|error| G[handleBlockError\nfireBlockCompleteCallback]
    F -->|async, non-blocking| D
    G -->|async, non-blocking| D
    E --> H[BlockLog pushed to ctx.blockLogs]
    H --> I[ExecutionResult.logs]
    I --> J[buildTraceSpans]
    J --> K[createSpanFromLog\nspan-factory.ts]
    K -->|timeSegments present| L[buildChildrenFromTimeSegments]
    K -->|no timeSegments| M[buildChildrenFromToolCalls]
    J --> N[groupIterationBlocks\niteration-grouping.ts]
    N --> O[groupIterationBlocksRecursive\nLoop/Parallel containers]
    O --> P[TraceView\ntrace-view.tsx]
    P --> Q[Resizable split\nTree pane left]
    P --> R[Detail pane right]
    Q --> S[Gantt bars]
    Q --> T[Provider icons]
Loading

Reviews (1): Last reviewed commit: "chore(skills): add /ship command to clau..." | Re-trigger Greptile

Comment on lines +462 to +472
blockStates?: Record<
string,
{ output?: unknown; executed?: boolean; executionTime?: number }
>
}
| undefined
if (!executionState?.blockStates) return undefined

// Starter/trigger blocks are pre-populated with executed: false and
// executionTime: 0, which distinguishes them from blocks that actually ran.
for (const state of Object.values(executionState.blockStates)) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 extractRetryInput heuristic is fragile for failed runs

The condition executed === false && executionTime === 0 && output != null is intended to identify starter/trigger blocks, but it is not unique. In a run that failed early, any block that was skipped or never reached also satisfies executed: false, executionTime: 0. Object.values iteration order is insertion order (stable in V8, but not guaranteed to put the trigger block first). If the first matching state belongs to a non-trigger block, the wrong output is returned as the retry input — causing a retry with unexpected payload.

A safer approach is to also check that the blockId matches a known trigger block type, or to use the workflowInput field (added in this same PR) directly and only fall back to the heuristic for pre-migration logs.

Comment on lines 458 to 475
return redactApiKeys(result)
}

private async callOnBlockStart(
/**
* Fires the `onBlockStart` progress callback without blocking block execution.
* Any error is logged and swallowed so callback I/O never stalls the critical path.
*/
private fireBlockStartCallback(
ctx: ExecutionContext,
node: DAGNode,
block: SerializedBlock,
executionOrder: number
): Promise<void> {
): void {
if (!this.contextExtensions.onBlockStart) return

const blockId = node.metadata?.originalBlockId ?? node.id
const blockName = block.metadata?.name ?? blockId
const blockType = block.metadata?.id ?? DEFAULTS.BLOCK_TYPE
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Fire-and-forget callbacks remove backpressure on DB writes

fireBlockStartCallback and fireBlockCompleteCallback now fire-and-forget (via void ...catch), removing the serial await that previously guaranteed each progress DB write finished before the next block began. Under heavy load or high-iteration-count loops, many unresolved promises can pile up simultaneously, potentially exhausting the DB connection pool and producing out-of-order progress records. Errors are only logged at warn, so callers have no signal that a write was dropped.

If the callbacks are purely best-effort streaming markers this is acceptable, but consider documenting that contract explicitly and adding a max-concurrency guard (e.g., a semaphore or a sliding window) for workflows with large fan-out.

Comment on lines +210 to +218
*
* Handles two cases:
* 1. **Flat** (backward compat): spans have loopId/parallelId + iterationIndex but no
* parentIterations. Grouped by immediate container -> iteration -> leaf.
* 2. **Nested** (new): spans have parentIterations chains. The outermost ancestor in the
* chain determines the top-level container. Iteration spans are peeled one level at a
* time and recursed.
*/
function groupIterationBlocksRecursive(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Name-pattern match can produce false positives for user-named blocks

The check span.name.match(/^(.+) \(iteration (\d+)\)$/) is used as the primary signal that a span belongs to an iteration. Any span whose user-supplied name ends with " (iteration N)" — e.g. a block named "Image (iteration 3)" — is silently reclassified as an iteration span and nested inside a loop container, even if it has no loopId or parallelId. This could corrupt the trace tree for such blocks.

Consider also requiring that the span has a loopId, parallelId, or non-empty parentIterations before treating it as an iteration span.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant