fix(logs): split summary/detail contracts to make trace tab gate type-safe#4431
fix(logs): split summary/detail contracts to make trace tab gate type-safe#4431waleedlatif1 merged 14 commits intostagingfrom
Conversation
…-safe The Trace tab was silently missing from the Log Details sidepanel because list and detail rows shared one WorkflowLog type with executionData: z.unknown(). The UI couldn't distinguish a summary row (no spans) from a detail row (with spans), so the tab gate read undefined and hid itself. Splits into WorkflowLogSummary (list) and WorkflowLogDetail (typed executionData with optional traceSpans). Detail and by-execution routes both write through to the same logKeys.detail(id) cache, eliminating the two-key fragmentation that caused the merge memo workaround. List route moves to cursor pagination on (sortValue, id) with proper NULLS LAST handling and SQL-side sort across workflow + job execution tables. Detail route now requires and asserts workspaceId. Deep-link path uses useLogByExecutionId instead of auto-paginating the entire workspace. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryMedium Risk Overview Refactors the logs API surface: Updates client hooks and UI to match: Reviewed by Cursor Bugbot for commit fb0ae86. Configure here. |
Greptile SummaryThis PR fixes the missing Trace tab by splitting
Confidence Score: 3/5Not safe to merge — the unfixed cross-table cursor tiebreaker causes silent row drops on page 2+ when workflow and job logs are mixed, and the new render-phase side effect risks double-initializing filter state in Strict Mode. Two P1 findings: the previously flagged cross-table cursor UUID tiebreaker is still present (confirmed not addressed in this diff), and the new useState render-phase side effect for initializeFromURL. Multiple P1s on a core data-fetching path pull the score below the 4/5 P1 ceiling. apps/sim/app/api/logs/route.ts (cursor tiebreaker), apps/sim/app/workspace/[workspaceId]/logs/logs.tsx (useState side effect) Important Files Changed
Sequence DiagramsequenceDiagram
participant UI as Logs UI
participant ListAPI as GET /api/logs
participant DetailAPI as GET /api/logs/[id]
participant ByExecAPI as GET /api/logs/by-execution/[id]
participant DB as Database
Note over UI: Normal page load
UI->>ListAPI: workspaceId + filters + sortBy/sortOrder
ListAPI->>DB: workflow UNION job (cursor paginated, SQL-sorted)
DB-->>ListAPI: WorkflowLogSummary[] + nextCursor
ListAPI-->>UI: { data, nextCursor }
Note over UI: User clicks a row
UI->>DetailAPI: logId + workspaceId
DetailAPI->>DB: fetchLogDetail(id)
DB-->>DetailAPI: WorkflowLogDetail (with executionData)
DetailAPI-->>UI: { data: WorkflowLogDetail }
UI-->>UI: setQueryData(logKeys.detail(id), data)
Note over UI: Deep-link ?executionId=X
UI->>ByExecAPI: executionId + workspaceId
ByExecAPI->>DB: fetchLogDetail(executionId)
DB-->>ByExecAPI: WorkflowLogDetail
ByExecAPI-->>UI: { data: WorkflowLogDetail }
UI-->>UI: setQueryData(logKeys.detail(data.id), data)
UI-->>UI: dispatch TOGGLE_LOG → sidebar opens
|
… enhanced spread order - Move onActiveTabChange call from render into useEffect to avoid side-effects during render (StrictMode safety). - Re-add logKeys.stats() invalidation to cancel/retry mutations so the dashboard reflects status flips immediately. - Reorder enhanced: true after ...execData spread in detail and by-execution routes so the literal discriminator is never overwritten by stale execData. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The in-memory merge of workflow + job pages negated the comparator
for DESC, which placed null sort values at the start. SQL orders
both ASC and DESC with NULLS LAST, so DESC pages emitted a cursor
{v: <last non-null>, id: ...} while null rows still satisfied the
cursor predicate (OR sort_expr IS NULL) on the next page —
producing duplicate null rows across pages on cost/duration sorts.
Handle nulls explicitly in the JS comparator so they always sort
last regardless of direction, matching the SQL ordering.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
@greptile |
|
@cursor review |
…invalidation, optimistic detail patch, trace loading state - Wrap LogDetails -> LogDetailsContent onActiveTabChange in useCallback so the child useEffect doesn't refire on every parent render. - Add logKeys.byExecutionAll() to cancel + retry invalidation so the table-embedded sidebar picks up status changes immediately. - Optimistic write-through to logKeys.detail in useCancelExecution so the open sidebar reflects 'cancelling' instantly; rolls back on error. - Distinguish trace loading from trace-empty: when log.executionData is not yet fetched, render "Loading trace…" instead of the empty state. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace the legacy `WorkflowLog` / `LogsResponse` / `WorkflowData` / `CostMetadata` / `ToolCallMetadata` shapes in `stores/logs/filters/types.ts` with direct use of the contract types `WorkflowLogSummary`, `WorkflowLogDetail`, and a new `WorkflowLogRow` alias for surfaces that render either form. Removes the `summaryToWorkflowLog` / `detailToWorkflowLog` bridge in the React Query layer along with their double-cast annotations. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Whitelist sort columns against logSortBy enum to prevent client crash when non-sortable headers (workflow, trigger) reach the contract parser. - Extract fetchLogDetail helper shared by /api/logs/[id] and /api/logs/by-execution/[executionId] — collapses ~360 duplicated lines to a single source of truth keyed on lookup column.
|
@greptile |
|
@cursor review |
When level=running or level=pending (workflow-only states involving endedAt/pausedExecutions semantics), jobLevelConditions stayed empty so no level constraint reached jobConditions — every job log in the workspace leaked into the result. Skip the job side entirely when the level filter has no job-applicable values (error/info).
Remove ExecutionLog/RawLogResponse/ExecutionCost/LogWithExecutionData/ TraceSpan/BlockExecution interfaces and the mapToExecutionLog, mapToExecutionLogAlt, extractOutput functions — all unreferenced after the contract split. -212 lines.
The previous useEffect-based initializeFromURL caused useLogsList and useDashboardStats to fire once with default store filters, then refetch after the effect updated filters from the URL. Move the initial hydrate into a useState lazy initializer so the first render already reads URL-derived filters; the popstate handler keeps the existing effect for back/forward navigation.
…traces-fix # Conflicts: # apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.tsx
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
@cursor review |
|
@greptile |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit e98e80a. Configure here.
|
@greptileai pushing back on both points after re-tracing the code: P1 — cross-table UUID tiebreaker. Not a bug. The cursor
…the predicate P2 — URL hydration double-fetch. Not a bug. If I'm missing something on either point, happy to dig in further — please point at the specific scenario that drops rows. |
|
Having read the full P1 — Cross-table cursor: Your analysis is correct. P2 — URL hydration: Also correct. The I'll withdraw both comments.
|
|
@greptile |
|
@cursor review |
Deep-linked logs resolved via useLogByExecutionId may not be in the current page list, leaving selectedLogIndex at -1. The hasNext prop was evaluating -1 < logs.length - 1 (true for any non-empty list), which enabled the next arrow and jumped to the first item on click. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
@greptile |
|
@cursor review |
…rds aligned Run the resolvedTab → onActiveTabChange propagation in useLayoutEffect so the parent's activeTabRef updates synchronously before the next paint. This closes the brief window where window keydown handlers in the logs page would still see activeTabRef.current === 'trace' and short-circuit arrow-key navigation immediately after switching to a log without a Trace tab. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
@greptile |
|
@cursor review |
|
@greptile |
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit fb0ae86. Configure here.
Summary
The Trace tab was silently missing from the Log Details sidepanel because list and detail rows shared one
WorkflowLogtype withexecutionData: z.unknown(). The UI couldn't distinguish a summary row (no spans) from a detail row (with spans), so the tab gate read undefined and hid itself.WorkflowLogSummary(list) andWorkflowLogDetail(typedexecutionDatawith optionaltraceSpans) — makes the bug structurally impossible.logKeys.detail(id)cache, eliminating two-key fragmentation that caused the merge memo workaround.(sortValue, id)with properNULLS LASThandling and SQL-side sort across workflow + job execution tables (replaces in-memory merge that dropped rows under filters).workspaceId. Deep-link path usesuseLogByExecutionIdinstead of auto-paginating the entire workspace.Test plan
?executionId=<id>→ resolves in one round-trip, sidebar opens with detail (even for off-page logs)lists()+detail(id), no stats refetch?executionId=<id from other workspace>→ 404