Skip to content

Fix trace detail panel Input/Output flicker (selection-driven state machine) - #2637

Merged
mitul-s merged 5 commits into
mainfrom
c+ms/detail-panel-io-flicker-refactor-2085
Jun 26, 2026
Merged

Fix trace detail panel Input/Output flicker (selection-driven state machine)#2637
mitul-s merged 5 commits into
mainfrom
c+ms/detail-panel-io-flicker-refactor-2085

Conversation

@mitul-s

@mitul-smitul-s commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Description

The run trace detail panel flickered its Input/Output sections every time you navigated between spans (steps/spans).

Root cause (structural). The panel rendered from two sources that update on different React commits:

  • The selected span (selectedSpan.data) updates synchronously on click, but the trace builder strips input/output, so it never carries I/O.
  • The fetched detail that carries I/O came from a lagging chain: EntityDetailPanel effect → onSpanSelect → page spanSelection state → useWorkflowResourceData → back down via SidebarDataContext.

So for 1–2 commits right after selecting a span, the panel showed the new span with isLoading === false while the fetched detail still belonged to the previous span (correctly rejected). With nothing to render and loading false, the Input/Output sections rendered nothing — they vanished, then reappeared a couple commits later. That disappear/reappear is the flicker. Every existing guard (spanDetailMatchesSelection, timestamp-pinning, the resolvedAttributes force-defaults, prevSelectionRef) was compensating for these signals being out of phase.

Fix. Collapse the out-of-phase signals into a single selection-driven state machine:

  • fetchSpanDetailResource (@workflow/web) — the run/step/hook/sleep fetch+hydrate core extracted out of useWorkflowResourceData as a plain async function (no React state). The hook now delegates to it.
  • useSelectedSpanDetail (@workflow/web-shared) — derives the selection synchronously, fetches its detail directly (with a request-token to drop stale/out-of-order responses), and returns a { status, displayData, error } view-model. status is a pure function of (selection, fetched detail) via deriveSpanDetailView, so it stays in phase with the selection on every render and the panel is loading from the first frame after a new span is picked until its matching detail arrives.

This is a net simplification: it removes the duplicate spanSelection state, the onSpanSelect notify-effect, four SidebarDataContext fields (spanDetailData/spanDetailError/spanDetailLoading/onSpanSelect, now a single injected fetchSpanDetail), and the scattered match/merge plumbing. The trace viewer's inbound contract shrinks to one fetchSpanDetail capability rather than mirroring transient async state back out to the host.

A follow-up cleanup of the AttributePanel Input/Output loading placeholders is split into a separate stacked PR (#2642) to keep this one focused on the flicker fix.

How did you test your changes?

  • Unit tests (packages/web-shared/test/span-detail-merge.test.ts) for the pure deriver deriveSpanDetailView — idle / loading / ready / error, stale-detail rejection (the exact race that caused the vanish), hooks ready inline — plus resourceNeedsFetchedDetail.
  • Existing useWorkflowResourceData tests still pass unchanged (the hook now delegates to the extracted fetchSpanDetailResource).
  • @workflow/web-shared builds (tsc) cleanly; tsc --noEmit on @workflow/web is clean for all touched files; Biome reports zero errors on the changed files; full web-shared vitest suite is green (the only failures are the pre-existing zstd-decoder tests, which need a newer Node than this CI runner).
  • Manual browser verification (navigating spans and rapid j/k in the run detail view to watch Input/Output stay stable) still needs an interactive browser session against a backend with runs — flagging for a reviewer to confirm visually.

PR Checklist - Required to merge

  • 📦 pnpm changeset was run to create a changelog for this PR (@workflow/web-shared + @workflow/web, patch)
  • 🔒 DCO sign-off passes — commits are not yet signed off (I avoided amending already-made commits); needs --signoff
  • 📝 Ping @vercel/workflow in a comment once the PR is ready, and the above checklist is complete
Open in WebOpen in Cursor

Extract the run/step/hook/sleep fetch+hydrate core out of useWorkflowResourceData
into a plain async fetchSpanDetailResource (no React state), and add a
selection-driven state machine in web-shared:
- deriveSpanDetailView / resourceNeedsFetchedDetail: pure view-model deriver
whose status (idle/loading/ready/error) is a function of (selection, fetched
detail), so it can never lag the selection.
- useSelectedSpanDetail: fetches a selected span's detail directly with a
request-token to drop stale/out-of-order responses.
Replace the cross-package selection round-trip (EntityDetailPanel useEffect ->
onSpanSelect -> page spanSelection state -> useWorkflowResourceData -> context)
with a single injected fetchSpanDetail capability:
- EntityDetailPanel consumes useSelectedSpanDetail; its loading state now stays
in phase with the selected span, so Input/Output no longer vanish and pop back
in while navigating.
- SidebarDataContext drops spanDetailData/Loading/Error + onSpanSelect for a
single fetchSpanDetail; RunDetailView injects it and drops the duplicate
spanSelection state.
- WorkflowTraceViewer / RunTraceView take fetchSpanDetail too.
Cover deriveSpanDetailView (idle/loading/ready/error, stale-detail rejection,
hooks ready inline) and resourceNeedsFetchedDetail.
@changeset-bot

changeset-botBot commented Jun 25, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 450df62

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 16 packages
NameType
@workflow/web-sharedPatch
@workflow/webPatch
@workflow/cliPatch
@workflow/nitroPatch
workflowPatch
@workflow/world-testingPatch
@workflow/nuxtPatch
@workflow/corePatch
@workflow/buildersPatch
@workflow/nextPatch
@workflow/vitestPatch
@workflow/astroPatch
@workflow/nestPatch
@workflow/rollupPatch
@workflow/sveltekitPatch
@workflow/vitePatch

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

@vercel

vercelBot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

@mitul-s
mitul-s marked this pull request as ready for review June 26, 2026 20:27
@mitul-s
mitul-s requested review from a team and ijjk as code ownersJune 26, 2026 20:27

@karthikscale3karthikscale3 left a comment

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.

LGTM

@mitul-s
mitul-s merged commit 8393716 into mainJun 26, 2026
59 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

No backport to stable for 8393716 (AI decision).

The fix replaces a span-detail merge architecture (mergeSpanDetail/spanDetailMatchesSelection, the SidebarDataContextspanDetailData/onSpanSelect plumbing) with a new selection-driven state machine, but the modules it builds on and rewires — span-detail-merge.ts, sidebar-data-context.tsx, the new use-selected-span-detail.ts, and the test file — do not exist on stable (which still uses a simpler spanDetailData ?? data display path). Because the change depends on main-only trace-viewer/sidebar structure and cannot apply cleanly to the diverged stable architecture, it should not be backported.

To override, re-run the Backport to stable workflow manually via workflow_dispatch and paste this commit SHA into the ref input:

8393716687c77ec46c724cd48335aa7ea9176c8b

@github-actionsgithub-actionsBot mentioned this pull request Jun 26, 2026
Sign up for freeto 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.

3 participants

@mitul-s@karthikscale3@cursoragent
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Fix trace detail panel Input/Output flicker (selection-driven state machine) by mitul-s · Pull Request #2637 · vercel/workflow · GitHub
Skip to content

Fix trace detail panel Input/Output flicker (selection-driven state machine) - #2637

Merged
mitul-s merged 5 commits into
mainfrom
c+ms/detail-panel-io-flicker-refactor-2085
Jun 26, 2026
Merged

Fix trace detail panel Input/Output flicker (selection-driven state machine)#2637
mitul-s merged 5 commits into
mainfrom
c+ms/detail-panel-io-flicker-refactor-2085

Conversation

@mitul-s

@mitul-smitul-s commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Description

The run trace detail panel flickered its Input/Output sections every time you navigated between spans (steps/spans).

Root cause (structural). The panel rendered from two sources that update on different React commits:

  • The selected span (selectedSpan.data) updates synchronously on click, but the trace builder strips input/output, so it never carries I/O.
  • The fetched detail that carries I/O came from a lagging chain: EntityDetailPanel effect → onSpanSelect → page spanSelection state → useWorkflowResourceData → back down via SidebarDataContext.

So for 1–2 commits right after selecting a span, the panel showed the new span with isLoading === false while the fetched detail still belonged to the previous span (correctly rejected). With nothing to render and loading false, the Input/Output sections rendered nothing — they vanished, then reappeared a couple commits later. That disappear/reappear is the flicker. Every existing guard (spanDetailMatchesSelection, timestamp-pinning, the resolvedAttributes force-defaults, prevSelectionRef) was compensating for these signals being out of phase.

Fix. Collapse the out-of-phase signals into a single selection-driven state machine:

  • fetchSpanDetailResource (@workflow/web) — the run/step/hook/sleep fetch+hydrate core extracted out of useWorkflowResourceData as a plain async function (no React state). The hook now delegates to it.
  • useSelectedSpanDetail (@workflow/web-shared) — derives the selection synchronously, fetches its detail directly (with a request-token to drop stale/out-of-order responses), and returns a { status, displayData, error } view-model. status is a pure function of (selection, fetched detail) via deriveSpanDetailView, so it stays in phase with the selection on every render and the panel is loading from the first frame after a new span is picked until its matching detail arrives.

This is a net simplification: it removes the duplicate spanSelection state, the onSpanSelect notify-effect, four SidebarDataContext fields (spanDetailData/spanDetailError/spanDetailLoading/onSpanSelect, now a single injected fetchSpanDetail), and the scattered match/merge plumbing. The trace viewer's inbound contract shrinks to one fetchSpanDetail capability rather than mirroring transient async state back out to the host.

A follow-up cleanup of the AttributePanel Input/Output loading placeholders is split into a separate stacked PR (#2642) to keep this one focused on the flicker fix.

How did you test your changes?

  • Unit tests (packages/web-shared/test/span-detail-merge.test.ts) for the pure deriver deriveSpanDetailView — idle / loading / ready / error, stale-detail rejection (the exact race that caused the vanish), hooks ready inline — plus resourceNeedsFetchedDetail.
  • Existing useWorkflowResourceData tests still pass unchanged (the hook now delegates to the extracted fetchSpanDetailResource).
  • @workflow/web-shared builds (tsc) cleanly; tsc --noEmit on @workflow/web is clean for all touched files; Biome reports zero errors on the changed files; full web-shared vitest suite is green (the only failures are the pre-existing zstd-decoder tests, which need a newer Node than this CI runner).
  • Manual browser verification (navigating spans and rapid j/k in the run detail view to watch Input/Output stay stable) still needs an interactive browser session against a backend with runs — flagging for a reviewer to confirm visually.

PR Checklist - Required to merge

  • 📦 pnpm changeset was run to create a changelog for this PR (@workflow/web-shared + @workflow/web, patch)
  • 🔒 DCO sign-off passes — commits are not yet signed off (I avoided amending already-made commits); needs --signoff
  • 📝 Ping @vercel/workflow in a comment once the PR is ready, and the above checklist is complete
Open in WebOpen in Cursor

Extract the run/step/hook/sleep fetch+hydrate core out of useWorkflowResourceData
into a plain async fetchSpanDetailResource (no React state), and add a
selection-driven state machine in web-shared:
- deriveSpanDetailView / resourceNeedsFetchedDetail: pure view-model deriver
whose status (idle/loading/ready/error) is a function of (selection, fetched
detail), so it can never lag the selection.
- useSelectedSpanDetail: fetches a selected span's detail directly with a
request-token to drop stale/out-of-order responses.
Replace the cross-package selection round-trip (EntityDetailPanel useEffect ->
onSpanSelect -> page spanSelection state -> useWorkflowResourceData -> context)
with a single injected fetchSpanDetail capability:
- EntityDetailPanel consumes useSelectedSpanDetail; its loading state now stays
in phase with the selected span, so Input/Output no longer vanish and pop back
in while navigating.
- SidebarDataContext drops spanDetailData/Loading/Error + onSpanSelect for a
single fetchSpanDetail; RunDetailView injects it and drops the duplicate
spanSelection state.
- WorkflowTraceViewer / RunTraceView take fetchSpanDetail too.
Cover deriveSpanDetailView (idle/loading/ready/error, stale-detail rejection,
hooks ready inline) and resourceNeedsFetchedDetail.
@changeset-bot

changeset-botBot commented Jun 25, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 450df62

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 16 packages
NameType
@workflow/web-sharedPatch
@workflow/webPatch
@workflow/cliPatch
@workflow/nitroPatch
workflowPatch
@workflow/world-testingPatch
@workflow/nuxtPatch
@workflow/corePatch
@workflow/buildersPatch
@workflow/nextPatch
@workflow/vitestPatch
@workflow/astroPatch
@workflow/nestPatch
@workflow/rollupPatch
@workflow/sveltekitPatch
@workflow/vitePatch

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

@vercel

vercelBot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

@mitul-s
mitul-s marked this pull request as ready for review June 26, 2026 20:27
@mitul-s
mitul-s requested review from a team and ijjk as code ownersJune 26, 2026 20:27

@karthikscale3karthikscale3 left a comment

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.

LGTM

@mitul-s
mitul-s merged commit 8393716 into mainJun 26, 2026
59 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

No backport to stable for 8393716 (AI decision).

The fix replaces a span-detail merge architecture (mergeSpanDetail/spanDetailMatchesSelection, the SidebarDataContextspanDetailData/onSpanSelect plumbing) with a new selection-driven state machine, but the modules it builds on and rewires — span-detail-merge.ts, sidebar-data-context.tsx, the new use-selected-span-detail.ts, and the test file — do not exist on stable (which still uses a simpler spanDetailData ?? data display path). Because the change depends on main-only trace-viewer/sidebar structure and cannot apply cleanly to the diverged stable architecture, it should not be backported.

To override, re-run the Backport to stable workflow manually via workflow_dispatch and paste this commit SHA into the ref input:

8393716687c77ec46c724cd48335aa7ea9176c8b

@github-actionsgithub-actionsBot mentioned this pull request Jun 26, 2026
Sign up for freeto 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.

3 participants

@mitul-s@karthikscale3@cursoragent
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Fix trace detail panel Input/Output flicker (selection-driven state machine) by mitul-s · Pull Request #2637 · vercel/workflow · GitHub
Skip to content

Fix trace detail panel Input/Output flicker (selection-driven state machine) - #2637

Merged
mitul-s merged 5 commits into
mainfrom
c+ms/detail-panel-io-flicker-refactor-2085
Jun 26, 2026
Merged

Fix trace detail panel Input/Output flicker (selection-driven state machine)#2637
mitul-s merged 5 commits into
mainfrom
c+ms/detail-panel-io-flicker-refactor-2085

Conversation

@mitul-s

@mitul-smitul-s commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Description

The run trace detail panel flickered its Input/Output sections every time you navigated between spans (steps/spans).

Root cause (structural). The panel rendered from two sources that update on different React commits:

  • The selected span (selectedSpan.data) updates synchronously on click, but the trace builder strips input/output, so it never carries I/O.
  • The fetched detail that carries I/O came from a lagging chain: EntityDetailPanel effect → onSpanSelect → page spanSelection state → useWorkflowResourceData → back down via SidebarDataContext.

So for 1–2 commits right after selecting a span, the panel showed the new span with isLoading === false while the fetched detail still belonged to the previous span (correctly rejected). With nothing to render and loading false, the Input/Output sections rendered nothing — they vanished, then reappeared a couple commits later. That disappear/reappear is the flicker. Every existing guard (spanDetailMatchesSelection, timestamp-pinning, the resolvedAttributes force-defaults, prevSelectionRef) was compensating for these signals being out of phase.

Fix. Collapse the out-of-phase signals into a single selection-driven state machine:

  • fetchSpanDetailResource (@workflow/web) — the run/step/hook/sleep fetch+hydrate core extracted out of useWorkflowResourceData as a plain async function (no React state). The hook now delegates to it.
  • useSelectedSpanDetail (@workflow/web-shared) — derives the selection synchronously, fetches its detail directly (with a request-token to drop stale/out-of-order responses), and returns a { status, displayData, error } view-model. status is a pure function of (selection, fetched detail) via deriveSpanDetailView, so it stays in phase with the selection on every render and the panel is loading from the first frame after a new span is picked until its matching detail arrives.

This is a net simplification: it removes the duplicate spanSelection state, the onSpanSelect notify-effect, four SidebarDataContext fields (spanDetailData/spanDetailError/spanDetailLoading/onSpanSelect, now a single injected fetchSpanDetail), and the scattered match/merge plumbing. The trace viewer's inbound contract shrinks to one fetchSpanDetail capability rather than mirroring transient async state back out to the host.

A follow-up cleanup of the AttributePanel Input/Output loading placeholders is split into a separate stacked PR (#2642) to keep this one focused on the flicker fix.

How did you test your changes?

  • Unit tests (packages/web-shared/test/span-detail-merge.test.ts) for the pure deriver deriveSpanDetailView — idle / loading / ready / error, stale-detail rejection (the exact race that caused the vanish), hooks ready inline — plus resourceNeedsFetchedDetail.
  • Existing useWorkflowResourceData tests still pass unchanged (the hook now delegates to the extracted fetchSpanDetailResource).
  • @workflow/web-shared builds (tsc) cleanly; tsc --noEmit on @workflow/web is clean for all touched files; Biome reports zero errors on the changed files; full web-shared vitest suite is green (the only failures are the pre-existing zstd-decoder tests, which need a newer Node than this CI runner).
  • Manual browser verification (navigating spans and rapid j/k in the run detail view to watch Input/Output stay stable) still needs an interactive browser session against a backend with runs — flagging for a reviewer to confirm visually.

PR Checklist - Required to merge

  • 📦 pnpm changeset was run to create a changelog for this PR (@workflow/web-shared + @workflow/web, patch)
  • 🔒 DCO sign-off passes — commits are not yet signed off (I avoided amending already-made commits); needs --signoff
  • 📝 Ping @vercel/workflow in a comment once the PR is ready, and the above checklist is complete
Open in WebOpen in Cursor

Extract the run/step/hook/sleep fetch+hydrate core out of useWorkflowResourceData
into a plain async fetchSpanDetailResource (no React state), and add a
selection-driven state machine in web-shared:
- deriveSpanDetailView / resourceNeedsFetchedDetail: pure view-model deriver
whose status (idle/loading/ready/error) is a function of (selection, fetched
detail), so it can never lag the selection.
- useSelectedSpanDetail: fetches a selected span's detail directly with a
request-token to drop stale/out-of-order responses.
Replace the cross-package selection round-trip (EntityDetailPanel useEffect ->
onSpanSelect -> page spanSelection state -> useWorkflowResourceData -> context)
with a single injected fetchSpanDetail capability:
- EntityDetailPanel consumes useSelectedSpanDetail; its loading state now stays
in phase with the selected span, so Input/Output no longer vanish and pop back
in while navigating.
- SidebarDataContext drops spanDetailData/Loading/Error + onSpanSelect for a
single fetchSpanDetail; RunDetailView injects it and drops the duplicate
spanSelection state.
- WorkflowTraceViewer / RunTraceView take fetchSpanDetail too.
Cover deriveSpanDetailView (idle/loading/ready/error, stale-detail rejection,
hooks ready inline) and resourceNeedsFetchedDetail.
@changeset-bot

changeset-botBot commented Jun 25, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 450df62

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 16 packages
NameType
@workflow/web-sharedPatch
@workflow/webPatch
@workflow/cliPatch
@workflow/nitroPatch
workflowPatch
@workflow/world-testingPatch
@workflow/nuxtPatch
@workflow/corePatch
@workflow/buildersPatch
@workflow/nextPatch
@workflow/vitestPatch
@workflow/astroPatch
@workflow/nestPatch
@workflow/rollupPatch
@workflow/sveltekitPatch
@workflow/vitePatch

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

@vercel

vercelBot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

@mitul-s
mitul-s marked this pull request as ready for review June 26, 2026 20:27
@mitul-s
mitul-s requested review from a team and ijjk as code ownersJune 26, 2026 20:27

@karthikscale3karthikscale3 left a comment

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.

LGTM

@mitul-s
mitul-s merged commit 8393716 into mainJun 26, 2026
59 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

No backport to stable for 8393716 (AI decision).

The fix replaces a span-detail merge architecture (mergeSpanDetail/spanDetailMatchesSelection, the SidebarDataContextspanDetailData/onSpanSelect plumbing) with a new selection-driven state machine, but the modules it builds on and rewires — span-detail-merge.ts, sidebar-data-context.tsx, the new use-selected-span-detail.ts, and the test file — do not exist on stable (which still uses a simpler spanDetailData ?? data display path). Because the change depends on main-only trace-viewer/sidebar structure and cannot apply cleanly to the diverged stable architecture, it should not be backported.

To override, re-run the Backport to stable workflow manually via workflow_dispatch and paste this commit SHA into the ref input:

8393716687c77ec46c724cd48335aa7ea9176c8b

@github-actionsgithub-actionsBot mentioned this pull request Jun 26, 2026
Sign up for freeto 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.

3 participants

@mitul-s@karthikscale3@cursoragent
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Fix trace detail panel Input/Output flicker (selection-driven state machine) by mitul-s · Pull Request #2637 · vercel/workflow · GitHub
Skip to content

Fix trace detail panel Input/Output flicker (selection-driven state machine) - #2637

Merged
mitul-s merged 5 commits into
mainfrom
c+ms/detail-panel-io-flicker-refactor-2085
Jun 26, 2026
Merged

Fix trace detail panel Input/Output flicker (selection-driven state machine)#2637
mitul-s merged 5 commits into
mainfrom
c+ms/detail-panel-io-flicker-refactor-2085

Conversation

@mitul-s

@mitul-smitul-s commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Description

The run trace detail panel flickered its Input/Output sections every time you navigated between spans (steps/spans).

Root cause (structural). The panel rendered from two sources that update on different React commits:

  • The selected span (selectedSpan.data) updates synchronously on click, but the trace builder strips input/output, so it never carries I/O.
  • The fetched detail that carries I/O came from a lagging chain: EntityDetailPanel effect → onSpanSelect → page spanSelection state → useWorkflowResourceData → back down via SidebarDataContext.

So for 1–2 commits right after selecting a span, the panel showed the new span with isLoading === false while the fetched detail still belonged to the previous span (correctly rejected). With nothing to render and loading false, the Input/Output sections rendered nothing — they vanished, then reappeared a couple commits later. That disappear/reappear is the flicker. Every existing guard (spanDetailMatchesSelection, timestamp-pinning, the resolvedAttributes force-defaults, prevSelectionRef) was compensating for these signals being out of phase.

Fix. Collapse the out-of-phase signals into a single selection-driven state machine:

  • fetchSpanDetailResource (@workflow/web) — the run/step/hook/sleep fetch+hydrate core extracted out of useWorkflowResourceData as a plain async function (no React state). The hook now delegates to it.
  • useSelectedSpanDetail (@workflow/web-shared) — derives the selection synchronously, fetches its detail directly (with a request-token to drop stale/out-of-order responses), and returns a { status, displayData, error } view-model. status is a pure function of (selection, fetched detail) via deriveSpanDetailView, so it stays in phase with the selection on every render and the panel is loading from the first frame after a new span is picked until its matching detail arrives.

This is a net simplification: it removes the duplicate spanSelection state, the onSpanSelect notify-effect, four SidebarDataContext fields (spanDetailData/spanDetailError/spanDetailLoading/onSpanSelect, now a single injected fetchSpanDetail), and the scattered match/merge plumbing. The trace viewer's inbound contract shrinks to one fetchSpanDetail capability rather than mirroring transient async state back out to the host.

A follow-up cleanup of the AttributePanel Input/Output loading placeholders is split into a separate stacked PR (#2642) to keep this one focused on the flicker fix.

How did you test your changes?

  • Unit tests (packages/web-shared/test/span-detail-merge.test.ts) for the pure deriver deriveSpanDetailView — idle / loading / ready / error, stale-detail rejection (the exact race that caused the vanish), hooks ready inline — plus resourceNeedsFetchedDetail.
  • Existing useWorkflowResourceData tests still pass unchanged (the hook now delegates to the extracted fetchSpanDetailResource).
  • @workflow/web-shared builds (tsc) cleanly; tsc --noEmit on @workflow/web is clean for all touched files; Biome reports zero errors on the changed files; full web-shared vitest suite is green (the only failures are the pre-existing zstd-decoder tests, which need a newer Node than this CI runner).
  • Manual browser verification (navigating spans and rapid j/k in the run detail view to watch Input/Output stay stable) still needs an interactive browser session against a backend with runs — flagging for a reviewer to confirm visually.

PR Checklist - Required to merge

  • 📦 pnpm changeset was run to create a changelog for this PR (@workflow/web-shared + @workflow/web, patch)
  • 🔒 DCO sign-off passes — commits are not yet signed off (I avoided amending already-made commits); needs --signoff
  • 📝 Ping @vercel/workflow in a comment once the PR is ready, and the above checklist is complete
Open in WebOpen in Cursor

Extract the run/step/hook/sleep fetch+hydrate core out of useWorkflowResourceData
into a plain async fetchSpanDetailResource (no React state), and add a
selection-driven state machine in web-shared:
- deriveSpanDetailView / resourceNeedsFetchedDetail: pure view-model deriver
whose status (idle/loading/ready/error) is a function of (selection, fetched
detail), so it can never lag the selection.
- useSelectedSpanDetail: fetches a selected span's detail directly with a
request-token to drop stale/out-of-order responses.
Replace the cross-package selection round-trip (EntityDetailPanel useEffect ->
onSpanSelect -> page spanSelection state -> useWorkflowResourceData -> context)
with a single injected fetchSpanDetail capability:
- EntityDetailPanel consumes useSelectedSpanDetail; its loading state now stays
in phase with the selected span, so Input/Output no longer vanish and pop back
in while navigating.
- SidebarDataContext drops spanDetailData/Loading/Error + onSpanSelect for a
single fetchSpanDetail; RunDetailView injects it and drops the duplicate
spanSelection state.
- WorkflowTraceViewer / RunTraceView take fetchSpanDetail too.
Cover deriveSpanDetailView (idle/loading/ready/error, stale-detail rejection,
hooks ready inline) and resourceNeedsFetchedDetail.
@changeset-bot

changeset-botBot commented Jun 25, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 450df62

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 16 packages
NameType
@workflow/web-sharedPatch
@workflow/webPatch
@workflow/cliPatch
@workflow/nitroPatch
workflowPatch
@workflow/world-testingPatch
@workflow/nuxtPatch
@workflow/corePatch
@workflow/buildersPatch
@workflow/nextPatch
@workflow/vitestPatch
@workflow/astroPatch
@workflow/nestPatch
@workflow/rollupPatch
@workflow/sveltekitPatch
@workflow/vitePatch

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

@vercel

vercelBot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

@mitul-s
mitul-s marked this pull request as ready for review June 26, 2026 20:27
@mitul-s
mitul-s requested review from a team and ijjk as code ownersJune 26, 2026 20:27

@karthikscale3karthikscale3 left a comment

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.

LGTM

@mitul-s
mitul-s merged commit 8393716 into mainJun 26, 2026
59 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

No backport to stable for 8393716 (AI decision).

The fix replaces a span-detail merge architecture (mergeSpanDetail/spanDetailMatchesSelection, the SidebarDataContextspanDetailData/onSpanSelect plumbing) with a new selection-driven state machine, but the modules it builds on and rewires — span-detail-merge.ts, sidebar-data-context.tsx, the new use-selected-span-detail.ts, and the test file — do not exist on stable (which still uses a simpler spanDetailData ?? data display path). Because the change depends on main-only trace-viewer/sidebar structure and cannot apply cleanly to the diverged stable architecture, it should not be backported.

To override, re-run the Backport to stable workflow manually via workflow_dispatch and paste this commit SHA into the ref input:

8393716687c77ec46c724cd48335aa7ea9176c8b

@github-actionsgithub-actionsBot mentioned this pull request Jun 26, 2026
Sign up for freeto 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.

3 participants

@mitul-s@karthikscale3@cursoragent
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Fix trace detail panel Input/Output flicker (selection-driven state machine) by mitul-s · Pull Request #2637 · vercel/workflow · GitHub
Skip to content

Fix trace detail panel Input/Output flicker (selection-driven state machine) - #2637

Merged
mitul-s merged 5 commits into
mainfrom
c+ms/detail-panel-io-flicker-refactor-2085
Jun 26, 2026
Merged

Fix trace detail panel Input/Output flicker (selection-driven state machine)#2637
mitul-s merged 5 commits into
mainfrom
c+ms/detail-panel-io-flicker-refactor-2085

Conversation

@mitul-s

@mitul-smitul-s commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Description

The run trace detail panel flickered its Input/Output sections every time you navigated between spans (steps/spans).

Root cause (structural). The panel rendered from two sources that update on different React commits:

  • The selected span (selectedSpan.data) updates synchronously on click, but the trace builder strips input/output, so it never carries I/O.
  • The fetched detail that carries I/O came from a lagging chain: EntityDetailPanel effect → onSpanSelect → page spanSelection state → useWorkflowResourceData → back down via SidebarDataContext.

So for 1–2 commits right after selecting a span, the panel showed the new span with isLoading === false while the fetched detail still belonged to the previous span (correctly rejected). With nothing to render and loading false, the Input/Output sections rendered nothing — they vanished, then reappeared a couple commits later. That disappear/reappear is the flicker. Every existing guard (spanDetailMatchesSelection, timestamp-pinning, the resolvedAttributes force-defaults, prevSelectionRef) was compensating for these signals being out of phase.

Fix. Collapse the out-of-phase signals into a single selection-driven state machine:

  • fetchSpanDetailResource (@workflow/web) — the run/step/hook/sleep fetch+hydrate core extracted out of useWorkflowResourceData as a plain async function (no React state). The hook now delegates to it.
  • useSelectedSpanDetail (@workflow/web-shared) — derives the selection synchronously, fetches its detail directly (with a request-token to drop stale/out-of-order responses), and returns a { status, displayData, error } view-model. status is a pure function of (selection, fetched detail) via deriveSpanDetailView, so it stays in phase with the selection on every render and the panel is loading from the first frame after a new span is picked until its matching detail arrives.

This is a net simplification: it removes the duplicate spanSelection state, the onSpanSelect notify-effect, four SidebarDataContext fields (spanDetailData/spanDetailError/spanDetailLoading/onSpanSelect, now a single injected fetchSpanDetail), and the scattered match/merge plumbing. The trace viewer's inbound contract shrinks to one fetchSpanDetail capability rather than mirroring transient async state back out to the host.

A follow-up cleanup of the AttributePanel Input/Output loading placeholders is split into a separate stacked PR (#2642) to keep this one focused on the flicker fix.

How did you test your changes?

  • Unit tests (packages/web-shared/test/span-detail-merge.test.ts) for the pure deriver deriveSpanDetailView — idle / loading / ready / error, stale-detail rejection (the exact race that caused the vanish), hooks ready inline — plus resourceNeedsFetchedDetail.
  • Existing useWorkflowResourceData tests still pass unchanged (the hook now delegates to the extracted fetchSpanDetailResource).
  • @workflow/web-shared builds (tsc) cleanly; tsc --noEmit on @workflow/web is clean for all touched files; Biome reports zero errors on the changed files; full web-shared vitest suite is green (the only failures are the pre-existing zstd-decoder tests, which need a newer Node than this CI runner).
  • Manual browser verification (navigating spans and rapid j/k in the run detail view to watch Input/Output stay stable) still needs an interactive browser session against a backend with runs — flagging for a reviewer to confirm visually.

PR Checklist - Required to merge

  • 📦 pnpm changeset was run to create a changelog for this PR (@workflow/web-shared + @workflow/web, patch)
  • 🔒 DCO sign-off passes — commits are not yet signed off (I avoided amending already-made commits); needs --signoff
  • 📝 Ping @vercel/workflow in a comment once the PR is ready, and the above checklist is complete
Open in WebOpen in Cursor

Extract the run/step/hook/sleep fetch+hydrate core out of useWorkflowResourceData
into a plain async fetchSpanDetailResource (no React state), and add a
selection-driven state machine in web-shared:
- deriveSpanDetailView / resourceNeedsFetchedDetail: pure view-model deriver
whose status (idle/loading/ready/error) is a function of (selection, fetched
detail), so it can never lag the selection.
- useSelectedSpanDetail: fetches a selected span's detail directly with a
request-token to drop stale/out-of-order responses.
Replace the cross-package selection round-trip (EntityDetailPanel useEffect ->
onSpanSelect -> page spanSelection state -> useWorkflowResourceData -> context)
with a single injected fetchSpanDetail capability:
- EntityDetailPanel consumes useSelectedSpanDetail; its loading state now stays
in phase with the selected span, so Input/Output no longer vanish and pop back
in while navigating.
- SidebarDataContext drops spanDetailData/Loading/Error + onSpanSelect for a
single fetchSpanDetail; RunDetailView injects it and drops the duplicate
spanSelection state.
- WorkflowTraceViewer / RunTraceView take fetchSpanDetail too.
Cover deriveSpanDetailView (idle/loading/ready/error, stale-detail rejection,
hooks ready inline) and resourceNeedsFetchedDetail.
@changeset-bot

changeset-botBot commented Jun 25, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 450df62

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 16 packages
NameType
@workflow/web-sharedPatch
@workflow/webPatch
@workflow/cliPatch
@workflow/nitroPatch
workflowPatch
@workflow/world-testingPatch
@workflow/nuxtPatch
@workflow/corePatch
@workflow/buildersPatch
@workflow/nextPatch
@workflow/vitestPatch
@workflow/astroPatch
@workflow/nestPatch
@workflow/rollupPatch
@workflow/sveltekitPatch
@workflow/vitePatch

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

@vercel

vercelBot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

@mitul-s
mitul-s marked this pull request as ready for review June 26, 2026 20:27
@mitul-s
mitul-s requested review from a team and ijjk as code ownersJune 26, 2026 20:27

@karthikscale3karthikscale3 left a comment

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.

LGTM

@mitul-s
mitul-s merged commit 8393716 into mainJun 26, 2026
59 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

No backport to stable for 8393716 (AI decision).

The fix replaces a span-detail merge architecture (mergeSpanDetail/spanDetailMatchesSelection, the SidebarDataContextspanDetailData/onSpanSelect plumbing) with a new selection-driven state machine, but the modules it builds on and rewires — span-detail-merge.ts, sidebar-data-context.tsx, the new use-selected-span-detail.ts, and the test file — do not exist on stable (which still uses a simpler spanDetailData ?? data display path). Because the change depends on main-only trace-viewer/sidebar structure and cannot apply cleanly to the diverged stable architecture, it should not be backported.

To override, re-run the Backport to stable workflow manually via workflow_dispatch and paste this commit SHA into the ref input:

8393716687c77ec46c724cd48335aa7ea9176c8b

@github-actionsgithub-actionsBot mentioned this pull request Jun 26, 2026
Sign up for freeto 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.

3 participants

@mitul-s@karthikscale3@cursoragent
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Fix trace detail panel Input/Output flicker (selection-driven state machine) by mitul-s · Pull Request #2637 · vercel/workflow · GitHub
Skip to content

Fix trace detail panel Input/Output flicker (selection-driven state machine) - #2637

Merged
mitul-s merged 5 commits into
mainfrom
c+ms/detail-panel-io-flicker-refactor-2085
Jun 26, 2026
Merged

Fix trace detail panel Input/Output flicker (selection-driven state machine)#2637
mitul-s merged 5 commits into
mainfrom
c+ms/detail-panel-io-flicker-refactor-2085

Conversation

@mitul-s

@mitul-smitul-s commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Description

The run trace detail panel flickered its Input/Output sections every time you navigated between spans (steps/spans).

Root cause (structural). The panel rendered from two sources that update on different React commits:

  • The selected span (selectedSpan.data) updates synchronously on click, but the trace builder strips input/output, so it never carries I/O.
  • The fetched detail that carries I/O came from a lagging chain: EntityDetailPanel effect → onSpanSelect → page spanSelection state → useWorkflowResourceData → back down via SidebarDataContext.

So for 1–2 commits right after selecting a span, the panel showed the new span with isLoading === false while the fetched detail still belonged to the previous span (correctly rejected). With nothing to render and loading false, the Input/Output sections rendered nothing — they vanished, then reappeared a couple commits later. That disappear/reappear is the flicker. Every existing guard (spanDetailMatchesSelection, timestamp-pinning, the resolvedAttributes force-defaults, prevSelectionRef) was compensating for these signals being out of phase.

Fix. Collapse the out-of-phase signals into a single selection-driven state machine:

  • fetchSpanDetailResource (@workflow/web) — the run/step/hook/sleep fetch+hydrate core extracted out of useWorkflowResourceData as a plain async function (no React state). The hook now delegates to it.
  • useSelectedSpanDetail (@workflow/web-shared) — derives the selection synchronously, fetches its detail directly (with a request-token to drop stale/out-of-order responses), and returns a { status, displayData, error } view-model. status is a pure function of (selection, fetched detail) via deriveSpanDetailView, so it stays in phase with the selection on every render and the panel is loading from the first frame after a new span is picked until its matching detail arrives.

This is a net simplification: it removes the duplicate spanSelection state, the onSpanSelect notify-effect, four SidebarDataContext fields (spanDetailData/spanDetailError/spanDetailLoading/onSpanSelect, now a single injected fetchSpanDetail), and the scattered match/merge plumbing. The trace viewer's inbound contract shrinks to one fetchSpanDetail capability rather than mirroring transient async state back out to the host.

A follow-up cleanup of the AttributePanel Input/Output loading placeholders is split into a separate stacked PR (#2642) to keep this one focused on the flicker fix.

How did you test your changes?

  • Unit tests (packages/web-shared/test/span-detail-merge.test.ts) for the pure deriver deriveSpanDetailView — idle / loading / ready / error, stale-detail rejection (the exact race that caused the vanish), hooks ready inline — plus resourceNeedsFetchedDetail.
  • Existing useWorkflowResourceData tests still pass unchanged (the hook now delegates to the extracted fetchSpanDetailResource).
  • @workflow/web-shared builds (tsc) cleanly; tsc --noEmit on @workflow/web is clean for all touched files; Biome reports zero errors on the changed files; full web-shared vitest suite is green (the only failures are the pre-existing zstd-decoder tests, which need a newer Node than this CI runner).
  • Manual browser verification (navigating spans and rapid j/k in the run detail view to watch Input/Output stay stable) still needs an interactive browser session against a backend with runs — flagging for a reviewer to confirm visually.

PR Checklist - Required to merge

  • 📦 pnpm changeset was run to create a changelog for this PR (@workflow/web-shared + @workflow/web, patch)
  • 🔒 DCO sign-off passes — commits are not yet signed off (I avoided amending already-made commits); needs --signoff
  • 📝 Ping @vercel/workflow in a comment once the PR is ready, and the above checklist is complete
Open in WebOpen in Cursor

Extract the run/step/hook/sleep fetch+hydrate core out of useWorkflowResourceData
into a plain async fetchSpanDetailResource (no React state), and add a
selection-driven state machine in web-shared:
- deriveSpanDetailView / resourceNeedsFetchedDetail: pure view-model deriver
whose status (idle/loading/ready/error) is a function of (selection, fetched
detail), so it can never lag the selection.
- useSelectedSpanDetail: fetches a selected span's detail directly with a
request-token to drop stale/out-of-order responses.
Replace the cross-package selection round-trip (EntityDetailPanel useEffect ->
onSpanSelect -> page spanSelection state -> useWorkflowResourceData -> context)
with a single injected fetchSpanDetail capability:
- EntityDetailPanel consumes useSelectedSpanDetail; its loading state now stays
in phase with the selected span, so Input/Output no longer vanish and pop back
in while navigating.
- SidebarDataContext drops spanDetailData/Loading/Error + onSpanSelect for a
single fetchSpanDetail; RunDetailView injects it and drops the duplicate
spanSelection state.
- WorkflowTraceViewer / RunTraceView take fetchSpanDetail too.
Cover deriveSpanDetailView (idle/loading/ready/error, stale-detail rejection,
hooks ready inline) and resourceNeedsFetchedDetail.
@changeset-bot

changeset-botBot commented Jun 25, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 450df62

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 16 packages
NameType
@workflow/web-sharedPatch
@workflow/webPatch
@workflow/cliPatch
@workflow/nitroPatch
workflowPatch
@workflow/world-testingPatch
@workflow/nuxtPatch
@workflow/corePatch
@workflow/buildersPatch
@workflow/nextPatch
@workflow/vitestPatch
@workflow/astroPatch
@workflow/nestPatch
@workflow/rollupPatch
@workflow/sveltekitPatch
@workflow/vitePatch

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

@vercel

vercelBot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

@mitul-s
mitul-s marked this pull request as ready for review June 26, 2026 20:27
@mitul-s
mitul-s requested review from a team and ijjk as code ownersJune 26, 2026 20:27

@karthikscale3karthikscale3 left a comment

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.

LGTM

@mitul-s
mitul-s merged commit 8393716 into mainJun 26, 2026
59 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

No backport to stable for 8393716 (AI decision).

The fix replaces a span-detail merge architecture (mergeSpanDetail/spanDetailMatchesSelection, the SidebarDataContextspanDetailData/onSpanSelect plumbing) with a new selection-driven state machine, but the modules it builds on and rewires — span-detail-merge.ts, sidebar-data-context.tsx, the new use-selected-span-detail.ts, and the test file — do not exist on stable (which still uses a simpler spanDetailData ?? data display path). Because the change depends on main-only trace-viewer/sidebar structure and cannot apply cleanly to the diverged stable architecture, it should not be backported.

To override, re-run the Backport to stable workflow manually via workflow_dispatch and paste this commit SHA into the ref input:

8393716687c77ec46c724cd48335aa7ea9176c8b

@github-actionsgithub-actionsBot mentioned this pull request Jun 26, 2026
Sign up for freeto 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.

3 participants

@mitul-s@karthikscale3@cursoragent
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Fix trace detail panel Input/Output flicker (selection-driven state machine) by mitul-s · Pull Request #2637 · vercel/workflow · GitHub
Skip to content

Fix trace detail panel Input/Output flicker (selection-driven state machine) - #2637

Merged
mitul-s merged 5 commits into
mainfrom
c+ms/detail-panel-io-flicker-refactor-2085
Jun 26, 2026
Merged

Fix trace detail panel Input/Output flicker (selection-driven state machine)#2637
mitul-s merged 5 commits into
mainfrom
c+ms/detail-panel-io-flicker-refactor-2085

Conversation

@mitul-s

@mitul-smitul-s commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Description

The run trace detail panel flickered its Input/Output sections every time you navigated between spans (steps/spans).

Root cause (structural). The panel rendered from two sources that update on different React commits:

  • The selected span (selectedSpan.data) updates synchronously on click, but the trace builder strips input/output, so it never carries I/O.
  • The fetched detail that carries I/O came from a lagging chain: EntityDetailPanel effect → onSpanSelect → page spanSelection state → useWorkflowResourceData → back down via SidebarDataContext.

So for 1–2 commits right after selecting a span, the panel showed the new span with isLoading === false while the fetched detail still belonged to the previous span (correctly rejected). With nothing to render and loading false, the Input/Output sections rendered nothing — they vanished, then reappeared a couple commits later. That disappear/reappear is the flicker. Every existing guard (spanDetailMatchesSelection, timestamp-pinning, the resolvedAttributes force-defaults, prevSelectionRef) was compensating for these signals being out of phase.

Fix. Collapse the out-of-phase signals into a single selection-driven state machine:

  • fetchSpanDetailResource (@workflow/web) — the run/step/hook/sleep fetch+hydrate core extracted out of useWorkflowResourceData as a plain async function (no React state). The hook now delegates to it.
  • useSelectedSpanDetail (@workflow/web-shared) — derives the selection synchronously, fetches its detail directly (with a request-token to drop stale/out-of-order responses), and returns a { status, displayData, error } view-model. status is a pure function of (selection, fetched detail) via deriveSpanDetailView, so it stays in phase with the selection on every render and the panel is loading from the first frame after a new span is picked until its matching detail arrives.

This is a net simplification: it removes the duplicate spanSelection state, the onSpanSelect notify-effect, four SidebarDataContext fields (spanDetailData/spanDetailError/spanDetailLoading/onSpanSelect, now a single injected fetchSpanDetail), and the scattered match/merge plumbing. The trace viewer's inbound contract shrinks to one fetchSpanDetail capability rather than mirroring transient async state back out to the host.

A follow-up cleanup of the AttributePanel Input/Output loading placeholders is split into a separate stacked PR (#2642) to keep this one focused on the flicker fix.

How did you test your changes?

  • Unit tests (packages/web-shared/test/span-detail-merge.test.ts) for the pure deriver deriveSpanDetailView — idle / loading / ready / error, stale-detail rejection (the exact race that caused the vanish), hooks ready inline — plus resourceNeedsFetchedDetail.
  • Existing useWorkflowResourceData tests still pass unchanged (the hook now delegates to the extracted fetchSpanDetailResource).
  • @workflow/web-shared builds (tsc) cleanly; tsc --noEmit on @workflow/web is clean for all touched files; Biome reports zero errors on the changed files; full web-shared vitest suite is green (the only failures are the pre-existing zstd-decoder tests, which need a newer Node than this CI runner).
  • Manual browser verification (navigating spans and rapid j/k in the run detail view to watch Input/Output stay stable) still needs an interactive browser session against a backend with runs — flagging for a reviewer to confirm visually.

PR Checklist - Required to merge

  • 📦 pnpm changeset was run to create a changelog for this PR (@workflow/web-shared + @workflow/web, patch)
  • 🔒 DCO sign-off passes — commits are not yet signed off (I avoided amending already-made commits); needs --signoff
  • 📝 Ping @vercel/workflow in a comment once the PR is ready, and the above checklist is complete
Open in WebOpen in Cursor

Extract the run/step/hook/sleep fetch+hydrate core out of useWorkflowResourceData
into a plain async fetchSpanDetailResource (no React state), and add a
selection-driven state machine in web-shared:
- deriveSpanDetailView / resourceNeedsFetchedDetail: pure view-model deriver
whose status (idle/loading/ready/error) is a function of (selection, fetched
detail), so it can never lag the selection.
- useSelectedSpanDetail: fetches a selected span's detail directly with a
request-token to drop stale/out-of-order responses.
Replace the cross-package selection round-trip (EntityDetailPanel useEffect ->
onSpanSelect -> page spanSelection state -> useWorkflowResourceData -> context)
with a single injected fetchSpanDetail capability:
- EntityDetailPanel consumes useSelectedSpanDetail; its loading state now stays
in phase with the selected span, so Input/Output no longer vanish and pop back
in while navigating.
- SidebarDataContext drops spanDetailData/Loading/Error + onSpanSelect for a
single fetchSpanDetail; RunDetailView injects it and drops the duplicate
spanSelection state.
- WorkflowTraceViewer / RunTraceView take fetchSpanDetail too.
Cover deriveSpanDetailView (idle/loading/ready/error, stale-detail rejection,
hooks ready inline) and resourceNeedsFetchedDetail.
@changeset-bot

changeset-botBot commented Jun 25, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 450df62

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 16 packages
NameType
@workflow/web-sharedPatch
@workflow/webPatch
@workflow/cliPatch
@workflow/nitroPatch
workflowPatch
@workflow/world-testingPatch
@workflow/nuxtPatch
@workflow/corePatch
@workflow/buildersPatch
@workflow/nextPatch
@workflow/vitestPatch
@workflow/astroPatch
@workflow/nestPatch
@workflow/rollupPatch
@workflow/sveltekitPatch
@workflow/vitePatch

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

@vercel

vercelBot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

@mitul-s
mitul-s marked this pull request as ready for review June 26, 2026 20:27
@mitul-s
mitul-s requested review from a team and ijjk as code ownersJune 26, 2026 20:27

@karthikscale3karthikscale3 left a comment

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.

LGTM

@mitul-s
mitul-s merged commit 8393716 into mainJun 26, 2026
59 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

No backport to stable for 8393716 (AI decision).

The fix replaces a span-detail merge architecture (mergeSpanDetail/spanDetailMatchesSelection, the SidebarDataContextspanDetailData/onSpanSelect plumbing) with a new selection-driven state machine, but the modules it builds on and rewires — span-detail-merge.ts, sidebar-data-context.tsx, the new use-selected-span-detail.ts, and the test file — do not exist on stable (which still uses a simpler spanDetailData ?? data display path). Because the change depends on main-only trace-viewer/sidebar structure and cannot apply cleanly to the diverged stable architecture, it should not be backported.

To override, re-run the Backport to stable workflow manually via workflow_dispatch and paste this commit SHA into the ref input:

8393716687c77ec46c724cd48335aa7ea9176c8b

@github-actionsgithub-actionsBot mentioned this pull request Jun 26, 2026
Sign up for freeto 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.

3 participants

@mitul-s@karthikscale3@cursoragent
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Fix trace detail panel Input/Output flicker (selection-driven state machine) by mitul-s · Pull Request #2637 · vercel/workflow · GitHub
Skip to content

Fix trace detail panel Input/Output flicker (selection-driven state machine) - #2637

Merged
mitul-s merged 5 commits into
mainfrom
c+ms/detail-panel-io-flicker-refactor-2085
Jun 26, 2026
Merged

Fix trace detail panel Input/Output flicker (selection-driven state machine)#2637
mitul-s merged 5 commits into
mainfrom
c+ms/detail-panel-io-flicker-refactor-2085

Conversation

@mitul-s

@mitul-smitul-s commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Description

The run trace detail panel flickered its Input/Output sections every time you navigated between spans (steps/spans).

Root cause (structural). The panel rendered from two sources that update on different React commits:

  • The selected span (selectedSpan.data) updates synchronously on click, but the trace builder strips input/output, so it never carries I/O.
  • The fetched detail that carries I/O came from a lagging chain: EntityDetailPanel effect → onSpanSelect → page spanSelection state → useWorkflowResourceData → back down via SidebarDataContext.

So for 1–2 commits right after selecting a span, the panel showed the new span with isLoading === false while the fetched detail still belonged to the previous span (correctly rejected). With nothing to render and loading false, the Input/Output sections rendered nothing — they vanished, then reappeared a couple commits later. That disappear/reappear is the flicker. Every existing guard (spanDetailMatchesSelection, timestamp-pinning, the resolvedAttributes force-defaults, prevSelectionRef) was compensating for these signals being out of phase.

Fix. Collapse the out-of-phase signals into a single selection-driven state machine:

  • fetchSpanDetailResource (@workflow/web) — the run/step/hook/sleep fetch+hydrate core extracted out of useWorkflowResourceData as a plain async function (no React state). The hook now delegates to it.
  • useSelectedSpanDetail (@workflow/web-shared) — derives the selection synchronously, fetches its detail directly (with a request-token to drop stale/out-of-order responses), and returns a { status, displayData, error } view-model. status is a pure function of (selection, fetched detail) via deriveSpanDetailView, so it stays in phase with the selection on every render and the panel is loading from the first frame after a new span is picked until its matching detail arrives.

This is a net simplification: it removes the duplicate spanSelection state, the onSpanSelect notify-effect, four SidebarDataContext fields (spanDetailData/spanDetailError/spanDetailLoading/onSpanSelect, now a single injected fetchSpanDetail), and the scattered match/merge plumbing. The trace viewer's inbound contract shrinks to one fetchSpanDetail capability rather than mirroring transient async state back out to the host.

A follow-up cleanup of the AttributePanel Input/Output loading placeholders is split into a separate stacked PR (#2642) to keep this one focused on the flicker fix.

How did you test your changes?

  • Unit tests (packages/web-shared/test/span-detail-merge.test.ts) for the pure deriver deriveSpanDetailView — idle / loading / ready / error, stale-detail rejection (the exact race that caused the vanish), hooks ready inline — plus resourceNeedsFetchedDetail.
  • Existing useWorkflowResourceData tests still pass unchanged (the hook now delegates to the extracted fetchSpanDetailResource).
  • @workflow/web-shared builds (tsc) cleanly; tsc --noEmit on @workflow/web is clean for all touched files; Biome reports zero errors on the changed files; full web-shared vitest suite is green (the only failures are the pre-existing zstd-decoder tests, which need a newer Node than this CI runner).
  • Manual browser verification (navigating spans and rapid j/k in the run detail view to watch Input/Output stay stable) still needs an interactive browser session against a backend with runs — flagging for a reviewer to confirm visually.

PR Checklist - Required to merge

  • 📦 pnpm changeset was run to create a changelog for this PR (@workflow/web-shared + @workflow/web, patch)
  • 🔒 DCO sign-off passes — commits are not yet signed off (I avoided amending already-made commits); needs --signoff
  • 📝 Ping @vercel/workflow in a comment once the PR is ready, and the above checklist is complete
Open in WebOpen in Cursor

Extract the run/step/hook/sleep fetch+hydrate core out of useWorkflowResourceData
into a plain async fetchSpanDetailResource (no React state), and add a
selection-driven state machine in web-shared:
- deriveSpanDetailView / resourceNeedsFetchedDetail: pure view-model deriver
whose status (idle/loading/ready/error) is a function of (selection, fetched
detail), so it can never lag the selection.
- useSelectedSpanDetail: fetches a selected span's detail directly with a
request-token to drop stale/out-of-order responses.
Replace the cross-package selection round-trip (EntityDetailPanel useEffect ->
onSpanSelect -> page spanSelection state -> useWorkflowResourceData -> context)
with a single injected fetchSpanDetail capability:
- EntityDetailPanel consumes useSelectedSpanDetail; its loading state now stays
in phase with the selected span, so Input/Output no longer vanish and pop back
in while navigating.
- SidebarDataContext drops spanDetailData/Loading/Error + onSpanSelect for a
single fetchSpanDetail; RunDetailView injects it and drops the duplicate
spanSelection state.
- WorkflowTraceViewer / RunTraceView take fetchSpanDetail too.
Cover deriveSpanDetailView (idle/loading/ready/error, stale-detail rejection,
hooks ready inline) and resourceNeedsFetchedDetail.
@changeset-bot

changeset-botBot commented Jun 25, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 450df62

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 16 packages
NameType
@workflow/web-sharedPatch
@workflow/webPatch
@workflow/cliPatch
@workflow/nitroPatch
workflowPatch
@workflow/world-testingPatch
@workflow/nuxtPatch
@workflow/corePatch
@workflow/buildersPatch
@workflow/nextPatch
@workflow/vitestPatch
@workflow/astroPatch
@workflow/nestPatch
@workflow/rollupPatch
@workflow/sveltekitPatch
@workflow/vitePatch

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

@vercel

vercelBot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

@mitul-s
mitul-s marked this pull request as ready for review June 26, 2026 20:27
@mitul-s
mitul-s requested review from a team and ijjk as code ownersJune 26, 2026 20:27

@karthikscale3karthikscale3 left a comment

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.

LGTM

@mitul-s
mitul-s merged commit 8393716 into mainJun 26, 2026
59 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

No backport to stable for 8393716 (AI decision).

The fix replaces a span-detail merge architecture (mergeSpanDetail/spanDetailMatchesSelection, the SidebarDataContextspanDetailData/onSpanSelect plumbing) with a new selection-driven state machine, but the modules it builds on and rewires — span-detail-merge.ts, sidebar-data-context.tsx, the new use-selected-span-detail.ts, and the test file — do not exist on stable (which still uses a simpler spanDetailData ?? data display path). Because the change depends on main-only trace-viewer/sidebar structure and cannot apply cleanly to the diverged stable architecture, it should not be backported.

To override, re-run the Backport to stable workflow manually via workflow_dispatch and paste this commit SHA into the ref input:

8393716687c77ec46c724cd48335aa7ea9176c8b

@github-actionsgithub-actionsBot mentioned this pull request Jun 26, 2026
Sign up for freeto 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.

3 participants

@mitul-s@karthikscale3@cursoragent