feat: add clickable Run reference rendering in observability UI#1681
feat: add clickable Run reference rendering in observability UI#1681TooTallNate wants to merge 3 commits intomainfrom
Conversation
When a serialized Run object appears in step input/output data, it is
now rendered as a clickable purple badge showing the runId. Clicking
navigates to the target run's detail page.
Changes:
- serialization-format.ts: Add RunRef type, isRunRef(), serializedRunToRunRef(),
and 'Run' entry in observabilityRevivers
- data-inspector.tsx: Add RunRefInline component (purple badge), RunClickContext,
collapseRefs() to make refs non-expandable in ObjectInspector
- attribute-panel.tsx: Thread onRunClick prop, wrap in RunClickContext.Provider
- entity-detail-panel.tsx: Thread onRunClick prop
- run-trace-view.tsx: Thread onRunClick prop
- workflow-trace-view.tsx: Thread onRunClick prop, reset selected span on run change
- trace-span-construction.ts: Show step name for builtin steps instead of empty string
- hydration.ts: Re-export RunRef types
- run-detail-view.tsx: Add handleRunRefClick that navigates to /run/{targetRunId}
🦋 Changeset detectedLatest commit: 4a71ef7 The changes in this PR will be included in the next version bump. This PR includes changesets to release 17 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
📊 Benchmark Results
workflow with no steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Next.js (Turbopack) | Nitro workflow with 1 step💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Next.js (Turbopack) workflow with 10 sequential steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Next.js (Turbopack) workflow with 25 sequential steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Next.js (Turbopack) | Nitro workflow with 50 sequential steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Next.js (Turbopack) Promise.all with 10 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Next.js (Turbopack) | Nitro Promise.all with 25 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Next.js (Turbopack) Promise.all with 50 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Next.js (Turbopack) Promise.race with 10 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Next.js (Turbopack) | Nitro Promise.race with 25 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Next.js (Turbopack) Promise.race with 50 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Next.js (Turbopack) workflow with 10 sequential data payload steps (10KB)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Next.js (Turbopack) workflow with 25 sequential data payload steps (10KB)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Next.js (Turbopack) workflow with 50 sequential data payload steps (10KB)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Next.js (Turbopack) workflow with 10 concurrent data payload steps (10KB)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Next.js (Turbopack) workflow with 25 concurrent data payload steps (10KB)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Next.js (Turbopack) workflow with 50 concurrent data payload steps (10KB)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Next.js (Turbopack) | Nitro Stream Benchmarks (includes TTFB metrics)workflow with stream💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Next.js (Turbopack) stream pipeline with 5 transform steps (1MB)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Next.js (Turbopack) 10 parallel streams (1MB each)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Next.js (Turbopack) fan-out fan-in 10 streams (1MB each)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Next.js (Turbopack) SummaryFastest Framework by WorldWinner determined by most benchmark wins
Fastest World by FrameworkWinner determined by most benchmark wins
Column Definitions
Worlds:
❌ Some benchmark jobs failed:
Check the workflow run for details. |
🧪 E2E Test Results❌ Some tests failed Summary
❌ Failed Tests🌍 Community Worlds (74 failed)mongodb (7 failed):
redis (7 failed):
turso (60 failed):
Details by Category✅ ▲ Vercel Production
✅ 💻 Local Development
✅ 📦 Local Production
✅ 🐘 Local Postgres
✅ 🪟 Windows
❌ 🌍 Community Worlds
✅ 📋 Other
❌ Some E2E test jobs failed:
Check the workflow run for details. |
There was a problem hiding this comment.
Pull request overview
Adds a UI-friendly RunRef marker to hydrated observability data so serialized Run values can render as clickable run-id badges that navigate to the referenced run detail page.
Changes:
- Introduce
RunRefin core serialization format and revive serializedRunvalues intoRunReffor o11y hydration. - Render
RunRefinline in the sharedDataInspectorand threadonRunClickthrough trace/detail panels to enable navigation. - Improve fallback naming for steps (built-in steps now show a non-empty name).
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| packages/core/src/serialization-format.ts | Adds RunRef marker/type + reviver so o11y hydration converts serialized Run to display-friendly references. |
| packages/web-shared/src/lib/hydration.ts | Re-exports RunRef utilities/types for web consumers. |
| packages/web-shared/src/components/ui/data-inspector.tsx | Adds inline RunRef rendering + ref-collapsing utilities and click context plumbing. |
| packages/web-shared/src/components/sidebar/attribute-panel.tsx | Provides RunClickContext to sidebar attribute rendering and improves step name fallback display. |
| packages/web-shared/src/components/sidebar/entity-detail-panel.tsx | Threads onRunClick into the sidebar attribute panel. |
| packages/web-shared/src/components/workflow-trace-view.tsx | Threads onRunClick to entity panel and resets selected span when run changes. |
| packages/web-shared/src/components/run-trace-view.tsx | Threads onRunClick into WorkflowTraceViewer. |
| packages/web-shared/src/components/workflow-traces/trace-span-construction.ts | Uses a better step name fallback for span names. |
| packages/web/app/components/run-detail-view.tsx | Implements run-ref click handler that navigates to /run/{runId}. |
| .changeset/o11y-run-ref-rendering.md | Publishes patch bumps for core/web-shared/web for the new o11y behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| function collapseRefs(data: unknown): unknown { | ||
| if (data === null || typeof data !== 'object') return data; | ||
| if (isRunRef(data) || isStreamRef(data)) | ||
| return makeOpaqueRef(data as unknown as Record<string, unknown>); | ||
| if (Array.isArray(data)) return data.map(collapseRefs); | ||
| const result: Record<string, unknown> = {}; | ||
| for (const [key, value] of Object.entries(data)) { | ||
| result[key] = collapseRefs(value); | ||
| } | ||
| return result; |
There was a problem hiding this comment.
collapseRefs() rebuilds every non-array object via Object.entries and a new {}. This will strip/flatten non-plain instances that getWebRevivers() explicitly revives (e.g. Date, Error, Map, Set, URL, Headers), often turning them into {} and breaking the existing Date/Error rendering in NodeRenderer. Consider only collapsing refs within plain objects/arrays (e.g., guard on Object.getPrototypeOf(data) being Object.prototype/null) and returning other object instances unchanged; also memoize the collapsed result so it isn’t recomputed (and deep-compared) on every render.
There was a problem hiding this comment.
Fixed. collapseRefs() now checks Object.getPrototypeOf(data) and only recurses into plain objects (Object.prototype or null prototype). Class instances like Date, Error, Map, Set, URL, Headers, etc. are returned unchanged. Also memoized the collapsed result with useMemo to avoid recomputing on every render.
Only recurse into plain objects (prototype is Object.prototype or null) to avoid stripping class instances like Date, Error, Map, etc. that have their own rendering in NodeRenderer. Also memoize the collapsed result to avoid recomputing on every render.
…de key The Run class goes through the standard Instance serialization pipeline (WORKFLOW_SERIALIZE/WORKFLOW_DESERIALIZE), not a dedicated 'Run' key. Move the RunRef detection into serializedInstanceToRef() which checks if the className is 'Run' and the data contains a runId string, then returns a RunRef instead of a generic ClassInstanceRef.
Summary
When a serialized
Runobject appears in step input/output data in the observability UI, it is now rendered as a clickable purple badge showing therunId. Clicking the badge navigates to the target run's detail page.This was extracted from PR #1491 as a standalone o11y feature.
Changes
Core
serialization-format.ts: AddRunReftype,isRunRef(),serializedRunToRunRef(), andRunentry inobservabilityReviversWeb Shared
data-inspector.tsx: AddRunRefInlinecomponent (purple clickable badge),RunClickContext,makeOpaqueRef()/collapseRefs()utilities to prevent ObjectInspector from expanding ref objectsattribute-panel.tsx: ThreadonRunClickprop, wrap content inRunClickContext.Provider, improvestepNamedisplay fallbackentity-detail-panel.tsx: ThreadonRunClickproprun-trace-view.tsx: ThreadonRunClickpropworkflow-trace-view.tsx: ThreadonRunClickprop, reset selected span when navigating to a different runtrace-span-construction.ts: Show step name for builtin steps instead of empty stringhydration.ts: Re-exportRunRef,RUN_REF_TYPE,isRunRefWeb
run-detail-view.tsx: AddhandleRunRefClickcallback that navigates to/run/{targetRunId}