Skip to content

Decode UTF-8 stream chunks - #1852

Merged
karthikscale3 merged 9 commits into
mainfrom
codex/decode-typed-array-stream-chunks
Apr 29, 2026
Merged

Decode UTF-8 stream chunks#1852
karthikscale3 merged 9 commits into
mainfrom
codex/decode-typed-array-stream-chunks

Conversation

@karthikscale3

@karthikscale3karthikscale3 commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

What changed

  • Added shared formatArrayBufferViewForDisplay + summarizeArrayBufferView helpers in @workflow/web-shared (src/lib/stream-display.ts). They UTF-8 decode typed-array values via TextDecoder({ fatal: true }) and fall back to a compact Uint8Array(N) [..] summary for binary data.
  • DataInspector (src/components/ui/data-inspector.tsx) now collapses any ArrayBufferView (other than DataView) into a non-expandable BytesDisplay opaque ref via collapseRefs, then renders it with a DecodedBytesChunk that shows decoded text by default and a Decoded / Bytes toggle to inspect the summarized raw value. Decoded text that parses as JSON is re-rendered through a nested DataInspector.
  • stream-viewer.tsx no longer formats stream chunks itself — chunks flow as raw hydrated values straight into DataInspector, so Events tab payloads, Trace detail fields, and stream chunks all share the same typed-array rendering path.
  • packages/web/app/lib/hooks/use-stream-reader.ts is dramatically simplified (95 lines removed): it no longer pre-formats chunks, it just keeps them hydrated for the inspector.
  • Decoded / Bytes toggle uses aria-pressed + aria-label toggle-button semantics (not role="tab", since the panel content lives above the buttons rather than as a sibling).
  • Public API in src/index.ts exposes only the DecodedStreamChunkSource type from this module; the rendering helpers are intentionally internal.
  • New regression tests in test/collapse-refs.test.ts (9 cases): top-level typed array, nested in plain object, array, Map values, Set entries, non-Uint8Array views (Int16Array), DataView exclusion, binary fallback summary, and primitive passthrough.

Why

AI agent workflow stream deltas (e.g. helloAgentWorkflow) arrive as Uint8Array values serialized through devalue. The previous viewer collapsed them into compact byte summaries like Uint8Array(87) [65, 73, ...], which made the actual text content unreadable. Decoded text alone would be lossy for binary payloads. Routing everything through DataInspector → collapseRefs keeps the readable default, makes the byte-like source explicit and inspectable, and gives Events / Trace / stream views consistent behavior.

Validation

  • pnpm --filter @workflow/web-shared test → 22 passed (22)
  • pnpm --filter @workflow/web-shared typecheck → clean
  • pnpm --filter @workflow/web-shared build → clean
  • pnpm exec biome check on touched files → only pre-existing complexity warnings on untouched code

Reference

@vercel

vercelBot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

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

ProjectDeploymentActionsUpdated (UTC)
example-nextjs-workflow-turbopackReadyReadyPreview, CommentApr 28, 2026 11:30pm
example-nextjs-workflow-webpackReadyReadyPreview, CommentApr 28, 2026 11:30pm
example-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-astro-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-express-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-fastify-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-hono-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-nitro-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-nuxt-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-sveltekit-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-vite-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workflow-docsReadyReadyPreview, Comment, Open in v0Apr 28, 2026 11:30pm
workflow-swc-playgroundReadyReadyPreview, CommentApr 28, 2026 11:30pm
workflow-webReadyReadyPreview, CommentApr 28, 2026 11:30pm

@changeset-bot

changeset-botBot commented Apr 24, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: addd614

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

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

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

@github-actions

github-actionsBot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production9890671056
✅ 💻 Local Development10660861152
✅ 📦 Local Production10660861152
✅ 🐘 Local Postgres10660861152
✅ 🪟 Windows960096
✅ 📋 Other270018288
Total455303434896

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro8907
✅ example8907
✅ express8907
✅ fastify8907
✅ hono8907
✅ nextjs-turbopack9402
✅ nextjs-webpack9402
✅ nitro8907
✅ nuxt8907
✅ sveltekit8907
✅ vite8907
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable9006
✅ express-stable9006
✅ fastify-stable9006
✅ hono-stable9006
✅ nextjs-turbopack-canary77019
✅ nextjs-turbopack-stable9600
✅ nextjs-webpack-canary77019
✅ nextjs-webpack-stable9600
✅ nitro-stable9006
✅ nuxt-stable9006
✅ sveltekit-stable9006
✅ vite-stable9006
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable9006
✅ express-stable9006
✅ fastify-stable9006
✅ hono-stable9006
✅ nextjs-turbopack-canary77019
✅ nextjs-turbopack-stable9600
✅ nextjs-webpack-canary77019
✅ nextjs-webpack-stable9600
✅ nitro-stable9006
✅ nuxt-stable9006
✅ sveltekit-stable9006
✅ vite-stable9006
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable9006
✅ express-stable9006
✅ fastify-stable9006
✅ hono-stable9006
✅ nextjs-turbopack-canary77019
✅ nextjs-turbopack-stable9600
✅ nextjs-webpack-canary77019
✅ nextjs-webpack-stable9600
✅ nitro-stable9006
✅ nuxt-stable9006
✅ sveltekit-stable9006
✅ vite-stable9006
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack9600
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable9006
✅ e2e-local-postgres-nest-stable9006
✅ e2e-local-prod-nest-stable9006

📋 View full workflow run

@github-actions

github-actionsBot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

📊 Benchmark Results

📈 Comparing against baseline from main branch. Green 🟢 = faster, Red 🔺 = slower.

workflow with no steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro0.041s (-3.9%)1.005s (~)0.964s101.00x
💻 LocalExpress0.044s (-1.1%)1.005s (~)0.961s101.06x
💻 LocalNext.js (Turbopack)0.048s1.005s0.957s101.16x
🐘 PostgresExpress0.054s (-6.0% 🟢)1.011s (~)0.956s101.32x
🐘 PostgresNitro0.055s (-42.4% 🟢)1.010s (-3.2%)0.955s101.32x
🐘 PostgresNext.js (Turbopack)0.057s1.011s0.953s101.38x
workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)1.116s2.006s0.890s101.00x
💻 LocalNitro1.129s (~)2.006s (~)0.877s101.01x
💻 LocalExpress1.130s (~)2.006s (~)0.877s101.01x
🐘 PostgresNext.js (Turbopack)1.141s2.010s0.869s101.02x
🐘 PostgresNitro1.143s (~)2.009s (~)0.867s101.02x
🐘 PostgresExpress1.148s (~)2.011s (~)0.862s101.03x
workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)10.794s11.025s0.231s31.00x
💻 LocalNitro10.946s (~)11.023s (~)0.078s31.01x
💻 LocalExpress10.952s (~)11.024s (~)0.071s31.01x
🐘 PostgresNitro10.957s (+0.8%)11.350s (+2.9%)0.393s31.02x
🐘 PostgresExpress10.961s (~)11.028s (~)0.067s31.02x
🐘 PostgresNext.js (Turbopack)11.002s11.359s0.357s31.02x
workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro14.507s (-0.6%)15.023s (~)0.517s41.00x
🐘 PostgresNext.js (Turbopack)14.508s15.023s0.514s41.00x
💻 LocalNext.js (Turbopack)14.596s15.029s0.434s41.01x
🐘 PostgresExpress14.621s (~)15.027s (~)0.406s41.01x
💻 LocalNitro14.983s (-0.5%)15.030s (-6.2% 🟢)0.047s41.03x
💻 LocalExpress14.999s (~)15.279s (+1.7%)0.280s41.03x
workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)13.770s14.020s0.251s71.00x
🐘 PostgresNitro13.792s (-1.3%)14.021s (-2.0%)0.229s71.00x
🐘 PostgresExpress14.142s (+1.0%)14.880s (+2.0%)0.739s71.03x
💻 LocalNext.js (Turbopack)15.743s16.031s0.288s61.14x
💻 LocalExpress16.535s (~)17.031s (~)0.496s61.20x
💻 LocalNitro16.756s (~)17.031s (~)0.275s61.22x
Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.225s2.010s0.785s151.00x
🐘 PostgresExpress1.255s (~)2.009s (~)0.754s151.02x
🐘 PostgresNitro1.278s (~)2.009s (~)0.731s151.04x
💻 LocalExpress1.513s (+1.6%)2.005s (~)0.493s151.24x
💻 LocalNext.js (Turbopack)1.541s2.006s0.465s151.26x
💻 LocalNitro1.545s (-5.3% 🟢)2.006s (-3.3%)0.460s151.26x
Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.325s (-1.1%)3.009s (~)0.684s101.00x
🐘 PostgresNext.js (Turbopack)2.355s3.007s0.653s101.01x
🐘 PostgresExpress2.365s (~)3.010s (~)0.645s101.02x
💻 LocalNext.js (Turbopack)2.878s3.342s0.464s91.24x
💻 LocalExpress2.970s (+0.6%)3.342s (-3.2%)0.372s91.28x
💻 LocalNitro3.135s (~)3.761s (-3.2%)0.626s81.35x
Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro3.466s (~)4.013s (~)0.547s81.00x
🐘 PostgresExpress3.511s (+0.7%)4.014s (~)0.503s81.01x
🐘 PostgresNext.js (Turbopack)3.622s4.014s0.392s81.05x
💻 LocalNext.js (Turbopack)7.191s7.765s0.575s42.07x
💻 LocalExpress7.682s (-7.9% 🟢)8.023s (-11.1% 🟢)0.341s42.22x
💻 LocalNitro8.748s (+4.8%)9.270s (+2.8%)0.523s42.52x
Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.233s2.008s0.775s151.00x
🐘 PostgresNitro1.262s (~)2.008s (~)0.746s151.02x
🐘 PostgresExpress1.269s (+0.9%)2.009s (~)0.740s151.03x
💻 LocalNext.js (Turbopack)1.522s2.006s0.484s151.23x
💻 LocalNitro1.541s (-17.4% 🟢)2.006s (-14.3% 🟢)0.465s151.25x
💻 LocalExpress1.545s (-18.4% 🟢)2.006s (-15.1% 🟢)0.461s151.25x
Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.318s (-0.9%)3.009s (~)0.691s101.00x
🐘 PostgresExpress2.354s (+0.5%)3.010s (~)0.656s101.02x
🐘 PostgresNext.js (Turbopack)2.432s3.009s0.577s101.05x
💻 LocalNext.js (Turbopack)2.815s3.310s0.495s101.21x
💻 LocalExpress2.839s (-9.4% 🟢)3.108s (-17.4% 🟢)0.269s101.22x
💻 LocalNitro3.042s (-0.8%)3.885s (~)0.843s81.31x
Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro3.475s (~)4.011s (~)0.536s81.00x
🐘 PostgresExpress3.493s (~)4.011s (~)0.517s81.01x
🐘 PostgresNext.js (Turbopack)3.616s4.011s0.395s81.04x
💻 LocalNext.js (Turbopack)7.662s8.270s0.607s42.21x
💻 LocalExpress7.892s (-10.3% 🟢)8.270s (-10.8% 🟢)0.378s42.27x
💻 LocalNitro8.903s (-2.6%)9.275s (-7.5% 🟢)0.372s42.56x
workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.778s1.024s0.246s591.00x
🐘 PostgresNitro0.806s (-1.8%)1.006s (~)0.200s601.04x
🐘 PostgresExpress0.837s (~)1.023s (~)0.186s591.08x
💻 LocalNext.js (Turbopack)0.865s1.039s0.174s581.11x
💻 LocalNitro1.000s (+2.0%)1.309s (+19.7% 🔺)0.309s461.29x
💻 LocalExpress1.015s (+3.1%)1.672s (+55.4% 🔺)0.657s361.31x
workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.914s (-0.7%)2.075s (-1.2%)0.161s441.00x
🐘 PostgresNext.js (Turbopack)1.925s2.149s0.225s421.01x
🐘 PostgresExpress1.968s (~)2.316s (+2.6%)0.348s391.03x
💻 LocalNext.js (Turbopack)2.737s3.008s0.271s301.43x
💻 LocalNitro3.017s (-0.6%)3.649s (-2.9%)0.632s251.58x
💻 LocalExpress3.020s (~)3.610s (+0.7%)0.589s251.58x
workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)3.864s4.076s0.212s301.00x
🐘 PostgresNitro3.866s (-5.8% 🟢)4.042s (-12.2% 🟢)0.176s301.00x
🐘 PostgresExpress4.204s (+5.4% 🔺)5.011s (+14.7% 🔺)0.807s241.09x
💻 LocalNext.js (Turbopack)8.536s9.017s0.481s142.21x
💻 LocalExpress8.969s (-2.6%)9.325s (-6.9% 🟢)0.355s132.32x
💻 LocalNitro9.222s (-0.8%)9.942s (-0.8%)0.720s132.39x
workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.247s1.007s0.760s601.00x
🐘 PostgresNitro0.284s (~)1.006s (~)0.722s601.15x
🐘 PostgresExpress0.294s (+4.1%)1.007s (~)0.713s601.19x
💻 LocalExpress0.562s (~)1.004s (~)0.442s602.27x
💻 LocalNext.js (Turbopack)0.575s1.004s0.429s602.33x
💻 LocalNitro0.580s (-4.1%)1.004s (-1.7%)0.425s602.34x
workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.488s1.007s0.519s901.00x
🐘 PostgresExpress0.488s (-4.3%)1.007s (~)0.519s901.00x
🐘 PostgresNitro0.496s (~)1.006s (~)0.510s901.02x
💻 LocalExpress2.378s (-5.4% 🟢)3.009s (~)0.631s304.88x
💻 LocalNitro2.571s (+1.3%)3.009s (~)0.438s305.27x
💻 LocalNext.js (Turbopack)2.576s3.009s0.433s305.28x
workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.792s (-3.3%)1.008s (-0.9%)0.217s1201.00x
🐘 PostgresNitro0.796s (+0.7%)1.007s (~)0.211s1201.01x
🐘 PostgresNext.js (Turbopack)0.798s1.015s0.217s1191.01x
💻 LocalNext.js (Turbopack)10.221s10.940s0.718s1112.91x
💻 LocalExpress10.431s (-6.8% 🟢)11.028s (-7.6% 🟢)0.597s1113.18x
💻 LocalNitro11.794s (+5.4% 🔺)12.327s (+5.7% 🔺)0.534s1014.90x
Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)0.183s1.003s0.010s1.016s0.834s101.00x
🐘 PostgresNext.js (Turbopack)0.190s1.000s0.001s1.009s0.819s101.04x
🐘 PostgresNitro0.203s (-0.8%)0.996s (~)0.001s (-33.3% 🟢)1.008s (~)0.805s101.11x
💻 LocalNitro0.208s (-2.6%)1.004s (~)0.012s (-1.6%)1.019s (~)0.810s101.14x
🐘 PostgresExpress0.227s (+10.5% 🔺)0.996s (~)0.001s (-12.5% 🟢)1.011s (~)0.784s101.24x
💻 LocalExpress0.248s (+24.5% 🔺)1.004s (~)0.020s (+67.8% 🔺)1.027s (+0.8%)0.779s101.36x
stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.619s (-0.7%)1.026s (+1.9%)0.004s (-5.8% 🟢)1.046s (+2.3%)0.427s581.00x
🐘 PostgresExpress0.627s (-0.5%)1.005s (~)0.012s (+205.3% 🔺)1.030s (+0.6%)0.403s591.01x
🐘 PostgresNext.js (Turbopack)0.640s1.044s0.004s1.058s0.418s571.03x
💻 LocalNext.js (Turbopack)0.696s1.012s0.011s1.026s0.331s591.12x
💻 LocalNitro0.747s (-10.9% 🟢)1.012s (~)0.010s (+8.2% 🔺)1.023s (-8.3% 🟢)0.276s591.21x
💻 LocalExpress0.763s (+0.8%)1.012s (-1.6%)0.010s (+1.4%)1.024s (-1.6%)0.260s591.23x
10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.926s1.199s0.000s1.208s0.282s511.00x
🐘 PostgresNitro0.955s (-1.4%)1.168s (-6.4% 🟢)0.000s (-52.9% 🟢)1.193s (-5.2% 🟢)0.238s511.03x
🐘 PostgresExpress0.967s (+0.6%)1.246s (-2.5%)0.000s (-52.1% 🟢)1.259s (-3.7%)0.292s481.04x
💻 LocalExpress1.214s (-0.9%)2.020s (~)0.000s (-10.0% 🟢)2.022s (~)0.808s301.31x
💻 LocalNitro1.233s (+0.8%)2.021s (~)0.000s (+333.3% 🔺)2.023s (~)0.790s301.33x
💻 LocalNext.js (Turbopack)1.313s2.022s0.000s2.025s0.712s301.42x
fan-out fan-in 10 streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.732s (-3.3%)2.139s (~)0.000s (-100.0% 🟢)2.152s (-1.0%)0.419s281.00x
🐘 PostgresNext.js (Turbopack)1.803s2.106s0.000s2.138s0.334s291.04x
🐘 PostgresExpress1.817s (+2.6%)2.261s (+3.8%)0.000s (+Infinity% 🔺)2.270s (+3.2%)0.453s271.05x
💻 LocalExpress3.438s (-0.9%)4.032s (~)0.001s (-33.3% 🟢)4.035s (~)0.597s151.98x
💻 LocalNitro3.503s (+3.4%)4.032s (~)0.001s (~)4.035s (~)0.532s152.02x
💻 LocalNext.js (Turbopack)3.861s4.235s0.000s4.239s0.378s152.23x

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalNext.js (Turbopack)15/21
🐘 PostgresNext.js (Turbopack)10/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres18/21
Next.js (Turbopack)🐘 Postgres17/21
Nitro🐘 Postgres18/21
Column Definitions
  • Workflow Time: Runtime reported by workflow (completedAt - createdAt) - primary metric
  • TTFB: Time to First Byte - time from workflow start until first stream byte received (stream benchmarks only)
  • Slurp: Time from first byte to complete stream consumption (stream benchmarks only)
  • Wall Time: Total testbench time (trigger workflow + poll for result)
  • Overhead: Testbench overhead (Wall Time - Workflow Time)
  • Samples: Number of benchmark iterations run
  • vs Fastest: How much slower compared to the fastest configuration for this benchmark

Worlds:

  • 💻 Local: In-memory filesystem world (local development)
  • 🐘 Postgres: PostgreSQL database world (local development)
  • ▲ Vercel: Vercel production/preview deployment
  • 🌐 Turso: Community world (local development)
  • 🌐 MongoDB: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Jazz: Community world (local development)

📋 View full workflow run

@VaguelySeriousVaguelySerious left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(AI) I'm not sure how https://github.com/vercel/front/pull/68191 is a mirror of this, but as long as both ends work, LGTM. I haven't tried it out locally, so I'd want to re-test once the live deployment is back online, or before release

Comment threadpackages/web-shared/src/index.ts Outdated
Comment threadpackages/web-shared/src/components/ui/data-inspector.tsx
Comment threadpackages/web-shared/test/stream-display.test.ts Outdated
…lapseRefs tests
- Remove unused formatStreamChunkForDisplay/sanitizeStreamChunkForDisplay
exports; keep only the formatArrayBufferViewForDisplay path actually used
by DataInspector.
- Replace broken role=tablist/role=tab on the Decoded/Bytes switcher
with aria-pressed toggle-button semantics.
- Export collapseRefs/isBytesDisplay and add regression tests covering
typed-array detection (top-level, nested in object/array/Map/Set,
DataView exclusion).
@karthikscale3

Copy link
Copy Markdown
ContributorAuthor

(AI) I'm not sure how vercel/front#68191 is a mirror of this, but as long as both ends work, LGTM. I haven't tried it out locally, so I'd want to re-test once the live deployment is back online, or before release

Front to close but not exactly 1:1. I will make a separate PR to close the gap.

karthikscale3and others added 3 commits April 28, 2026 15:55
* Replace eval with JSON.parse in serialization revive helper
devalue.stringify() always produces valid JSON — special values
(undefined, NaN, Infinity, -0) are encoded as negative integer
sentinels. JSON.parse yields the same flattened array form that
unflatten() expects, without the eval anti-pattern (VULN-918).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Drop redundant workflow package from changeset
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Emits Uint8Array chunks containing multi-byte UTF-8 (Latin Extended,
CJK, emoji, RTL Arabic) plus a UTF-8 encoded JSON document, and
asserts each chunk round-trips through TextDecoder({ fatal: true }).
Exercises the same decode path the web inspector relies on for
typed-array stream values.
Made-with: Cursor
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport-stableCherry-pick this PR to the stable branch when merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@karthikscale3@VaguelySerious@pranaygp
, '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" + '
Decode UTF-8 stream chunks by karthikscale3 · Pull Request #1852 · vercel/workflow · GitHub
Skip to content

Decode UTF-8 stream chunks - #1852

Merged
karthikscale3 merged 9 commits into
mainfrom
codex/decode-typed-array-stream-chunks
Apr 29, 2026
Merged

Decode UTF-8 stream chunks#1852
karthikscale3 merged 9 commits into
mainfrom
codex/decode-typed-array-stream-chunks

Conversation

@karthikscale3

@karthikscale3karthikscale3 commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

What changed

  • Added shared formatArrayBufferViewForDisplay + summarizeArrayBufferView helpers in @workflow/web-shared (src/lib/stream-display.ts). They UTF-8 decode typed-array values via TextDecoder({ fatal: true }) and fall back to a compact Uint8Array(N) [..] summary for binary data.
  • DataInspector (src/components/ui/data-inspector.tsx) now collapses any ArrayBufferView (other than DataView) into a non-expandable BytesDisplay opaque ref via collapseRefs, then renders it with a DecodedBytesChunk that shows decoded text by default and a Decoded / Bytes toggle to inspect the summarized raw value. Decoded text that parses as JSON is re-rendered through a nested DataInspector.
  • stream-viewer.tsx no longer formats stream chunks itself — chunks flow as raw hydrated values straight into DataInspector, so Events tab payloads, Trace detail fields, and stream chunks all share the same typed-array rendering path.
  • packages/web/app/lib/hooks/use-stream-reader.ts is dramatically simplified (95 lines removed): it no longer pre-formats chunks, it just keeps them hydrated for the inspector.
  • Decoded / Bytes toggle uses aria-pressed + aria-label toggle-button semantics (not role="tab", since the panel content lives above the buttons rather than as a sibling).
  • Public API in src/index.ts exposes only the DecodedStreamChunkSource type from this module; the rendering helpers are intentionally internal.
  • New regression tests in test/collapse-refs.test.ts (9 cases): top-level typed array, nested in plain object, array, Map values, Set entries, non-Uint8Array views (Int16Array), DataView exclusion, binary fallback summary, and primitive passthrough.

Why

AI agent workflow stream deltas (e.g. helloAgentWorkflow) arrive as Uint8Array values serialized through devalue. The previous viewer collapsed them into compact byte summaries like Uint8Array(87) [65, 73, ...], which made the actual text content unreadable. Decoded text alone would be lossy for binary payloads. Routing everything through DataInspector → collapseRefs keeps the readable default, makes the byte-like source explicit and inspectable, and gives Events / Trace / stream views consistent behavior.

Validation

  • pnpm --filter @workflow/web-shared test → 22 passed (22)
  • pnpm --filter @workflow/web-shared typecheck → clean
  • pnpm --filter @workflow/web-shared build → clean
  • pnpm exec biome check on touched files → only pre-existing complexity warnings on untouched code

Reference

@vercel

vercelBot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

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

ProjectDeploymentActionsUpdated (UTC)
example-nextjs-workflow-turbopackReadyReadyPreview, CommentApr 28, 2026 11:30pm
example-nextjs-workflow-webpackReadyReadyPreview, CommentApr 28, 2026 11:30pm
example-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-astro-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-express-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-fastify-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-hono-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-nitro-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-nuxt-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-sveltekit-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-vite-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workflow-docsReadyReadyPreview, Comment, Open in v0Apr 28, 2026 11:30pm
workflow-swc-playgroundReadyReadyPreview, CommentApr 28, 2026 11:30pm
workflow-webReadyReadyPreview, CommentApr 28, 2026 11:30pm

@changeset-bot

changeset-botBot commented Apr 24, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: addd614

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

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

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

@github-actions

github-actionsBot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production9890671056
✅ 💻 Local Development10660861152
✅ 📦 Local Production10660861152
✅ 🐘 Local Postgres10660861152
✅ 🪟 Windows960096
✅ 📋 Other270018288
Total455303434896

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro8907
✅ example8907
✅ express8907
✅ fastify8907
✅ hono8907
✅ nextjs-turbopack9402
✅ nextjs-webpack9402
✅ nitro8907
✅ nuxt8907
✅ sveltekit8907
✅ vite8907
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable9006
✅ express-stable9006
✅ fastify-stable9006
✅ hono-stable9006
✅ nextjs-turbopack-canary77019
✅ nextjs-turbopack-stable9600
✅ nextjs-webpack-canary77019
✅ nextjs-webpack-stable9600
✅ nitro-stable9006
✅ nuxt-stable9006
✅ sveltekit-stable9006
✅ vite-stable9006
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable9006
✅ express-stable9006
✅ fastify-stable9006
✅ hono-stable9006
✅ nextjs-turbopack-canary77019
✅ nextjs-turbopack-stable9600
✅ nextjs-webpack-canary77019
✅ nextjs-webpack-stable9600
✅ nitro-stable9006
✅ nuxt-stable9006
✅ sveltekit-stable9006
✅ vite-stable9006
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable9006
✅ express-stable9006
✅ fastify-stable9006
✅ hono-stable9006
✅ nextjs-turbopack-canary77019
✅ nextjs-turbopack-stable9600
✅ nextjs-webpack-canary77019
✅ nextjs-webpack-stable9600
✅ nitro-stable9006
✅ nuxt-stable9006
✅ sveltekit-stable9006
✅ vite-stable9006
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack9600
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable9006
✅ e2e-local-postgres-nest-stable9006
✅ e2e-local-prod-nest-stable9006

📋 View full workflow run

@github-actions

github-actionsBot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

📊 Benchmark Results

📈 Comparing against baseline from main branch. Green 🟢 = faster, Red 🔺 = slower.

workflow with no steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro0.041s (-3.9%)1.005s (~)0.964s101.00x
💻 LocalExpress0.044s (-1.1%)1.005s (~)0.961s101.06x
💻 LocalNext.js (Turbopack)0.048s1.005s0.957s101.16x
🐘 PostgresExpress0.054s (-6.0% 🟢)1.011s (~)0.956s101.32x
🐘 PostgresNitro0.055s (-42.4% 🟢)1.010s (-3.2%)0.955s101.32x
🐘 PostgresNext.js (Turbopack)0.057s1.011s0.953s101.38x
workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)1.116s2.006s0.890s101.00x
💻 LocalNitro1.129s (~)2.006s (~)0.877s101.01x
💻 LocalExpress1.130s (~)2.006s (~)0.877s101.01x
🐘 PostgresNext.js (Turbopack)1.141s2.010s0.869s101.02x
🐘 PostgresNitro1.143s (~)2.009s (~)0.867s101.02x
🐘 PostgresExpress1.148s (~)2.011s (~)0.862s101.03x
workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)10.794s11.025s0.231s31.00x
💻 LocalNitro10.946s (~)11.023s (~)0.078s31.01x
💻 LocalExpress10.952s (~)11.024s (~)0.071s31.01x
🐘 PostgresNitro10.957s (+0.8%)11.350s (+2.9%)0.393s31.02x
🐘 PostgresExpress10.961s (~)11.028s (~)0.067s31.02x
🐘 PostgresNext.js (Turbopack)11.002s11.359s0.357s31.02x
workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro14.507s (-0.6%)15.023s (~)0.517s41.00x
🐘 PostgresNext.js (Turbopack)14.508s15.023s0.514s41.00x
💻 LocalNext.js (Turbopack)14.596s15.029s0.434s41.01x
🐘 PostgresExpress14.621s (~)15.027s (~)0.406s41.01x
💻 LocalNitro14.983s (-0.5%)15.030s (-6.2% 🟢)0.047s41.03x
💻 LocalExpress14.999s (~)15.279s (+1.7%)0.280s41.03x
workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)13.770s14.020s0.251s71.00x
🐘 PostgresNitro13.792s (-1.3%)14.021s (-2.0%)0.229s71.00x
🐘 PostgresExpress14.142s (+1.0%)14.880s (+2.0%)0.739s71.03x
💻 LocalNext.js (Turbopack)15.743s16.031s0.288s61.14x
💻 LocalExpress16.535s (~)17.031s (~)0.496s61.20x
💻 LocalNitro16.756s (~)17.031s (~)0.275s61.22x
Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.225s2.010s0.785s151.00x
🐘 PostgresExpress1.255s (~)2.009s (~)0.754s151.02x
🐘 PostgresNitro1.278s (~)2.009s (~)0.731s151.04x
💻 LocalExpress1.513s (+1.6%)2.005s (~)0.493s151.24x
💻 LocalNext.js (Turbopack)1.541s2.006s0.465s151.26x
💻 LocalNitro1.545s (-5.3% 🟢)2.006s (-3.3%)0.460s151.26x
Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.325s (-1.1%)3.009s (~)0.684s101.00x
🐘 PostgresNext.js (Turbopack)2.355s3.007s0.653s101.01x
🐘 PostgresExpress2.365s (~)3.010s (~)0.645s101.02x
💻 LocalNext.js (Turbopack)2.878s3.342s0.464s91.24x
💻 LocalExpress2.970s (+0.6%)3.342s (-3.2%)0.372s91.28x
💻 LocalNitro3.135s (~)3.761s (-3.2%)0.626s81.35x
Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro3.466s (~)4.013s (~)0.547s81.00x
🐘 PostgresExpress3.511s (+0.7%)4.014s (~)0.503s81.01x
🐘 PostgresNext.js (Turbopack)3.622s4.014s0.392s81.05x
💻 LocalNext.js (Turbopack)7.191s7.765s0.575s42.07x
💻 LocalExpress7.682s (-7.9% 🟢)8.023s (-11.1% 🟢)0.341s42.22x
💻 LocalNitro8.748s (+4.8%)9.270s (+2.8%)0.523s42.52x
Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.233s2.008s0.775s151.00x
🐘 PostgresNitro1.262s (~)2.008s (~)0.746s151.02x
🐘 PostgresExpress1.269s (+0.9%)2.009s (~)0.740s151.03x
💻 LocalNext.js (Turbopack)1.522s2.006s0.484s151.23x
💻 LocalNitro1.541s (-17.4% 🟢)2.006s (-14.3% 🟢)0.465s151.25x
💻 LocalExpress1.545s (-18.4% 🟢)2.006s (-15.1% 🟢)0.461s151.25x
Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.318s (-0.9%)3.009s (~)0.691s101.00x
🐘 PostgresExpress2.354s (+0.5%)3.010s (~)0.656s101.02x
🐘 PostgresNext.js (Turbopack)2.432s3.009s0.577s101.05x
💻 LocalNext.js (Turbopack)2.815s3.310s0.495s101.21x
💻 LocalExpress2.839s (-9.4% 🟢)3.108s (-17.4% 🟢)0.269s101.22x
💻 LocalNitro3.042s (-0.8%)3.885s (~)0.843s81.31x
Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro3.475s (~)4.011s (~)0.536s81.00x
🐘 PostgresExpress3.493s (~)4.011s (~)0.517s81.01x
🐘 PostgresNext.js (Turbopack)3.616s4.011s0.395s81.04x
💻 LocalNext.js (Turbopack)7.662s8.270s0.607s42.21x
💻 LocalExpress7.892s (-10.3% 🟢)8.270s (-10.8% 🟢)0.378s42.27x
💻 LocalNitro8.903s (-2.6%)9.275s (-7.5% 🟢)0.372s42.56x
workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.778s1.024s0.246s591.00x
🐘 PostgresNitro0.806s (-1.8%)1.006s (~)0.200s601.04x
🐘 PostgresExpress0.837s (~)1.023s (~)0.186s591.08x
💻 LocalNext.js (Turbopack)0.865s1.039s0.174s581.11x
💻 LocalNitro1.000s (+2.0%)1.309s (+19.7% 🔺)0.309s461.29x
💻 LocalExpress1.015s (+3.1%)1.672s (+55.4% 🔺)0.657s361.31x
workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.914s (-0.7%)2.075s (-1.2%)0.161s441.00x
🐘 PostgresNext.js (Turbopack)1.925s2.149s0.225s421.01x
🐘 PostgresExpress1.968s (~)2.316s (+2.6%)0.348s391.03x
💻 LocalNext.js (Turbopack)2.737s3.008s0.271s301.43x
💻 LocalNitro3.017s (-0.6%)3.649s (-2.9%)0.632s251.58x
💻 LocalExpress3.020s (~)3.610s (+0.7%)0.589s251.58x
workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)3.864s4.076s0.212s301.00x
🐘 PostgresNitro3.866s (-5.8% 🟢)4.042s (-12.2% 🟢)0.176s301.00x
🐘 PostgresExpress4.204s (+5.4% 🔺)5.011s (+14.7% 🔺)0.807s241.09x
💻 LocalNext.js (Turbopack)8.536s9.017s0.481s142.21x
💻 LocalExpress8.969s (-2.6%)9.325s (-6.9% 🟢)0.355s132.32x
💻 LocalNitro9.222s (-0.8%)9.942s (-0.8%)0.720s132.39x
workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.247s1.007s0.760s601.00x
🐘 PostgresNitro0.284s (~)1.006s (~)0.722s601.15x
🐘 PostgresExpress0.294s (+4.1%)1.007s (~)0.713s601.19x
💻 LocalExpress0.562s (~)1.004s (~)0.442s602.27x
💻 LocalNext.js (Turbopack)0.575s1.004s0.429s602.33x
💻 LocalNitro0.580s (-4.1%)1.004s (-1.7%)0.425s602.34x
workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.488s1.007s0.519s901.00x
🐘 PostgresExpress0.488s (-4.3%)1.007s (~)0.519s901.00x
🐘 PostgresNitro0.496s (~)1.006s (~)0.510s901.02x
💻 LocalExpress2.378s (-5.4% 🟢)3.009s (~)0.631s304.88x
💻 LocalNitro2.571s (+1.3%)3.009s (~)0.438s305.27x
💻 LocalNext.js (Turbopack)2.576s3.009s0.433s305.28x
workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.792s (-3.3%)1.008s (-0.9%)0.217s1201.00x
🐘 PostgresNitro0.796s (+0.7%)1.007s (~)0.211s1201.01x
🐘 PostgresNext.js (Turbopack)0.798s1.015s0.217s1191.01x
💻 LocalNext.js (Turbopack)10.221s10.940s0.718s1112.91x
💻 LocalExpress10.431s (-6.8% 🟢)11.028s (-7.6% 🟢)0.597s1113.18x
💻 LocalNitro11.794s (+5.4% 🔺)12.327s (+5.7% 🔺)0.534s1014.90x
Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)0.183s1.003s0.010s1.016s0.834s101.00x
🐘 PostgresNext.js (Turbopack)0.190s1.000s0.001s1.009s0.819s101.04x
🐘 PostgresNitro0.203s (-0.8%)0.996s (~)0.001s (-33.3% 🟢)1.008s (~)0.805s101.11x
💻 LocalNitro0.208s (-2.6%)1.004s (~)0.012s (-1.6%)1.019s (~)0.810s101.14x
🐘 PostgresExpress0.227s (+10.5% 🔺)0.996s (~)0.001s (-12.5% 🟢)1.011s (~)0.784s101.24x
💻 LocalExpress0.248s (+24.5% 🔺)1.004s (~)0.020s (+67.8% 🔺)1.027s (+0.8%)0.779s101.36x
stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.619s (-0.7%)1.026s (+1.9%)0.004s (-5.8% 🟢)1.046s (+2.3%)0.427s581.00x
🐘 PostgresExpress0.627s (-0.5%)1.005s (~)0.012s (+205.3% 🔺)1.030s (+0.6%)0.403s591.01x
🐘 PostgresNext.js (Turbopack)0.640s1.044s0.004s1.058s0.418s571.03x
💻 LocalNext.js (Turbopack)0.696s1.012s0.011s1.026s0.331s591.12x
💻 LocalNitro0.747s (-10.9% 🟢)1.012s (~)0.010s (+8.2% 🔺)1.023s (-8.3% 🟢)0.276s591.21x
💻 LocalExpress0.763s (+0.8%)1.012s (-1.6%)0.010s (+1.4%)1.024s (-1.6%)0.260s591.23x
10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.926s1.199s0.000s1.208s0.282s511.00x
🐘 PostgresNitro0.955s (-1.4%)1.168s (-6.4% 🟢)0.000s (-52.9% 🟢)1.193s (-5.2% 🟢)0.238s511.03x
🐘 PostgresExpress0.967s (+0.6%)1.246s (-2.5%)0.000s (-52.1% 🟢)1.259s (-3.7%)0.292s481.04x
💻 LocalExpress1.214s (-0.9%)2.020s (~)0.000s (-10.0% 🟢)2.022s (~)0.808s301.31x
💻 LocalNitro1.233s (+0.8%)2.021s (~)0.000s (+333.3% 🔺)2.023s (~)0.790s301.33x
💻 LocalNext.js (Turbopack)1.313s2.022s0.000s2.025s0.712s301.42x
fan-out fan-in 10 streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.732s (-3.3%)2.139s (~)0.000s (-100.0% 🟢)2.152s (-1.0%)0.419s281.00x
🐘 PostgresNext.js (Turbopack)1.803s2.106s0.000s2.138s0.334s291.04x
🐘 PostgresExpress1.817s (+2.6%)2.261s (+3.8%)0.000s (+Infinity% 🔺)2.270s (+3.2%)0.453s271.05x
💻 LocalExpress3.438s (-0.9%)4.032s (~)0.001s (-33.3% 🟢)4.035s (~)0.597s151.98x
💻 LocalNitro3.503s (+3.4%)4.032s (~)0.001s (~)4.035s (~)0.532s152.02x
💻 LocalNext.js (Turbopack)3.861s4.235s0.000s4.239s0.378s152.23x

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalNext.js (Turbopack)15/21
🐘 PostgresNext.js (Turbopack)10/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres18/21
Next.js (Turbopack)🐘 Postgres17/21
Nitro🐘 Postgres18/21
Column Definitions
  • Workflow Time: Runtime reported by workflow (completedAt - createdAt) - primary metric
  • TTFB: Time to First Byte - time from workflow start until first stream byte received (stream benchmarks only)
  • Slurp: Time from first byte to complete stream consumption (stream benchmarks only)
  • Wall Time: Total testbench time (trigger workflow + poll for result)
  • Overhead: Testbench overhead (Wall Time - Workflow Time)
  • Samples: Number of benchmark iterations run
  • vs Fastest: How much slower compared to the fastest configuration for this benchmark

Worlds:

  • 💻 Local: In-memory filesystem world (local development)
  • 🐘 Postgres: PostgreSQL database world (local development)
  • ▲ Vercel: Vercel production/preview deployment
  • 🌐 Turso: Community world (local development)
  • 🌐 MongoDB: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Jazz: Community world (local development)

📋 View full workflow run

@VaguelySeriousVaguelySerious left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(AI) I'm not sure how https://github.com/vercel/front/pull/68191 is a mirror of this, but as long as both ends work, LGTM. I haven't tried it out locally, so I'd want to re-test once the live deployment is back online, or before release

Comment threadpackages/web-shared/src/index.ts Outdated
Comment threadpackages/web-shared/src/components/ui/data-inspector.tsx
Comment threadpackages/web-shared/test/stream-display.test.ts Outdated
…lapseRefs tests
- Remove unused formatStreamChunkForDisplay/sanitizeStreamChunkForDisplay
exports; keep only the formatArrayBufferViewForDisplay path actually used
by DataInspector.
- Replace broken role=tablist/role=tab on the Decoded/Bytes switcher
with aria-pressed toggle-button semantics.
- Export collapseRefs/isBytesDisplay and add regression tests covering
typed-array detection (top-level, nested in object/array/Map/Set,
DataView exclusion).
@karthikscale3

Copy link
Copy Markdown
ContributorAuthor

(AI) I'm not sure how vercel/front#68191 is a mirror of this, but as long as both ends work, LGTM. I haven't tried it out locally, so I'd want to re-test once the live deployment is back online, or before release

Front to close but not exactly 1:1. I will make a separate PR to close the gap.

karthikscale3and others added 3 commits April 28, 2026 15:55
* Replace eval with JSON.parse in serialization revive helper
devalue.stringify() always produces valid JSON — special values
(undefined, NaN, Infinity, -0) are encoded as negative integer
sentinels. JSON.parse yields the same flattened array form that
unflatten() expects, without the eval anti-pattern (VULN-918).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Drop redundant workflow package from changeset
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Emits Uint8Array chunks containing multi-byte UTF-8 (Latin Extended,
CJK, emoji, RTL Arabic) plus a UTF-8 encoded JSON document, and
asserts each chunk round-trips through TextDecoder({ fatal: true }).
Exercises the same decode path the web inspector relies on for
typed-array stream values.
Made-with: Cursor
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport-stableCherry-pick this PR to the stable branch when merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@karthikscale3@VaguelySerious@pranaygp
, '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('^' + ".*" + ' Decode UTF-8 stream chunks by karthikscale3 · Pull Request #1852 · vercel/workflow · GitHub
Skip to content

Decode UTF-8 stream chunks - #1852

Merged
karthikscale3 merged 9 commits into
mainfrom
codex/decode-typed-array-stream-chunks
Apr 29, 2026
Merged

Decode UTF-8 stream chunks#1852
karthikscale3 merged 9 commits into
mainfrom
codex/decode-typed-array-stream-chunks

Conversation

@karthikscale3

@karthikscale3karthikscale3 commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

What changed

  • Added shared formatArrayBufferViewForDisplay + summarizeArrayBufferView helpers in @workflow/web-shared (src/lib/stream-display.ts). They UTF-8 decode typed-array values via TextDecoder({ fatal: true }) and fall back to a compact Uint8Array(N) [..] summary for binary data.
  • DataInspector (src/components/ui/data-inspector.tsx) now collapses any ArrayBufferView (other than DataView) into a non-expandable BytesDisplay opaque ref via collapseRefs, then renders it with a DecodedBytesChunk that shows decoded text by default and a Decoded / Bytes toggle to inspect the summarized raw value. Decoded text that parses as JSON is re-rendered through a nested DataInspector.
  • stream-viewer.tsx no longer formats stream chunks itself — chunks flow as raw hydrated values straight into DataInspector, so Events tab payloads, Trace detail fields, and stream chunks all share the same typed-array rendering path.
  • packages/web/app/lib/hooks/use-stream-reader.ts is dramatically simplified (95 lines removed): it no longer pre-formats chunks, it just keeps them hydrated for the inspector.
  • Decoded / Bytes toggle uses aria-pressed + aria-label toggle-button semantics (not role="tab", since the panel content lives above the buttons rather than as a sibling).
  • Public API in src/index.ts exposes only the DecodedStreamChunkSource type from this module; the rendering helpers are intentionally internal.
  • New regression tests in test/collapse-refs.test.ts (9 cases): top-level typed array, nested in plain object, array, Map values, Set entries, non-Uint8Array views (Int16Array), DataView exclusion, binary fallback summary, and primitive passthrough.

Why

AI agent workflow stream deltas (e.g. helloAgentWorkflow) arrive as Uint8Array values serialized through devalue. The previous viewer collapsed them into compact byte summaries like Uint8Array(87) [65, 73, ...], which made the actual text content unreadable. Decoded text alone would be lossy for binary payloads. Routing everything through DataInspector → collapseRefs keeps the readable default, makes the byte-like source explicit and inspectable, and gives Events / Trace / stream views consistent behavior.

Validation

  • pnpm --filter @workflow/web-shared test → 22 passed (22)
  • pnpm --filter @workflow/web-shared typecheck → clean
  • pnpm --filter @workflow/web-shared build → clean
  • pnpm exec biome check on touched files → only pre-existing complexity warnings on untouched code

Reference

@vercel

vercelBot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

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

ProjectDeploymentActionsUpdated (UTC)
example-nextjs-workflow-turbopackReadyReadyPreview, CommentApr 28, 2026 11:30pm
example-nextjs-workflow-webpackReadyReadyPreview, CommentApr 28, 2026 11:30pm
example-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-astro-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-express-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-fastify-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-hono-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-nitro-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-nuxt-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-sveltekit-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-vite-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workflow-docsReadyReadyPreview, Comment, Open in v0Apr 28, 2026 11:30pm
workflow-swc-playgroundReadyReadyPreview, CommentApr 28, 2026 11:30pm
workflow-webReadyReadyPreview, CommentApr 28, 2026 11:30pm

@changeset-bot

changeset-botBot commented Apr 24, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: addd614

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

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

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

@github-actions

github-actionsBot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production9890671056
✅ 💻 Local Development10660861152
✅ 📦 Local Production10660861152
✅ 🐘 Local Postgres10660861152
✅ 🪟 Windows960096
✅ 📋 Other270018288
Total455303434896

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro8907
✅ example8907
✅ express8907
✅ fastify8907
✅ hono8907
✅ nextjs-turbopack9402
✅ nextjs-webpack9402
✅ nitro8907
✅ nuxt8907
✅ sveltekit8907
✅ vite8907
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable9006
✅ express-stable9006
✅ fastify-stable9006
✅ hono-stable9006
✅ nextjs-turbopack-canary77019
✅ nextjs-turbopack-stable9600
✅ nextjs-webpack-canary77019
✅ nextjs-webpack-stable9600
✅ nitro-stable9006
✅ nuxt-stable9006
✅ sveltekit-stable9006
✅ vite-stable9006
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable9006
✅ express-stable9006
✅ fastify-stable9006
✅ hono-stable9006
✅ nextjs-turbopack-canary77019
✅ nextjs-turbopack-stable9600
✅ nextjs-webpack-canary77019
✅ nextjs-webpack-stable9600
✅ nitro-stable9006
✅ nuxt-stable9006
✅ sveltekit-stable9006
✅ vite-stable9006
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable9006
✅ express-stable9006
✅ fastify-stable9006
✅ hono-stable9006
✅ nextjs-turbopack-canary77019
✅ nextjs-turbopack-stable9600
✅ nextjs-webpack-canary77019
✅ nextjs-webpack-stable9600
✅ nitro-stable9006
✅ nuxt-stable9006
✅ sveltekit-stable9006
✅ vite-stable9006
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack9600
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable9006
✅ e2e-local-postgres-nest-stable9006
✅ e2e-local-prod-nest-stable9006

📋 View full workflow run

@github-actions

github-actionsBot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

📊 Benchmark Results

📈 Comparing against baseline from main branch. Green 🟢 = faster, Red 🔺 = slower.

workflow with no steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro0.041s (-3.9%)1.005s (~)0.964s101.00x
💻 LocalExpress0.044s (-1.1%)1.005s (~)0.961s101.06x
💻 LocalNext.js (Turbopack)0.048s1.005s0.957s101.16x
🐘 PostgresExpress0.054s (-6.0% 🟢)1.011s (~)0.956s101.32x
🐘 PostgresNitro0.055s (-42.4% 🟢)1.010s (-3.2%)0.955s101.32x
🐘 PostgresNext.js (Turbopack)0.057s1.011s0.953s101.38x
workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)1.116s2.006s0.890s101.00x
💻 LocalNitro1.129s (~)2.006s (~)0.877s101.01x
💻 LocalExpress1.130s (~)2.006s (~)0.877s101.01x
🐘 PostgresNext.js (Turbopack)1.141s2.010s0.869s101.02x
🐘 PostgresNitro1.143s (~)2.009s (~)0.867s101.02x
🐘 PostgresExpress1.148s (~)2.011s (~)0.862s101.03x
workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)10.794s11.025s0.231s31.00x
💻 LocalNitro10.946s (~)11.023s (~)0.078s31.01x
💻 LocalExpress10.952s (~)11.024s (~)0.071s31.01x
🐘 PostgresNitro10.957s (+0.8%)11.350s (+2.9%)0.393s31.02x
🐘 PostgresExpress10.961s (~)11.028s (~)0.067s31.02x
🐘 PostgresNext.js (Turbopack)11.002s11.359s0.357s31.02x
workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro14.507s (-0.6%)15.023s (~)0.517s41.00x
🐘 PostgresNext.js (Turbopack)14.508s15.023s0.514s41.00x
💻 LocalNext.js (Turbopack)14.596s15.029s0.434s41.01x
🐘 PostgresExpress14.621s (~)15.027s (~)0.406s41.01x
💻 LocalNitro14.983s (-0.5%)15.030s (-6.2% 🟢)0.047s41.03x
💻 LocalExpress14.999s (~)15.279s (+1.7%)0.280s41.03x
workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)13.770s14.020s0.251s71.00x
🐘 PostgresNitro13.792s (-1.3%)14.021s (-2.0%)0.229s71.00x
🐘 PostgresExpress14.142s (+1.0%)14.880s (+2.0%)0.739s71.03x
💻 LocalNext.js (Turbopack)15.743s16.031s0.288s61.14x
💻 LocalExpress16.535s (~)17.031s (~)0.496s61.20x
💻 LocalNitro16.756s (~)17.031s (~)0.275s61.22x
Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.225s2.010s0.785s151.00x
🐘 PostgresExpress1.255s (~)2.009s (~)0.754s151.02x
🐘 PostgresNitro1.278s (~)2.009s (~)0.731s151.04x
💻 LocalExpress1.513s (+1.6%)2.005s (~)0.493s151.24x
💻 LocalNext.js (Turbopack)1.541s2.006s0.465s151.26x
💻 LocalNitro1.545s (-5.3% 🟢)2.006s (-3.3%)0.460s151.26x
Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.325s (-1.1%)3.009s (~)0.684s101.00x
🐘 PostgresNext.js (Turbopack)2.355s3.007s0.653s101.01x
🐘 PostgresExpress2.365s (~)3.010s (~)0.645s101.02x
💻 LocalNext.js (Turbopack)2.878s3.342s0.464s91.24x
💻 LocalExpress2.970s (+0.6%)3.342s (-3.2%)0.372s91.28x
💻 LocalNitro3.135s (~)3.761s (-3.2%)0.626s81.35x
Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro3.466s (~)4.013s (~)0.547s81.00x
🐘 PostgresExpress3.511s (+0.7%)4.014s (~)0.503s81.01x
🐘 PostgresNext.js (Turbopack)3.622s4.014s0.392s81.05x
💻 LocalNext.js (Turbopack)7.191s7.765s0.575s42.07x
💻 LocalExpress7.682s (-7.9% 🟢)8.023s (-11.1% 🟢)0.341s42.22x
💻 LocalNitro8.748s (+4.8%)9.270s (+2.8%)0.523s42.52x
Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.233s2.008s0.775s151.00x
🐘 PostgresNitro1.262s (~)2.008s (~)0.746s151.02x
🐘 PostgresExpress1.269s (+0.9%)2.009s (~)0.740s151.03x
💻 LocalNext.js (Turbopack)1.522s2.006s0.484s151.23x
💻 LocalNitro1.541s (-17.4% 🟢)2.006s (-14.3% 🟢)0.465s151.25x
💻 LocalExpress1.545s (-18.4% 🟢)2.006s (-15.1% 🟢)0.461s151.25x
Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.318s (-0.9%)3.009s (~)0.691s101.00x
🐘 PostgresExpress2.354s (+0.5%)3.010s (~)0.656s101.02x
🐘 PostgresNext.js (Turbopack)2.432s3.009s0.577s101.05x
💻 LocalNext.js (Turbopack)2.815s3.310s0.495s101.21x
💻 LocalExpress2.839s (-9.4% 🟢)3.108s (-17.4% 🟢)0.269s101.22x
💻 LocalNitro3.042s (-0.8%)3.885s (~)0.843s81.31x
Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro3.475s (~)4.011s (~)0.536s81.00x
🐘 PostgresExpress3.493s (~)4.011s (~)0.517s81.01x
🐘 PostgresNext.js (Turbopack)3.616s4.011s0.395s81.04x
💻 LocalNext.js (Turbopack)7.662s8.270s0.607s42.21x
💻 LocalExpress7.892s (-10.3% 🟢)8.270s (-10.8% 🟢)0.378s42.27x
💻 LocalNitro8.903s (-2.6%)9.275s (-7.5% 🟢)0.372s42.56x
workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.778s1.024s0.246s591.00x
🐘 PostgresNitro0.806s (-1.8%)1.006s (~)0.200s601.04x
🐘 PostgresExpress0.837s (~)1.023s (~)0.186s591.08x
💻 LocalNext.js (Turbopack)0.865s1.039s0.174s581.11x
💻 LocalNitro1.000s (+2.0%)1.309s (+19.7% 🔺)0.309s461.29x
💻 LocalExpress1.015s (+3.1%)1.672s (+55.4% 🔺)0.657s361.31x
workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.914s (-0.7%)2.075s (-1.2%)0.161s441.00x
🐘 PostgresNext.js (Turbopack)1.925s2.149s0.225s421.01x
🐘 PostgresExpress1.968s (~)2.316s (+2.6%)0.348s391.03x
💻 LocalNext.js (Turbopack)2.737s3.008s0.271s301.43x
💻 LocalNitro3.017s (-0.6%)3.649s (-2.9%)0.632s251.58x
💻 LocalExpress3.020s (~)3.610s (+0.7%)0.589s251.58x
workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)3.864s4.076s0.212s301.00x
🐘 PostgresNitro3.866s (-5.8% 🟢)4.042s (-12.2% 🟢)0.176s301.00x
🐘 PostgresExpress4.204s (+5.4% 🔺)5.011s (+14.7% 🔺)0.807s241.09x
💻 LocalNext.js (Turbopack)8.536s9.017s0.481s142.21x
💻 LocalExpress8.969s (-2.6%)9.325s (-6.9% 🟢)0.355s132.32x
💻 LocalNitro9.222s (-0.8%)9.942s (-0.8%)0.720s132.39x
workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.247s1.007s0.760s601.00x
🐘 PostgresNitro0.284s (~)1.006s (~)0.722s601.15x
🐘 PostgresExpress0.294s (+4.1%)1.007s (~)0.713s601.19x
💻 LocalExpress0.562s (~)1.004s (~)0.442s602.27x
💻 LocalNext.js (Turbopack)0.575s1.004s0.429s602.33x
💻 LocalNitro0.580s (-4.1%)1.004s (-1.7%)0.425s602.34x
workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.488s1.007s0.519s901.00x
🐘 PostgresExpress0.488s (-4.3%)1.007s (~)0.519s901.00x
🐘 PostgresNitro0.496s (~)1.006s (~)0.510s901.02x
💻 LocalExpress2.378s (-5.4% 🟢)3.009s (~)0.631s304.88x
💻 LocalNitro2.571s (+1.3%)3.009s (~)0.438s305.27x
💻 LocalNext.js (Turbopack)2.576s3.009s0.433s305.28x
workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.792s (-3.3%)1.008s (-0.9%)0.217s1201.00x
🐘 PostgresNitro0.796s (+0.7%)1.007s (~)0.211s1201.01x
🐘 PostgresNext.js (Turbopack)0.798s1.015s0.217s1191.01x
💻 LocalNext.js (Turbopack)10.221s10.940s0.718s1112.91x
💻 LocalExpress10.431s (-6.8% 🟢)11.028s (-7.6% 🟢)0.597s1113.18x
💻 LocalNitro11.794s (+5.4% 🔺)12.327s (+5.7% 🔺)0.534s1014.90x
Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)0.183s1.003s0.010s1.016s0.834s101.00x
🐘 PostgresNext.js (Turbopack)0.190s1.000s0.001s1.009s0.819s101.04x
🐘 PostgresNitro0.203s (-0.8%)0.996s (~)0.001s (-33.3% 🟢)1.008s (~)0.805s101.11x
💻 LocalNitro0.208s (-2.6%)1.004s (~)0.012s (-1.6%)1.019s (~)0.810s101.14x
🐘 PostgresExpress0.227s (+10.5% 🔺)0.996s (~)0.001s (-12.5% 🟢)1.011s (~)0.784s101.24x
💻 LocalExpress0.248s (+24.5% 🔺)1.004s (~)0.020s (+67.8% 🔺)1.027s (+0.8%)0.779s101.36x
stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.619s (-0.7%)1.026s (+1.9%)0.004s (-5.8% 🟢)1.046s (+2.3%)0.427s581.00x
🐘 PostgresExpress0.627s (-0.5%)1.005s (~)0.012s (+205.3% 🔺)1.030s (+0.6%)0.403s591.01x
🐘 PostgresNext.js (Turbopack)0.640s1.044s0.004s1.058s0.418s571.03x
💻 LocalNext.js (Turbopack)0.696s1.012s0.011s1.026s0.331s591.12x
💻 LocalNitro0.747s (-10.9% 🟢)1.012s (~)0.010s (+8.2% 🔺)1.023s (-8.3% 🟢)0.276s591.21x
💻 LocalExpress0.763s (+0.8%)1.012s (-1.6%)0.010s (+1.4%)1.024s (-1.6%)0.260s591.23x
10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.926s1.199s0.000s1.208s0.282s511.00x
🐘 PostgresNitro0.955s (-1.4%)1.168s (-6.4% 🟢)0.000s (-52.9% 🟢)1.193s (-5.2% 🟢)0.238s511.03x
🐘 PostgresExpress0.967s (+0.6%)1.246s (-2.5%)0.000s (-52.1% 🟢)1.259s (-3.7%)0.292s481.04x
💻 LocalExpress1.214s (-0.9%)2.020s (~)0.000s (-10.0% 🟢)2.022s (~)0.808s301.31x
💻 LocalNitro1.233s (+0.8%)2.021s (~)0.000s (+333.3% 🔺)2.023s (~)0.790s301.33x
💻 LocalNext.js (Turbopack)1.313s2.022s0.000s2.025s0.712s301.42x
fan-out fan-in 10 streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.732s (-3.3%)2.139s (~)0.000s (-100.0% 🟢)2.152s (-1.0%)0.419s281.00x
🐘 PostgresNext.js (Turbopack)1.803s2.106s0.000s2.138s0.334s291.04x
🐘 PostgresExpress1.817s (+2.6%)2.261s (+3.8%)0.000s (+Infinity% 🔺)2.270s (+3.2%)0.453s271.05x
💻 LocalExpress3.438s (-0.9%)4.032s (~)0.001s (-33.3% 🟢)4.035s (~)0.597s151.98x
💻 LocalNitro3.503s (+3.4%)4.032s (~)0.001s (~)4.035s (~)0.532s152.02x
💻 LocalNext.js (Turbopack)3.861s4.235s0.000s4.239s0.378s152.23x

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalNext.js (Turbopack)15/21
🐘 PostgresNext.js (Turbopack)10/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres18/21
Next.js (Turbopack)🐘 Postgres17/21
Nitro🐘 Postgres18/21
Column Definitions
  • Workflow Time: Runtime reported by workflow (completedAt - createdAt) - primary metric
  • TTFB: Time to First Byte - time from workflow start until first stream byte received (stream benchmarks only)
  • Slurp: Time from first byte to complete stream consumption (stream benchmarks only)
  • Wall Time: Total testbench time (trigger workflow + poll for result)
  • Overhead: Testbench overhead (Wall Time - Workflow Time)
  • Samples: Number of benchmark iterations run
  • vs Fastest: How much slower compared to the fastest configuration for this benchmark

Worlds:

  • 💻 Local: In-memory filesystem world (local development)
  • 🐘 Postgres: PostgreSQL database world (local development)
  • ▲ Vercel: Vercel production/preview deployment
  • 🌐 Turso: Community world (local development)
  • 🌐 MongoDB: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Jazz: Community world (local development)

📋 View full workflow run

@VaguelySeriousVaguelySerious left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(AI) I'm not sure how https://github.com/vercel/front/pull/68191 is a mirror of this, but as long as both ends work, LGTM. I haven't tried it out locally, so I'd want to re-test once the live deployment is back online, or before release

Comment threadpackages/web-shared/src/index.ts Outdated
Comment threadpackages/web-shared/src/components/ui/data-inspector.tsx
Comment threadpackages/web-shared/test/stream-display.test.ts Outdated
…lapseRefs tests
- Remove unused formatStreamChunkForDisplay/sanitizeStreamChunkForDisplay
exports; keep only the formatArrayBufferViewForDisplay path actually used
by DataInspector.
- Replace broken role=tablist/role=tab on the Decoded/Bytes switcher
with aria-pressed toggle-button semantics.
- Export collapseRefs/isBytesDisplay and add regression tests covering
typed-array detection (top-level, nested in object/array/Map/Set,
DataView exclusion).
@karthikscale3

Copy link
Copy Markdown
ContributorAuthor

(AI) I'm not sure how vercel/front#68191 is a mirror of this, but as long as both ends work, LGTM. I haven't tried it out locally, so I'd want to re-test once the live deployment is back online, or before release

Front to close but not exactly 1:1. I will make a separate PR to close the gap.

karthikscale3and others added 3 commits April 28, 2026 15:55
* Replace eval with JSON.parse in serialization revive helper
devalue.stringify() always produces valid JSON — special values
(undefined, NaN, Infinity, -0) are encoded as negative integer
sentinels. JSON.parse yields the same flattened array form that
unflatten() expects, without the eval anti-pattern (VULN-918).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Drop redundant workflow package from changeset
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Emits Uint8Array chunks containing multi-byte UTF-8 (Latin Extended,
CJK, emoji, RTL Arabic) plus a UTF-8 encoded JSON document, and
asserts each chunk round-trips through TextDecoder({ fatal: true }).
Exercises the same decode path the web inspector relies on for
typed-array stream values.
Made-with: Cursor
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport-stableCherry-pick this PR to the stable branch when merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@karthikscale3@VaguelySerious@pranaygp
, '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('^' + ".*" + ' Decode UTF-8 stream chunks by karthikscale3 · Pull Request #1852 · vercel/workflow · GitHub
Skip to content

Decode UTF-8 stream chunks - #1852

Merged
karthikscale3 merged 9 commits into
mainfrom
codex/decode-typed-array-stream-chunks
Apr 29, 2026
Merged

Decode UTF-8 stream chunks#1852
karthikscale3 merged 9 commits into
mainfrom
codex/decode-typed-array-stream-chunks

Conversation

@karthikscale3

@karthikscale3karthikscale3 commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

What changed

  • Added shared formatArrayBufferViewForDisplay + summarizeArrayBufferView helpers in @workflow/web-shared (src/lib/stream-display.ts). They UTF-8 decode typed-array values via TextDecoder({ fatal: true }) and fall back to a compact Uint8Array(N) [..] summary for binary data.
  • DataInspector (src/components/ui/data-inspector.tsx) now collapses any ArrayBufferView (other than DataView) into a non-expandable BytesDisplay opaque ref via collapseRefs, then renders it with a DecodedBytesChunk that shows decoded text by default and a Decoded / Bytes toggle to inspect the summarized raw value. Decoded text that parses as JSON is re-rendered through a nested DataInspector.
  • stream-viewer.tsx no longer formats stream chunks itself — chunks flow as raw hydrated values straight into DataInspector, so Events tab payloads, Trace detail fields, and stream chunks all share the same typed-array rendering path.
  • packages/web/app/lib/hooks/use-stream-reader.ts is dramatically simplified (95 lines removed): it no longer pre-formats chunks, it just keeps them hydrated for the inspector.
  • Decoded / Bytes toggle uses aria-pressed + aria-label toggle-button semantics (not role="tab", since the panel content lives above the buttons rather than as a sibling).
  • Public API in src/index.ts exposes only the DecodedStreamChunkSource type from this module; the rendering helpers are intentionally internal.
  • New regression tests in test/collapse-refs.test.ts (9 cases): top-level typed array, nested in plain object, array, Map values, Set entries, non-Uint8Array views (Int16Array), DataView exclusion, binary fallback summary, and primitive passthrough.

Why

AI agent workflow stream deltas (e.g. helloAgentWorkflow) arrive as Uint8Array values serialized through devalue. The previous viewer collapsed them into compact byte summaries like Uint8Array(87) [65, 73, ...], which made the actual text content unreadable. Decoded text alone would be lossy for binary payloads. Routing everything through DataInspector → collapseRefs keeps the readable default, makes the byte-like source explicit and inspectable, and gives Events / Trace / stream views consistent behavior.

Validation

  • pnpm --filter @workflow/web-shared test → 22 passed (22)
  • pnpm --filter @workflow/web-shared typecheck → clean
  • pnpm --filter @workflow/web-shared build → clean
  • pnpm exec biome check on touched files → only pre-existing complexity warnings on untouched code

Reference

@vercel

vercelBot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

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

ProjectDeploymentActionsUpdated (UTC)
example-nextjs-workflow-turbopackReadyReadyPreview, CommentApr 28, 2026 11:30pm
example-nextjs-workflow-webpackReadyReadyPreview, CommentApr 28, 2026 11:30pm
example-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-astro-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-express-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-fastify-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-hono-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-nitro-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-nuxt-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-sveltekit-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-vite-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workflow-docsReadyReadyPreview, Comment, Open in v0Apr 28, 2026 11:30pm
workflow-swc-playgroundReadyReadyPreview, CommentApr 28, 2026 11:30pm
workflow-webReadyReadyPreview, CommentApr 28, 2026 11:30pm

@changeset-bot

changeset-botBot commented Apr 24, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: addd614

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

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

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

@github-actions

github-actionsBot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production9890671056
✅ 💻 Local Development10660861152
✅ 📦 Local Production10660861152
✅ 🐘 Local Postgres10660861152
✅ 🪟 Windows960096
✅ 📋 Other270018288
Total455303434896

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro8907
✅ example8907
✅ express8907
✅ fastify8907
✅ hono8907
✅ nextjs-turbopack9402
✅ nextjs-webpack9402
✅ nitro8907
✅ nuxt8907
✅ sveltekit8907
✅ vite8907
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable9006
✅ express-stable9006
✅ fastify-stable9006
✅ hono-stable9006
✅ nextjs-turbopack-canary77019
✅ nextjs-turbopack-stable9600
✅ nextjs-webpack-canary77019
✅ nextjs-webpack-stable9600
✅ nitro-stable9006
✅ nuxt-stable9006
✅ sveltekit-stable9006
✅ vite-stable9006
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable9006
✅ express-stable9006
✅ fastify-stable9006
✅ hono-stable9006
✅ nextjs-turbopack-canary77019
✅ nextjs-turbopack-stable9600
✅ nextjs-webpack-canary77019
✅ nextjs-webpack-stable9600
✅ nitro-stable9006
✅ nuxt-stable9006
✅ sveltekit-stable9006
✅ vite-stable9006
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable9006
✅ express-stable9006
✅ fastify-stable9006
✅ hono-stable9006
✅ nextjs-turbopack-canary77019
✅ nextjs-turbopack-stable9600
✅ nextjs-webpack-canary77019
✅ nextjs-webpack-stable9600
✅ nitro-stable9006
✅ nuxt-stable9006
✅ sveltekit-stable9006
✅ vite-stable9006
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack9600
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable9006
✅ e2e-local-postgres-nest-stable9006
✅ e2e-local-prod-nest-stable9006

📋 View full workflow run

@github-actions

github-actionsBot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

📊 Benchmark Results

📈 Comparing against baseline from main branch. Green 🟢 = faster, Red 🔺 = slower.

workflow with no steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro0.041s (-3.9%)1.005s (~)0.964s101.00x
💻 LocalExpress0.044s (-1.1%)1.005s (~)0.961s101.06x
💻 LocalNext.js (Turbopack)0.048s1.005s0.957s101.16x
🐘 PostgresExpress0.054s (-6.0% 🟢)1.011s (~)0.956s101.32x
🐘 PostgresNitro0.055s (-42.4% 🟢)1.010s (-3.2%)0.955s101.32x
🐘 PostgresNext.js (Turbopack)0.057s1.011s0.953s101.38x
workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)1.116s2.006s0.890s101.00x
💻 LocalNitro1.129s (~)2.006s (~)0.877s101.01x
💻 LocalExpress1.130s (~)2.006s (~)0.877s101.01x
🐘 PostgresNext.js (Turbopack)1.141s2.010s0.869s101.02x
🐘 PostgresNitro1.143s (~)2.009s (~)0.867s101.02x
🐘 PostgresExpress1.148s (~)2.011s (~)0.862s101.03x
workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)10.794s11.025s0.231s31.00x
💻 LocalNitro10.946s (~)11.023s (~)0.078s31.01x
💻 LocalExpress10.952s (~)11.024s (~)0.071s31.01x
🐘 PostgresNitro10.957s (+0.8%)11.350s (+2.9%)0.393s31.02x
🐘 PostgresExpress10.961s (~)11.028s (~)0.067s31.02x
🐘 PostgresNext.js (Turbopack)11.002s11.359s0.357s31.02x
workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro14.507s (-0.6%)15.023s (~)0.517s41.00x
🐘 PostgresNext.js (Turbopack)14.508s15.023s0.514s41.00x
💻 LocalNext.js (Turbopack)14.596s15.029s0.434s41.01x
🐘 PostgresExpress14.621s (~)15.027s (~)0.406s41.01x
💻 LocalNitro14.983s (-0.5%)15.030s (-6.2% 🟢)0.047s41.03x
💻 LocalExpress14.999s (~)15.279s (+1.7%)0.280s41.03x
workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)13.770s14.020s0.251s71.00x
🐘 PostgresNitro13.792s (-1.3%)14.021s (-2.0%)0.229s71.00x
🐘 PostgresExpress14.142s (+1.0%)14.880s (+2.0%)0.739s71.03x
💻 LocalNext.js (Turbopack)15.743s16.031s0.288s61.14x
💻 LocalExpress16.535s (~)17.031s (~)0.496s61.20x
💻 LocalNitro16.756s (~)17.031s (~)0.275s61.22x
Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.225s2.010s0.785s151.00x
🐘 PostgresExpress1.255s (~)2.009s (~)0.754s151.02x
🐘 PostgresNitro1.278s (~)2.009s (~)0.731s151.04x
💻 LocalExpress1.513s (+1.6%)2.005s (~)0.493s151.24x
💻 LocalNext.js (Turbopack)1.541s2.006s0.465s151.26x
💻 LocalNitro1.545s (-5.3% 🟢)2.006s (-3.3%)0.460s151.26x
Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.325s (-1.1%)3.009s (~)0.684s101.00x
🐘 PostgresNext.js (Turbopack)2.355s3.007s0.653s101.01x
🐘 PostgresExpress2.365s (~)3.010s (~)0.645s101.02x
💻 LocalNext.js (Turbopack)2.878s3.342s0.464s91.24x
💻 LocalExpress2.970s (+0.6%)3.342s (-3.2%)0.372s91.28x
💻 LocalNitro3.135s (~)3.761s (-3.2%)0.626s81.35x
Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro3.466s (~)4.013s (~)0.547s81.00x
🐘 PostgresExpress3.511s (+0.7%)4.014s (~)0.503s81.01x
🐘 PostgresNext.js (Turbopack)3.622s4.014s0.392s81.05x
💻 LocalNext.js (Turbopack)7.191s7.765s0.575s42.07x
💻 LocalExpress7.682s (-7.9% 🟢)8.023s (-11.1% 🟢)0.341s42.22x
💻 LocalNitro8.748s (+4.8%)9.270s (+2.8%)0.523s42.52x
Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.233s2.008s0.775s151.00x
🐘 PostgresNitro1.262s (~)2.008s (~)0.746s151.02x
🐘 PostgresExpress1.269s (+0.9%)2.009s (~)0.740s151.03x
💻 LocalNext.js (Turbopack)1.522s2.006s0.484s151.23x
💻 LocalNitro1.541s (-17.4% 🟢)2.006s (-14.3% 🟢)0.465s151.25x
💻 LocalExpress1.545s (-18.4% 🟢)2.006s (-15.1% 🟢)0.461s151.25x
Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.318s (-0.9%)3.009s (~)0.691s101.00x
🐘 PostgresExpress2.354s (+0.5%)3.010s (~)0.656s101.02x
🐘 PostgresNext.js (Turbopack)2.432s3.009s0.577s101.05x
💻 LocalNext.js (Turbopack)2.815s3.310s0.495s101.21x
💻 LocalExpress2.839s (-9.4% 🟢)3.108s (-17.4% 🟢)0.269s101.22x
💻 LocalNitro3.042s (-0.8%)3.885s (~)0.843s81.31x
Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro3.475s (~)4.011s (~)0.536s81.00x
🐘 PostgresExpress3.493s (~)4.011s (~)0.517s81.01x
🐘 PostgresNext.js (Turbopack)3.616s4.011s0.395s81.04x
💻 LocalNext.js (Turbopack)7.662s8.270s0.607s42.21x
💻 LocalExpress7.892s (-10.3% 🟢)8.270s (-10.8% 🟢)0.378s42.27x
💻 LocalNitro8.903s (-2.6%)9.275s (-7.5% 🟢)0.372s42.56x
workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.778s1.024s0.246s591.00x
🐘 PostgresNitro0.806s (-1.8%)1.006s (~)0.200s601.04x
🐘 PostgresExpress0.837s (~)1.023s (~)0.186s591.08x
💻 LocalNext.js (Turbopack)0.865s1.039s0.174s581.11x
💻 LocalNitro1.000s (+2.0%)1.309s (+19.7% 🔺)0.309s461.29x
💻 LocalExpress1.015s (+3.1%)1.672s (+55.4% 🔺)0.657s361.31x
workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.914s (-0.7%)2.075s (-1.2%)0.161s441.00x
🐘 PostgresNext.js (Turbopack)1.925s2.149s0.225s421.01x
🐘 PostgresExpress1.968s (~)2.316s (+2.6%)0.348s391.03x
💻 LocalNext.js (Turbopack)2.737s3.008s0.271s301.43x
💻 LocalNitro3.017s (-0.6%)3.649s (-2.9%)0.632s251.58x
💻 LocalExpress3.020s (~)3.610s (+0.7%)0.589s251.58x
workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)3.864s4.076s0.212s301.00x
🐘 PostgresNitro3.866s (-5.8% 🟢)4.042s (-12.2% 🟢)0.176s301.00x
🐘 PostgresExpress4.204s (+5.4% 🔺)5.011s (+14.7% 🔺)0.807s241.09x
💻 LocalNext.js (Turbopack)8.536s9.017s0.481s142.21x
💻 LocalExpress8.969s (-2.6%)9.325s (-6.9% 🟢)0.355s132.32x
💻 LocalNitro9.222s (-0.8%)9.942s (-0.8%)0.720s132.39x
workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.247s1.007s0.760s601.00x
🐘 PostgresNitro0.284s (~)1.006s (~)0.722s601.15x
🐘 PostgresExpress0.294s (+4.1%)1.007s (~)0.713s601.19x
💻 LocalExpress0.562s (~)1.004s (~)0.442s602.27x
💻 LocalNext.js (Turbopack)0.575s1.004s0.429s602.33x
💻 LocalNitro0.580s (-4.1%)1.004s (-1.7%)0.425s602.34x
workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.488s1.007s0.519s901.00x
🐘 PostgresExpress0.488s (-4.3%)1.007s (~)0.519s901.00x
🐘 PostgresNitro0.496s (~)1.006s (~)0.510s901.02x
💻 LocalExpress2.378s (-5.4% 🟢)3.009s (~)0.631s304.88x
💻 LocalNitro2.571s (+1.3%)3.009s (~)0.438s305.27x
💻 LocalNext.js (Turbopack)2.576s3.009s0.433s305.28x
workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.792s (-3.3%)1.008s (-0.9%)0.217s1201.00x
🐘 PostgresNitro0.796s (+0.7%)1.007s (~)0.211s1201.01x
🐘 PostgresNext.js (Turbopack)0.798s1.015s0.217s1191.01x
💻 LocalNext.js (Turbopack)10.221s10.940s0.718s1112.91x
💻 LocalExpress10.431s (-6.8% 🟢)11.028s (-7.6% 🟢)0.597s1113.18x
💻 LocalNitro11.794s (+5.4% 🔺)12.327s (+5.7% 🔺)0.534s1014.90x
Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)0.183s1.003s0.010s1.016s0.834s101.00x
🐘 PostgresNext.js (Turbopack)0.190s1.000s0.001s1.009s0.819s101.04x
🐘 PostgresNitro0.203s (-0.8%)0.996s (~)0.001s (-33.3% 🟢)1.008s (~)0.805s101.11x
💻 LocalNitro0.208s (-2.6%)1.004s (~)0.012s (-1.6%)1.019s (~)0.810s101.14x
🐘 PostgresExpress0.227s (+10.5% 🔺)0.996s (~)0.001s (-12.5% 🟢)1.011s (~)0.784s101.24x
💻 LocalExpress0.248s (+24.5% 🔺)1.004s (~)0.020s (+67.8% 🔺)1.027s (+0.8%)0.779s101.36x
stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.619s (-0.7%)1.026s (+1.9%)0.004s (-5.8% 🟢)1.046s (+2.3%)0.427s581.00x
🐘 PostgresExpress0.627s (-0.5%)1.005s (~)0.012s (+205.3% 🔺)1.030s (+0.6%)0.403s591.01x
🐘 PostgresNext.js (Turbopack)0.640s1.044s0.004s1.058s0.418s571.03x
💻 LocalNext.js (Turbopack)0.696s1.012s0.011s1.026s0.331s591.12x
💻 LocalNitro0.747s (-10.9% 🟢)1.012s (~)0.010s (+8.2% 🔺)1.023s (-8.3% 🟢)0.276s591.21x
💻 LocalExpress0.763s (+0.8%)1.012s (-1.6%)0.010s (+1.4%)1.024s (-1.6%)0.260s591.23x
10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.926s1.199s0.000s1.208s0.282s511.00x
🐘 PostgresNitro0.955s (-1.4%)1.168s (-6.4% 🟢)0.000s (-52.9% 🟢)1.193s (-5.2% 🟢)0.238s511.03x
🐘 PostgresExpress0.967s (+0.6%)1.246s (-2.5%)0.000s (-52.1% 🟢)1.259s (-3.7%)0.292s481.04x
💻 LocalExpress1.214s (-0.9%)2.020s (~)0.000s (-10.0% 🟢)2.022s (~)0.808s301.31x
💻 LocalNitro1.233s (+0.8%)2.021s (~)0.000s (+333.3% 🔺)2.023s (~)0.790s301.33x
💻 LocalNext.js (Turbopack)1.313s2.022s0.000s2.025s0.712s301.42x
fan-out fan-in 10 streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.732s (-3.3%)2.139s (~)0.000s (-100.0% 🟢)2.152s (-1.0%)0.419s281.00x
🐘 PostgresNext.js (Turbopack)1.803s2.106s0.000s2.138s0.334s291.04x
🐘 PostgresExpress1.817s (+2.6%)2.261s (+3.8%)0.000s (+Infinity% 🔺)2.270s (+3.2%)0.453s271.05x
💻 LocalExpress3.438s (-0.9%)4.032s (~)0.001s (-33.3% 🟢)4.035s (~)0.597s151.98x
💻 LocalNitro3.503s (+3.4%)4.032s (~)0.001s (~)4.035s (~)0.532s152.02x
💻 LocalNext.js (Turbopack)3.861s4.235s0.000s4.239s0.378s152.23x

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalNext.js (Turbopack)15/21
🐘 PostgresNext.js (Turbopack)10/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres18/21
Next.js (Turbopack)🐘 Postgres17/21
Nitro🐘 Postgres18/21
Column Definitions
  • Workflow Time: Runtime reported by workflow (completedAt - createdAt) - primary metric
  • TTFB: Time to First Byte - time from workflow start until first stream byte received (stream benchmarks only)
  • Slurp: Time from first byte to complete stream consumption (stream benchmarks only)
  • Wall Time: Total testbench time (trigger workflow + poll for result)
  • Overhead: Testbench overhead (Wall Time - Workflow Time)
  • Samples: Number of benchmark iterations run
  • vs Fastest: How much slower compared to the fastest configuration for this benchmark

Worlds:

  • 💻 Local: In-memory filesystem world (local development)
  • 🐘 Postgres: PostgreSQL database world (local development)
  • ▲ Vercel: Vercel production/preview deployment
  • 🌐 Turso: Community world (local development)
  • 🌐 MongoDB: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Jazz: Community world (local development)

📋 View full workflow run

@VaguelySeriousVaguelySerious left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(AI) I'm not sure how https://github.com/vercel/front/pull/68191 is a mirror of this, but as long as both ends work, LGTM. I haven't tried it out locally, so I'd want to re-test once the live deployment is back online, or before release

Comment threadpackages/web-shared/src/index.ts Outdated
Comment threadpackages/web-shared/src/components/ui/data-inspector.tsx
Comment threadpackages/web-shared/test/stream-display.test.ts Outdated
…lapseRefs tests
- Remove unused formatStreamChunkForDisplay/sanitizeStreamChunkForDisplay
exports; keep only the formatArrayBufferViewForDisplay path actually used
by DataInspector.
- Replace broken role=tablist/role=tab on the Decoded/Bytes switcher
with aria-pressed toggle-button semantics.
- Export collapseRefs/isBytesDisplay and add regression tests covering
typed-array detection (top-level, nested in object/array/Map/Set,
DataView exclusion).
@karthikscale3

Copy link
Copy Markdown
ContributorAuthor

(AI) I'm not sure how vercel/front#68191 is a mirror of this, but as long as both ends work, LGTM. I haven't tried it out locally, so I'd want to re-test once the live deployment is back online, or before release

Front to close but not exactly 1:1. I will make a separate PR to close the gap.

karthikscale3and others added 3 commits April 28, 2026 15:55
* Replace eval with JSON.parse in serialization revive helper
devalue.stringify() always produces valid JSON — special values
(undefined, NaN, Infinity, -0) are encoded as negative integer
sentinels. JSON.parse yields the same flattened array form that
unflatten() expects, without the eval anti-pattern (VULN-918).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Drop redundant workflow package from changeset
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Emits Uint8Array chunks containing multi-byte UTF-8 (Latin Extended,
CJK, emoji, RTL Arabic) plus a UTF-8 encoded JSON document, and
asserts each chunk round-trips through TextDecoder({ fatal: true }).
Exercises the same decode path the web inspector relies on for
typed-array stream values.
Made-with: Cursor
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport-stableCherry-pick this PR to the stable branch when merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@karthikscale3@VaguelySerious@pranaygp
, '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" + ' Decode UTF-8 stream chunks by karthikscale3 · Pull Request #1852 · vercel/workflow · GitHub
Skip to content

Decode UTF-8 stream chunks - #1852

Merged
karthikscale3 merged 9 commits into
mainfrom
codex/decode-typed-array-stream-chunks
Apr 29, 2026
Merged

Decode UTF-8 stream chunks#1852
karthikscale3 merged 9 commits into
mainfrom
codex/decode-typed-array-stream-chunks

Conversation

@karthikscale3

@karthikscale3karthikscale3 commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

What changed

  • Added shared formatArrayBufferViewForDisplay + summarizeArrayBufferView helpers in @workflow/web-shared (src/lib/stream-display.ts). They UTF-8 decode typed-array values via TextDecoder({ fatal: true }) and fall back to a compact Uint8Array(N) [..] summary for binary data.
  • DataInspector (src/components/ui/data-inspector.tsx) now collapses any ArrayBufferView (other than DataView) into a non-expandable BytesDisplay opaque ref via collapseRefs, then renders it with a DecodedBytesChunk that shows decoded text by default and a Decoded / Bytes toggle to inspect the summarized raw value. Decoded text that parses as JSON is re-rendered through a nested DataInspector.
  • stream-viewer.tsx no longer formats stream chunks itself — chunks flow as raw hydrated values straight into DataInspector, so Events tab payloads, Trace detail fields, and stream chunks all share the same typed-array rendering path.
  • packages/web/app/lib/hooks/use-stream-reader.ts is dramatically simplified (95 lines removed): it no longer pre-formats chunks, it just keeps them hydrated for the inspector.
  • Decoded / Bytes toggle uses aria-pressed + aria-label toggle-button semantics (not role="tab", since the panel content lives above the buttons rather than as a sibling).
  • Public API in src/index.ts exposes only the DecodedStreamChunkSource type from this module; the rendering helpers are intentionally internal.
  • New regression tests in test/collapse-refs.test.ts (9 cases): top-level typed array, nested in plain object, array, Map values, Set entries, non-Uint8Array views (Int16Array), DataView exclusion, binary fallback summary, and primitive passthrough.

Why

AI agent workflow stream deltas (e.g. helloAgentWorkflow) arrive as Uint8Array values serialized through devalue. The previous viewer collapsed them into compact byte summaries like Uint8Array(87) [65, 73, ...], which made the actual text content unreadable. Decoded text alone would be lossy for binary payloads. Routing everything through DataInspector → collapseRefs keeps the readable default, makes the byte-like source explicit and inspectable, and gives Events / Trace / stream views consistent behavior.

Validation

  • pnpm --filter @workflow/web-shared test → 22 passed (22)
  • pnpm --filter @workflow/web-shared typecheck → clean
  • pnpm --filter @workflow/web-shared build → clean
  • pnpm exec biome check on touched files → only pre-existing complexity warnings on untouched code

Reference

@vercel

vercelBot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

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

ProjectDeploymentActionsUpdated (UTC)
example-nextjs-workflow-turbopackReadyReadyPreview, CommentApr 28, 2026 11:30pm
example-nextjs-workflow-webpackReadyReadyPreview, CommentApr 28, 2026 11:30pm
example-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-astro-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-express-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-fastify-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-hono-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-nitro-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-nuxt-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-sveltekit-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-vite-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workflow-docsReadyReadyPreview, Comment, Open in v0Apr 28, 2026 11:30pm
workflow-swc-playgroundReadyReadyPreview, CommentApr 28, 2026 11:30pm
workflow-webReadyReadyPreview, CommentApr 28, 2026 11:30pm

@changeset-bot

changeset-botBot commented Apr 24, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: addd614

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

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

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

@github-actions

github-actionsBot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production9890671056
✅ 💻 Local Development10660861152
✅ 📦 Local Production10660861152
✅ 🐘 Local Postgres10660861152
✅ 🪟 Windows960096
✅ 📋 Other270018288
Total455303434896

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro8907
✅ example8907
✅ express8907
✅ fastify8907
✅ hono8907
✅ nextjs-turbopack9402
✅ nextjs-webpack9402
✅ nitro8907
✅ nuxt8907
✅ sveltekit8907
✅ vite8907
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable9006
✅ express-stable9006
✅ fastify-stable9006
✅ hono-stable9006
✅ nextjs-turbopack-canary77019
✅ nextjs-turbopack-stable9600
✅ nextjs-webpack-canary77019
✅ nextjs-webpack-stable9600
✅ nitro-stable9006
✅ nuxt-stable9006
✅ sveltekit-stable9006
✅ vite-stable9006
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable9006
✅ express-stable9006
✅ fastify-stable9006
✅ hono-stable9006
✅ nextjs-turbopack-canary77019
✅ nextjs-turbopack-stable9600
✅ nextjs-webpack-canary77019
✅ nextjs-webpack-stable9600
✅ nitro-stable9006
✅ nuxt-stable9006
✅ sveltekit-stable9006
✅ vite-stable9006
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable9006
✅ express-stable9006
✅ fastify-stable9006
✅ hono-stable9006
✅ nextjs-turbopack-canary77019
✅ nextjs-turbopack-stable9600
✅ nextjs-webpack-canary77019
✅ nextjs-webpack-stable9600
✅ nitro-stable9006
✅ nuxt-stable9006
✅ sveltekit-stable9006
✅ vite-stable9006
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack9600
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable9006
✅ e2e-local-postgres-nest-stable9006
✅ e2e-local-prod-nest-stable9006

📋 View full workflow run

@github-actions

github-actionsBot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

📊 Benchmark Results

📈 Comparing against baseline from main branch. Green 🟢 = faster, Red 🔺 = slower.

workflow with no steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro0.041s (-3.9%)1.005s (~)0.964s101.00x
💻 LocalExpress0.044s (-1.1%)1.005s (~)0.961s101.06x
💻 LocalNext.js (Turbopack)0.048s1.005s0.957s101.16x
🐘 PostgresExpress0.054s (-6.0% 🟢)1.011s (~)0.956s101.32x
🐘 PostgresNitro0.055s (-42.4% 🟢)1.010s (-3.2%)0.955s101.32x
🐘 PostgresNext.js (Turbopack)0.057s1.011s0.953s101.38x
workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)1.116s2.006s0.890s101.00x
💻 LocalNitro1.129s (~)2.006s (~)0.877s101.01x
💻 LocalExpress1.130s (~)2.006s (~)0.877s101.01x
🐘 PostgresNext.js (Turbopack)1.141s2.010s0.869s101.02x
🐘 PostgresNitro1.143s (~)2.009s (~)0.867s101.02x
🐘 PostgresExpress1.148s (~)2.011s (~)0.862s101.03x
workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)10.794s11.025s0.231s31.00x
💻 LocalNitro10.946s (~)11.023s (~)0.078s31.01x
💻 LocalExpress10.952s (~)11.024s (~)0.071s31.01x
🐘 PostgresNitro10.957s (+0.8%)11.350s (+2.9%)0.393s31.02x
🐘 PostgresExpress10.961s (~)11.028s (~)0.067s31.02x
🐘 PostgresNext.js (Turbopack)11.002s11.359s0.357s31.02x
workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro14.507s (-0.6%)15.023s (~)0.517s41.00x
🐘 PostgresNext.js (Turbopack)14.508s15.023s0.514s41.00x
💻 LocalNext.js (Turbopack)14.596s15.029s0.434s41.01x
🐘 PostgresExpress14.621s (~)15.027s (~)0.406s41.01x
💻 LocalNitro14.983s (-0.5%)15.030s (-6.2% 🟢)0.047s41.03x
💻 LocalExpress14.999s (~)15.279s (+1.7%)0.280s41.03x
workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)13.770s14.020s0.251s71.00x
🐘 PostgresNitro13.792s (-1.3%)14.021s (-2.0%)0.229s71.00x
🐘 PostgresExpress14.142s (+1.0%)14.880s (+2.0%)0.739s71.03x
💻 LocalNext.js (Turbopack)15.743s16.031s0.288s61.14x
💻 LocalExpress16.535s (~)17.031s (~)0.496s61.20x
💻 LocalNitro16.756s (~)17.031s (~)0.275s61.22x
Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.225s2.010s0.785s151.00x
🐘 PostgresExpress1.255s (~)2.009s (~)0.754s151.02x
🐘 PostgresNitro1.278s (~)2.009s (~)0.731s151.04x
💻 LocalExpress1.513s (+1.6%)2.005s (~)0.493s151.24x
💻 LocalNext.js (Turbopack)1.541s2.006s0.465s151.26x
💻 LocalNitro1.545s (-5.3% 🟢)2.006s (-3.3%)0.460s151.26x
Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.325s (-1.1%)3.009s (~)0.684s101.00x
🐘 PostgresNext.js (Turbopack)2.355s3.007s0.653s101.01x
🐘 PostgresExpress2.365s (~)3.010s (~)0.645s101.02x
💻 LocalNext.js (Turbopack)2.878s3.342s0.464s91.24x
💻 LocalExpress2.970s (+0.6%)3.342s (-3.2%)0.372s91.28x
💻 LocalNitro3.135s (~)3.761s (-3.2%)0.626s81.35x
Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro3.466s (~)4.013s (~)0.547s81.00x
🐘 PostgresExpress3.511s (+0.7%)4.014s (~)0.503s81.01x
🐘 PostgresNext.js (Turbopack)3.622s4.014s0.392s81.05x
💻 LocalNext.js (Turbopack)7.191s7.765s0.575s42.07x
💻 LocalExpress7.682s (-7.9% 🟢)8.023s (-11.1% 🟢)0.341s42.22x
💻 LocalNitro8.748s (+4.8%)9.270s (+2.8%)0.523s42.52x
Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.233s2.008s0.775s151.00x
🐘 PostgresNitro1.262s (~)2.008s (~)0.746s151.02x
🐘 PostgresExpress1.269s (+0.9%)2.009s (~)0.740s151.03x
💻 LocalNext.js (Turbopack)1.522s2.006s0.484s151.23x
💻 LocalNitro1.541s (-17.4% 🟢)2.006s (-14.3% 🟢)0.465s151.25x
💻 LocalExpress1.545s (-18.4% 🟢)2.006s (-15.1% 🟢)0.461s151.25x
Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.318s (-0.9%)3.009s (~)0.691s101.00x
🐘 PostgresExpress2.354s (+0.5%)3.010s (~)0.656s101.02x
🐘 PostgresNext.js (Turbopack)2.432s3.009s0.577s101.05x
💻 LocalNext.js (Turbopack)2.815s3.310s0.495s101.21x
💻 LocalExpress2.839s (-9.4% 🟢)3.108s (-17.4% 🟢)0.269s101.22x
💻 LocalNitro3.042s (-0.8%)3.885s (~)0.843s81.31x
Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro3.475s (~)4.011s (~)0.536s81.00x
🐘 PostgresExpress3.493s (~)4.011s (~)0.517s81.01x
🐘 PostgresNext.js (Turbopack)3.616s4.011s0.395s81.04x
💻 LocalNext.js (Turbopack)7.662s8.270s0.607s42.21x
💻 LocalExpress7.892s (-10.3% 🟢)8.270s (-10.8% 🟢)0.378s42.27x
💻 LocalNitro8.903s (-2.6%)9.275s (-7.5% 🟢)0.372s42.56x
workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.778s1.024s0.246s591.00x
🐘 PostgresNitro0.806s (-1.8%)1.006s (~)0.200s601.04x
🐘 PostgresExpress0.837s (~)1.023s (~)0.186s591.08x
💻 LocalNext.js (Turbopack)0.865s1.039s0.174s581.11x
💻 LocalNitro1.000s (+2.0%)1.309s (+19.7% 🔺)0.309s461.29x
💻 LocalExpress1.015s (+3.1%)1.672s (+55.4% 🔺)0.657s361.31x
workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.914s (-0.7%)2.075s (-1.2%)0.161s441.00x
🐘 PostgresNext.js (Turbopack)1.925s2.149s0.225s421.01x
🐘 PostgresExpress1.968s (~)2.316s (+2.6%)0.348s391.03x
💻 LocalNext.js (Turbopack)2.737s3.008s0.271s301.43x
💻 LocalNitro3.017s (-0.6%)3.649s (-2.9%)0.632s251.58x
💻 LocalExpress3.020s (~)3.610s (+0.7%)0.589s251.58x
workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)3.864s4.076s0.212s301.00x
🐘 PostgresNitro3.866s (-5.8% 🟢)4.042s (-12.2% 🟢)0.176s301.00x
🐘 PostgresExpress4.204s (+5.4% 🔺)5.011s (+14.7% 🔺)0.807s241.09x
💻 LocalNext.js (Turbopack)8.536s9.017s0.481s142.21x
💻 LocalExpress8.969s (-2.6%)9.325s (-6.9% 🟢)0.355s132.32x
💻 LocalNitro9.222s (-0.8%)9.942s (-0.8%)0.720s132.39x
workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.247s1.007s0.760s601.00x
🐘 PostgresNitro0.284s (~)1.006s (~)0.722s601.15x
🐘 PostgresExpress0.294s (+4.1%)1.007s (~)0.713s601.19x
💻 LocalExpress0.562s (~)1.004s (~)0.442s602.27x
💻 LocalNext.js (Turbopack)0.575s1.004s0.429s602.33x
💻 LocalNitro0.580s (-4.1%)1.004s (-1.7%)0.425s602.34x
workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.488s1.007s0.519s901.00x
🐘 PostgresExpress0.488s (-4.3%)1.007s (~)0.519s901.00x
🐘 PostgresNitro0.496s (~)1.006s (~)0.510s901.02x
💻 LocalExpress2.378s (-5.4% 🟢)3.009s (~)0.631s304.88x
💻 LocalNitro2.571s (+1.3%)3.009s (~)0.438s305.27x
💻 LocalNext.js (Turbopack)2.576s3.009s0.433s305.28x
workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.792s (-3.3%)1.008s (-0.9%)0.217s1201.00x
🐘 PostgresNitro0.796s (+0.7%)1.007s (~)0.211s1201.01x
🐘 PostgresNext.js (Turbopack)0.798s1.015s0.217s1191.01x
💻 LocalNext.js (Turbopack)10.221s10.940s0.718s1112.91x
💻 LocalExpress10.431s (-6.8% 🟢)11.028s (-7.6% 🟢)0.597s1113.18x
💻 LocalNitro11.794s (+5.4% 🔺)12.327s (+5.7% 🔺)0.534s1014.90x
Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)0.183s1.003s0.010s1.016s0.834s101.00x
🐘 PostgresNext.js (Turbopack)0.190s1.000s0.001s1.009s0.819s101.04x
🐘 PostgresNitro0.203s (-0.8%)0.996s (~)0.001s (-33.3% 🟢)1.008s (~)0.805s101.11x
💻 LocalNitro0.208s (-2.6%)1.004s (~)0.012s (-1.6%)1.019s (~)0.810s101.14x
🐘 PostgresExpress0.227s (+10.5% 🔺)0.996s (~)0.001s (-12.5% 🟢)1.011s (~)0.784s101.24x
💻 LocalExpress0.248s (+24.5% 🔺)1.004s (~)0.020s (+67.8% 🔺)1.027s (+0.8%)0.779s101.36x
stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.619s (-0.7%)1.026s (+1.9%)0.004s (-5.8% 🟢)1.046s (+2.3%)0.427s581.00x
🐘 PostgresExpress0.627s (-0.5%)1.005s (~)0.012s (+205.3% 🔺)1.030s (+0.6%)0.403s591.01x
🐘 PostgresNext.js (Turbopack)0.640s1.044s0.004s1.058s0.418s571.03x
💻 LocalNext.js (Turbopack)0.696s1.012s0.011s1.026s0.331s591.12x
💻 LocalNitro0.747s (-10.9% 🟢)1.012s (~)0.010s (+8.2% 🔺)1.023s (-8.3% 🟢)0.276s591.21x
💻 LocalExpress0.763s (+0.8%)1.012s (-1.6%)0.010s (+1.4%)1.024s (-1.6%)0.260s591.23x
10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.926s1.199s0.000s1.208s0.282s511.00x
🐘 PostgresNitro0.955s (-1.4%)1.168s (-6.4% 🟢)0.000s (-52.9% 🟢)1.193s (-5.2% 🟢)0.238s511.03x
🐘 PostgresExpress0.967s (+0.6%)1.246s (-2.5%)0.000s (-52.1% 🟢)1.259s (-3.7%)0.292s481.04x
💻 LocalExpress1.214s (-0.9%)2.020s (~)0.000s (-10.0% 🟢)2.022s (~)0.808s301.31x
💻 LocalNitro1.233s (+0.8%)2.021s (~)0.000s (+333.3% 🔺)2.023s (~)0.790s301.33x
💻 LocalNext.js (Turbopack)1.313s2.022s0.000s2.025s0.712s301.42x
fan-out fan-in 10 streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.732s (-3.3%)2.139s (~)0.000s (-100.0% 🟢)2.152s (-1.0%)0.419s281.00x
🐘 PostgresNext.js (Turbopack)1.803s2.106s0.000s2.138s0.334s291.04x
🐘 PostgresExpress1.817s (+2.6%)2.261s (+3.8%)0.000s (+Infinity% 🔺)2.270s (+3.2%)0.453s271.05x
💻 LocalExpress3.438s (-0.9%)4.032s (~)0.001s (-33.3% 🟢)4.035s (~)0.597s151.98x
💻 LocalNitro3.503s (+3.4%)4.032s (~)0.001s (~)4.035s (~)0.532s152.02x
💻 LocalNext.js (Turbopack)3.861s4.235s0.000s4.239s0.378s152.23x

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalNext.js (Turbopack)15/21
🐘 PostgresNext.js (Turbopack)10/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres18/21
Next.js (Turbopack)🐘 Postgres17/21
Nitro🐘 Postgres18/21
Column Definitions
  • Workflow Time: Runtime reported by workflow (completedAt - createdAt) - primary metric
  • TTFB: Time to First Byte - time from workflow start until first stream byte received (stream benchmarks only)
  • Slurp: Time from first byte to complete stream consumption (stream benchmarks only)
  • Wall Time: Total testbench time (trigger workflow + poll for result)
  • Overhead: Testbench overhead (Wall Time - Workflow Time)
  • Samples: Number of benchmark iterations run
  • vs Fastest: How much slower compared to the fastest configuration for this benchmark

Worlds:

  • 💻 Local: In-memory filesystem world (local development)
  • 🐘 Postgres: PostgreSQL database world (local development)
  • ▲ Vercel: Vercel production/preview deployment
  • 🌐 Turso: Community world (local development)
  • 🌐 MongoDB: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Jazz: Community world (local development)

📋 View full workflow run

@VaguelySeriousVaguelySerious left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(AI) I'm not sure how https://github.com/vercel/front/pull/68191 is a mirror of this, but as long as both ends work, LGTM. I haven't tried it out locally, so I'd want to re-test once the live deployment is back online, or before release

Comment threadpackages/web-shared/src/index.ts Outdated
Comment threadpackages/web-shared/src/components/ui/data-inspector.tsx
Comment threadpackages/web-shared/test/stream-display.test.ts Outdated
…lapseRefs tests
- Remove unused formatStreamChunkForDisplay/sanitizeStreamChunkForDisplay
exports; keep only the formatArrayBufferViewForDisplay path actually used
by DataInspector.
- Replace broken role=tablist/role=tab on the Decoded/Bytes switcher
with aria-pressed toggle-button semantics.
- Export collapseRefs/isBytesDisplay and add regression tests covering
typed-array detection (top-level, nested in object/array/Map/Set,
DataView exclusion).
@karthikscale3

Copy link
Copy Markdown
ContributorAuthor

(AI) I'm not sure how vercel/front#68191 is a mirror of this, but as long as both ends work, LGTM. I haven't tried it out locally, so I'd want to re-test once the live deployment is back online, or before release

Front to close but not exactly 1:1. I will make a separate PR to close the gap.

karthikscale3and others added 3 commits April 28, 2026 15:55
* Replace eval with JSON.parse in serialization revive helper
devalue.stringify() always produces valid JSON — special values
(undefined, NaN, Infinity, -0) are encoded as negative integer
sentinels. JSON.parse yields the same flattened array form that
unflatten() expects, without the eval anti-pattern (VULN-918).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Drop redundant workflow package from changeset
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Emits Uint8Array chunks containing multi-byte UTF-8 (Latin Extended,
CJK, emoji, RTL Arabic) plus a UTF-8 encoded JSON document, and
asserts each chunk round-trips through TextDecoder({ fatal: true }).
Exercises the same decode path the web inspector relies on for
typed-array stream values.
Made-with: Cursor
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport-stableCherry-pick this PR to the stable branch when merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@karthikscale3@VaguelySerious@pranaygp
, '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('^' + ".*" + ' Decode UTF-8 stream chunks by karthikscale3 · Pull Request #1852 · vercel/workflow · GitHub
Skip to content

Decode UTF-8 stream chunks - #1852

Merged
karthikscale3 merged 9 commits into
mainfrom
codex/decode-typed-array-stream-chunks
Apr 29, 2026
Merged

Decode UTF-8 stream chunks#1852
karthikscale3 merged 9 commits into
mainfrom
codex/decode-typed-array-stream-chunks

Conversation

@karthikscale3

@karthikscale3karthikscale3 commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

What changed

  • Added shared formatArrayBufferViewForDisplay + summarizeArrayBufferView helpers in @workflow/web-shared (src/lib/stream-display.ts). They UTF-8 decode typed-array values via TextDecoder({ fatal: true }) and fall back to a compact Uint8Array(N) [..] summary for binary data.
  • DataInspector (src/components/ui/data-inspector.tsx) now collapses any ArrayBufferView (other than DataView) into a non-expandable BytesDisplay opaque ref via collapseRefs, then renders it with a DecodedBytesChunk that shows decoded text by default and a Decoded / Bytes toggle to inspect the summarized raw value. Decoded text that parses as JSON is re-rendered through a nested DataInspector.
  • stream-viewer.tsx no longer formats stream chunks itself — chunks flow as raw hydrated values straight into DataInspector, so Events tab payloads, Trace detail fields, and stream chunks all share the same typed-array rendering path.
  • packages/web/app/lib/hooks/use-stream-reader.ts is dramatically simplified (95 lines removed): it no longer pre-formats chunks, it just keeps them hydrated for the inspector.
  • Decoded / Bytes toggle uses aria-pressed + aria-label toggle-button semantics (not role="tab", since the panel content lives above the buttons rather than as a sibling).
  • Public API in src/index.ts exposes only the DecodedStreamChunkSource type from this module; the rendering helpers are intentionally internal.
  • New regression tests in test/collapse-refs.test.ts (9 cases): top-level typed array, nested in plain object, array, Map values, Set entries, non-Uint8Array views (Int16Array), DataView exclusion, binary fallback summary, and primitive passthrough.

Why

AI agent workflow stream deltas (e.g. helloAgentWorkflow) arrive as Uint8Array values serialized through devalue. The previous viewer collapsed them into compact byte summaries like Uint8Array(87) [65, 73, ...], which made the actual text content unreadable. Decoded text alone would be lossy for binary payloads. Routing everything through DataInspector → collapseRefs keeps the readable default, makes the byte-like source explicit and inspectable, and gives Events / Trace / stream views consistent behavior.

Validation

  • pnpm --filter @workflow/web-shared test → 22 passed (22)
  • pnpm --filter @workflow/web-shared typecheck → clean
  • pnpm --filter @workflow/web-shared build → clean
  • pnpm exec biome check on touched files → only pre-existing complexity warnings on untouched code

Reference

@vercel

vercelBot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

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

ProjectDeploymentActionsUpdated (UTC)
example-nextjs-workflow-turbopackReadyReadyPreview, CommentApr 28, 2026 11:30pm
example-nextjs-workflow-webpackReadyReadyPreview, CommentApr 28, 2026 11:30pm
example-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-astro-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-express-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-fastify-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-hono-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-nitro-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-nuxt-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-sveltekit-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-vite-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workflow-docsReadyReadyPreview, Comment, Open in v0Apr 28, 2026 11:30pm
workflow-swc-playgroundReadyReadyPreview, CommentApr 28, 2026 11:30pm
workflow-webReadyReadyPreview, CommentApr 28, 2026 11:30pm

@changeset-bot

changeset-botBot commented Apr 24, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: addd614

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

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

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

@github-actions

github-actionsBot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production9890671056
✅ 💻 Local Development10660861152
✅ 📦 Local Production10660861152
✅ 🐘 Local Postgres10660861152
✅ 🪟 Windows960096
✅ 📋 Other270018288
Total455303434896

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro8907
✅ example8907
✅ express8907
✅ fastify8907
✅ hono8907
✅ nextjs-turbopack9402
✅ nextjs-webpack9402
✅ nitro8907
✅ nuxt8907
✅ sveltekit8907
✅ vite8907
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable9006
✅ express-stable9006
✅ fastify-stable9006
✅ hono-stable9006
✅ nextjs-turbopack-canary77019
✅ nextjs-turbopack-stable9600
✅ nextjs-webpack-canary77019
✅ nextjs-webpack-stable9600
✅ nitro-stable9006
✅ nuxt-stable9006
✅ sveltekit-stable9006
✅ vite-stable9006
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable9006
✅ express-stable9006
✅ fastify-stable9006
✅ hono-stable9006
✅ nextjs-turbopack-canary77019
✅ nextjs-turbopack-stable9600
✅ nextjs-webpack-canary77019
✅ nextjs-webpack-stable9600
✅ nitro-stable9006
✅ nuxt-stable9006
✅ sveltekit-stable9006
✅ vite-stable9006
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable9006
✅ express-stable9006
✅ fastify-stable9006
✅ hono-stable9006
✅ nextjs-turbopack-canary77019
✅ nextjs-turbopack-stable9600
✅ nextjs-webpack-canary77019
✅ nextjs-webpack-stable9600
✅ nitro-stable9006
✅ nuxt-stable9006
✅ sveltekit-stable9006
✅ vite-stable9006
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack9600
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable9006
✅ e2e-local-postgres-nest-stable9006
✅ e2e-local-prod-nest-stable9006

📋 View full workflow run

@github-actions

github-actionsBot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

📊 Benchmark Results

📈 Comparing against baseline from main branch. Green 🟢 = faster, Red 🔺 = slower.

workflow with no steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro0.041s (-3.9%)1.005s (~)0.964s101.00x
💻 LocalExpress0.044s (-1.1%)1.005s (~)0.961s101.06x
💻 LocalNext.js (Turbopack)0.048s1.005s0.957s101.16x
🐘 PostgresExpress0.054s (-6.0% 🟢)1.011s (~)0.956s101.32x
🐘 PostgresNitro0.055s (-42.4% 🟢)1.010s (-3.2%)0.955s101.32x
🐘 PostgresNext.js (Turbopack)0.057s1.011s0.953s101.38x
workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)1.116s2.006s0.890s101.00x
💻 LocalNitro1.129s (~)2.006s (~)0.877s101.01x
💻 LocalExpress1.130s (~)2.006s (~)0.877s101.01x
🐘 PostgresNext.js (Turbopack)1.141s2.010s0.869s101.02x
🐘 PostgresNitro1.143s (~)2.009s (~)0.867s101.02x
🐘 PostgresExpress1.148s (~)2.011s (~)0.862s101.03x
workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)10.794s11.025s0.231s31.00x
💻 LocalNitro10.946s (~)11.023s (~)0.078s31.01x
💻 LocalExpress10.952s (~)11.024s (~)0.071s31.01x
🐘 PostgresNitro10.957s (+0.8%)11.350s (+2.9%)0.393s31.02x
🐘 PostgresExpress10.961s (~)11.028s (~)0.067s31.02x
🐘 PostgresNext.js (Turbopack)11.002s11.359s0.357s31.02x
workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro14.507s (-0.6%)15.023s (~)0.517s41.00x
🐘 PostgresNext.js (Turbopack)14.508s15.023s0.514s41.00x
💻 LocalNext.js (Turbopack)14.596s15.029s0.434s41.01x
🐘 PostgresExpress14.621s (~)15.027s (~)0.406s41.01x
💻 LocalNitro14.983s (-0.5%)15.030s (-6.2% 🟢)0.047s41.03x
💻 LocalExpress14.999s (~)15.279s (+1.7%)0.280s41.03x
workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)13.770s14.020s0.251s71.00x
🐘 PostgresNitro13.792s (-1.3%)14.021s (-2.0%)0.229s71.00x
🐘 PostgresExpress14.142s (+1.0%)14.880s (+2.0%)0.739s71.03x
💻 LocalNext.js (Turbopack)15.743s16.031s0.288s61.14x
💻 LocalExpress16.535s (~)17.031s (~)0.496s61.20x
💻 LocalNitro16.756s (~)17.031s (~)0.275s61.22x
Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.225s2.010s0.785s151.00x
🐘 PostgresExpress1.255s (~)2.009s (~)0.754s151.02x
🐘 PostgresNitro1.278s (~)2.009s (~)0.731s151.04x
💻 LocalExpress1.513s (+1.6%)2.005s (~)0.493s151.24x
💻 LocalNext.js (Turbopack)1.541s2.006s0.465s151.26x
💻 LocalNitro1.545s (-5.3% 🟢)2.006s (-3.3%)0.460s151.26x
Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.325s (-1.1%)3.009s (~)0.684s101.00x
🐘 PostgresNext.js (Turbopack)2.355s3.007s0.653s101.01x
🐘 PostgresExpress2.365s (~)3.010s (~)0.645s101.02x
💻 LocalNext.js (Turbopack)2.878s3.342s0.464s91.24x
💻 LocalExpress2.970s (+0.6%)3.342s (-3.2%)0.372s91.28x
💻 LocalNitro3.135s (~)3.761s (-3.2%)0.626s81.35x
Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro3.466s (~)4.013s (~)0.547s81.00x
🐘 PostgresExpress3.511s (+0.7%)4.014s (~)0.503s81.01x
🐘 PostgresNext.js (Turbopack)3.622s4.014s0.392s81.05x
💻 LocalNext.js (Turbopack)7.191s7.765s0.575s42.07x
💻 LocalExpress7.682s (-7.9% 🟢)8.023s (-11.1% 🟢)0.341s42.22x
💻 LocalNitro8.748s (+4.8%)9.270s (+2.8%)0.523s42.52x
Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.233s2.008s0.775s151.00x
🐘 PostgresNitro1.262s (~)2.008s (~)0.746s151.02x
🐘 PostgresExpress1.269s (+0.9%)2.009s (~)0.740s151.03x
💻 LocalNext.js (Turbopack)1.522s2.006s0.484s151.23x
💻 LocalNitro1.541s (-17.4% 🟢)2.006s (-14.3% 🟢)0.465s151.25x
💻 LocalExpress1.545s (-18.4% 🟢)2.006s (-15.1% 🟢)0.461s151.25x
Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.318s (-0.9%)3.009s (~)0.691s101.00x
🐘 PostgresExpress2.354s (+0.5%)3.010s (~)0.656s101.02x
🐘 PostgresNext.js (Turbopack)2.432s3.009s0.577s101.05x
💻 LocalNext.js (Turbopack)2.815s3.310s0.495s101.21x
💻 LocalExpress2.839s (-9.4% 🟢)3.108s (-17.4% 🟢)0.269s101.22x
💻 LocalNitro3.042s (-0.8%)3.885s (~)0.843s81.31x
Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro3.475s (~)4.011s (~)0.536s81.00x
🐘 PostgresExpress3.493s (~)4.011s (~)0.517s81.01x
🐘 PostgresNext.js (Turbopack)3.616s4.011s0.395s81.04x
💻 LocalNext.js (Turbopack)7.662s8.270s0.607s42.21x
💻 LocalExpress7.892s (-10.3% 🟢)8.270s (-10.8% 🟢)0.378s42.27x
💻 LocalNitro8.903s (-2.6%)9.275s (-7.5% 🟢)0.372s42.56x
workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.778s1.024s0.246s591.00x
🐘 PostgresNitro0.806s (-1.8%)1.006s (~)0.200s601.04x
🐘 PostgresExpress0.837s (~)1.023s (~)0.186s591.08x
💻 LocalNext.js (Turbopack)0.865s1.039s0.174s581.11x
💻 LocalNitro1.000s (+2.0%)1.309s (+19.7% 🔺)0.309s461.29x
💻 LocalExpress1.015s (+3.1%)1.672s (+55.4% 🔺)0.657s361.31x
workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.914s (-0.7%)2.075s (-1.2%)0.161s441.00x
🐘 PostgresNext.js (Turbopack)1.925s2.149s0.225s421.01x
🐘 PostgresExpress1.968s (~)2.316s (+2.6%)0.348s391.03x
💻 LocalNext.js (Turbopack)2.737s3.008s0.271s301.43x
💻 LocalNitro3.017s (-0.6%)3.649s (-2.9%)0.632s251.58x
💻 LocalExpress3.020s (~)3.610s (+0.7%)0.589s251.58x
workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)3.864s4.076s0.212s301.00x
🐘 PostgresNitro3.866s (-5.8% 🟢)4.042s (-12.2% 🟢)0.176s301.00x
🐘 PostgresExpress4.204s (+5.4% 🔺)5.011s (+14.7% 🔺)0.807s241.09x
💻 LocalNext.js (Turbopack)8.536s9.017s0.481s142.21x
💻 LocalExpress8.969s (-2.6%)9.325s (-6.9% 🟢)0.355s132.32x
💻 LocalNitro9.222s (-0.8%)9.942s (-0.8%)0.720s132.39x
workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.247s1.007s0.760s601.00x
🐘 PostgresNitro0.284s (~)1.006s (~)0.722s601.15x
🐘 PostgresExpress0.294s (+4.1%)1.007s (~)0.713s601.19x
💻 LocalExpress0.562s (~)1.004s (~)0.442s602.27x
💻 LocalNext.js (Turbopack)0.575s1.004s0.429s602.33x
💻 LocalNitro0.580s (-4.1%)1.004s (-1.7%)0.425s602.34x
workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.488s1.007s0.519s901.00x
🐘 PostgresExpress0.488s (-4.3%)1.007s (~)0.519s901.00x
🐘 PostgresNitro0.496s (~)1.006s (~)0.510s901.02x
💻 LocalExpress2.378s (-5.4% 🟢)3.009s (~)0.631s304.88x
💻 LocalNitro2.571s (+1.3%)3.009s (~)0.438s305.27x
💻 LocalNext.js (Turbopack)2.576s3.009s0.433s305.28x
workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.792s (-3.3%)1.008s (-0.9%)0.217s1201.00x
🐘 PostgresNitro0.796s (+0.7%)1.007s (~)0.211s1201.01x
🐘 PostgresNext.js (Turbopack)0.798s1.015s0.217s1191.01x
💻 LocalNext.js (Turbopack)10.221s10.940s0.718s1112.91x
💻 LocalExpress10.431s (-6.8% 🟢)11.028s (-7.6% 🟢)0.597s1113.18x
💻 LocalNitro11.794s (+5.4% 🔺)12.327s (+5.7% 🔺)0.534s1014.90x
Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)0.183s1.003s0.010s1.016s0.834s101.00x
🐘 PostgresNext.js (Turbopack)0.190s1.000s0.001s1.009s0.819s101.04x
🐘 PostgresNitro0.203s (-0.8%)0.996s (~)0.001s (-33.3% 🟢)1.008s (~)0.805s101.11x
💻 LocalNitro0.208s (-2.6%)1.004s (~)0.012s (-1.6%)1.019s (~)0.810s101.14x
🐘 PostgresExpress0.227s (+10.5% 🔺)0.996s (~)0.001s (-12.5% 🟢)1.011s (~)0.784s101.24x
💻 LocalExpress0.248s (+24.5% 🔺)1.004s (~)0.020s (+67.8% 🔺)1.027s (+0.8%)0.779s101.36x
stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.619s (-0.7%)1.026s (+1.9%)0.004s (-5.8% 🟢)1.046s (+2.3%)0.427s581.00x
🐘 PostgresExpress0.627s (-0.5%)1.005s (~)0.012s (+205.3% 🔺)1.030s (+0.6%)0.403s591.01x
🐘 PostgresNext.js (Turbopack)0.640s1.044s0.004s1.058s0.418s571.03x
💻 LocalNext.js (Turbopack)0.696s1.012s0.011s1.026s0.331s591.12x
💻 LocalNitro0.747s (-10.9% 🟢)1.012s (~)0.010s (+8.2% 🔺)1.023s (-8.3% 🟢)0.276s591.21x
💻 LocalExpress0.763s (+0.8%)1.012s (-1.6%)0.010s (+1.4%)1.024s (-1.6%)0.260s591.23x
10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.926s1.199s0.000s1.208s0.282s511.00x
🐘 PostgresNitro0.955s (-1.4%)1.168s (-6.4% 🟢)0.000s (-52.9% 🟢)1.193s (-5.2% 🟢)0.238s511.03x
🐘 PostgresExpress0.967s (+0.6%)1.246s (-2.5%)0.000s (-52.1% 🟢)1.259s (-3.7%)0.292s481.04x
💻 LocalExpress1.214s (-0.9%)2.020s (~)0.000s (-10.0% 🟢)2.022s (~)0.808s301.31x
💻 LocalNitro1.233s (+0.8%)2.021s (~)0.000s (+333.3% 🔺)2.023s (~)0.790s301.33x
💻 LocalNext.js (Turbopack)1.313s2.022s0.000s2.025s0.712s301.42x
fan-out fan-in 10 streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.732s (-3.3%)2.139s (~)0.000s (-100.0% 🟢)2.152s (-1.0%)0.419s281.00x
🐘 PostgresNext.js (Turbopack)1.803s2.106s0.000s2.138s0.334s291.04x
🐘 PostgresExpress1.817s (+2.6%)2.261s (+3.8%)0.000s (+Infinity% 🔺)2.270s (+3.2%)0.453s271.05x
💻 LocalExpress3.438s (-0.9%)4.032s (~)0.001s (-33.3% 🟢)4.035s (~)0.597s151.98x
💻 LocalNitro3.503s (+3.4%)4.032s (~)0.001s (~)4.035s (~)0.532s152.02x
💻 LocalNext.js (Turbopack)3.861s4.235s0.000s4.239s0.378s152.23x

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalNext.js (Turbopack)15/21
🐘 PostgresNext.js (Turbopack)10/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres18/21
Next.js (Turbopack)🐘 Postgres17/21
Nitro🐘 Postgres18/21
Column Definitions
  • Workflow Time: Runtime reported by workflow (completedAt - createdAt) - primary metric
  • TTFB: Time to First Byte - time from workflow start until first stream byte received (stream benchmarks only)
  • Slurp: Time from first byte to complete stream consumption (stream benchmarks only)
  • Wall Time: Total testbench time (trigger workflow + poll for result)
  • Overhead: Testbench overhead (Wall Time - Workflow Time)
  • Samples: Number of benchmark iterations run
  • vs Fastest: How much slower compared to the fastest configuration for this benchmark

Worlds:

  • 💻 Local: In-memory filesystem world (local development)
  • 🐘 Postgres: PostgreSQL database world (local development)
  • ▲ Vercel: Vercel production/preview deployment
  • 🌐 Turso: Community world (local development)
  • 🌐 MongoDB: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Jazz: Community world (local development)

📋 View full workflow run

@VaguelySeriousVaguelySerious left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(AI) I'm not sure how https://github.com/vercel/front/pull/68191 is a mirror of this, but as long as both ends work, LGTM. I haven't tried it out locally, so I'd want to re-test once the live deployment is back online, or before release

Comment threadpackages/web-shared/src/index.ts Outdated
Comment threadpackages/web-shared/src/components/ui/data-inspector.tsx
Comment threadpackages/web-shared/test/stream-display.test.ts Outdated
…lapseRefs tests
- Remove unused formatStreamChunkForDisplay/sanitizeStreamChunkForDisplay
exports; keep only the formatArrayBufferViewForDisplay path actually used
by DataInspector.
- Replace broken role=tablist/role=tab on the Decoded/Bytes switcher
with aria-pressed toggle-button semantics.
- Export collapseRefs/isBytesDisplay and add regression tests covering
typed-array detection (top-level, nested in object/array/Map/Set,
DataView exclusion).
@karthikscale3

Copy link
Copy Markdown
ContributorAuthor

(AI) I'm not sure how vercel/front#68191 is a mirror of this, but as long as both ends work, LGTM. I haven't tried it out locally, so I'd want to re-test once the live deployment is back online, or before release

Front to close but not exactly 1:1. I will make a separate PR to close the gap.

karthikscale3and others added 3 commits April 28, 2026 15:55
* Replace eval with JSON.parse in serialization revive helper
devalue.stringify() always produces valid JSON — special values
(undefined, NaN, Infinity, -0) are encoded as negative integer
sentinels. JSON.parse yields the same flattened array form that
unflatten() expects, without the eval anti-pattern (VULN-918).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Drop redundant workflow package from changeset
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Emits Uint8Array chunks containing multi-byte UTF-8 (Latin Extended,
CJK, emoji, RTL Arabic) plus a UTF-8 encoded JSON document, and
asserts each chunk round-trips through TextDecoder({ fatal: true }).
Exercises the same decode path the web inspector relies on for
typed-array stream values.
Made-with: Cursor
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport-stableCherry-pick this PR to the stable branch when merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@karthikscale3@VaguelySerious@pranaygp
, '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('^' + ".*" + ' Decode UTF-8 stream chunks by karthikscale3 · Pull Request #1852 · vercel/workflow · GitHub
Skip to content

Decode UTF-8 stream chunks - #1852

Merged
karthikscale3 merged 9 commits into
mainfrom
codex/decode-typed-array-stream-chunks
Apr 29, 2026
Merged

Decode UTF-8 stream chunks#1852
karthikscale3 merged 9 commits into
mainfrom
codex/decode-typed-array-stream-chunks

Conversation

@karthikscale3

@karthikscale3karthikscale3 commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

What changed

  • Added shared formatArrayBufferViewForDisplay + summarizeArrayBufferView helpers in @workflow/web-shared (src/lib/stream-display.ts). They UTF-8 decode typed-array values via TextDecoder({ fatal: true }) and fall back to a compact Uint8Array(N) [..] summary for binary data.
  • DataInspector (src/components/ui/data-inspector.tsx) now collapses any ArrayBufferView (other than DataView) into a non-expandable BytesDisplay opaque ref via collapseRefs, then renders it with a DecodedBytesChunk that shows decoded text by default and a Decoded / Bytes toggle to inspect the summarized raw value. Decoded text that parses as JSON is re-rendered through a nested DataInspector.
  • stream-viewer.tsx no longer formats stream chunks itself — chunks flow as raw hydrated values straight into DataInspector, so Events tab payloads, Trace detail fields, and stream chunks all share the same typed-array rendering path.
  • packages/web/app/lib/hooks/use-stream-reader.ts is dramatically simplified (95 lines removed): it no longer pre-formats chunks, it just keeps them hydrated for the inspector.
  • Decoded / Bytes toggle uses aria-pressed + aria-label toggle-button semantics (not role="tab", since the panel content lives above the buttons rather than as a sibling).
  • Public API in src/index.ts exposes only the DecodedStreamChunkSource type from this module; the rendering helpers are intentionally internal.
  • New regression tests in test/collapse-refs.test.ts (9 cases): top-level typed array, nested in plain object, array, Map values, Set entries, non-Uint8Array views (Int16Array), DataView exclusion, binary fallback summary, and primitive passthrough.

Why

AI agent workflow stream deltas (e.g. helloAgentWorkflow) arrive as Uint8Array values serialized through devalue. The previous viewer collapsed them into compact byte summaries like Uint8Array(87) [65, 73, ...], which made the actual text content unreadable. Decoded text alone would be lossy for binary payloads. Routing everything through DataInspector → collapseRefs keeps the readable default, makes the byte-like source explicit and inspectable, and gives Events / Trace / stream views consistent behavior.

Validation

  • pnpm --filter @workflow/web-shared test → 22 passed (22)
  • pnpm --filter @workflow/web-shared typecheck → clean
  • pnpm --filter @workflow/web-shared build → clean
  • pnpm exec biome check on touched files → only pre-existing complexity warnings on untouched code

Reference

@vercel

vercelBot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

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

ProjectDeploymentActionsUpdated (UTC)
example-nextjs-workflow-turbopackReadyReadyPreview, CommentApr 28, 2026 11:30pm
example-nextjs-workflow-webpackReadyReadyPreview, CommentApr 28, 2026 11:30pm
example-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-astro-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-express-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-fastify-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-hono-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-nitro-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-nuxt-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-sveltekit-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-vite-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workflow-docsReadyReadyPreview, Comment, Open in v0Apr 28, 2026 11:30pm
workflow-swc-playgroundReadyReadyPreview, CommentApr 28, 2026 11:30pm
workflow-webReadyReadyPreview, CommentApr 28, 2026 11:30pm

@changeset-bot

changeset-botBot commented Apr 24, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: addd614

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

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

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

@github-actions

github-actionsBot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production9890671056
✅ 💻 Local Development10660861152
✅ 📦 Local Production10660861152
✅ 🐘 Local Postgres10660861152
✅ 🪟 Windows960096
✅ 📋 Other270018288
Total455303434896

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro8907
✅ example8907
✅ express8907
✅ fastify8907
✅ hono8907
✅ nextjs-turbopack9402
✅ nextjs-webpack9402
✅ nitro8907
✅ nuxt8907
✅ sveltekit8907
✅ vite8907
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable9006
✅ express-stable9006
✅ fastify-stable9006
✅ hono-stable9006
✅ nextjs-turbopack-canary77019
✅ nextjs-turbopack-stable9600
✅ nextjs-webpack-canary77019
✅ nextjs-webpack-stable9600
✅ nitro-stable9006
✅ nuxt-stable9006
✅ sveltekit-stable9006
✅ vite-stable9006
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable9006
✅ express-stable9006
✅ fastify-stable9006
✅ hono-stable9006
✅ nextjs-turbopack-canary77019
✅ nextjs-turbopack-stable9600
✅ nextjs-webpack-canary77019
✅ nextjs-webpack-stable9600
✅ nitro-stable9006
✅ nuxt-stable9006
✅ sveltekit-stable9006
✅ vite-stable9006
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable9006
✅ express-stable9006
✅ fastify-stable9006
✅ hono-stable9006
✅ nextjs-turbopack-canary77019
✅ nextjs-turbopack-stable9600
✅ nextjs-webpack-canary77019
✅ nextjs-webpack-stable9600
✅ nitro-stable9006
✅ nuxt-stable9006
✅ sveltekit-stable9006
✅ vite-stable9006
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack9600
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable9006
✅ e2e-local-postgres-nest-stable9006
✅ e2e-local-prod-nest-stable9006

📋 View full workflow run

@github-actions

github-actionsBot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

📊 Benchmark Results

📈 Comparing against baseline from main branch. Green 🟢 = faster, Red 🔺 = slower.

workflow with no steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro0.041s (-3.9%)1.005s (~)0.964s101.00x
💻 LocalExpress0.044s (-1.1%)1.005s (~)0.961s101.06x
💻 LocalNext.js (Turbopack)0.048s1.005s0.957s101.16x
🐘 PostgresExpress0.054s (-6.0% 🟢)1.011s (~)0.956s101.32x
🐘 PostgresNitro0.055s (-42.4% 🟢)1.010s (-3.2%)0.955s101.32x
🐘 PostgresNext.js (Turbopack)0.057s1.011s0.953s101.38x
workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)1.116s2.006s0.890s101.00x
💻 LocalNitro1.129s (~)2.006s (~)0.877s101.01x
💻 LocalExpress1.130s (~)2.006s (~)0.877s101.01x
🐘 PostgresNext.js (Turbopack)1.141s2.010s0.869s101.02x
🐘 PostgresNitro1.143s (~)2.009s (~)0.867s101.02x
🐘 PostgresExpress1.148s (~)2.011s (~)0.862s101.03x
workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)10.794s11.025s0.231s31.00x
💻 LocalNitro10.946s (~)11.023s (~)0.078s31.01x
💻 LocalExpress10.952s (~)11.024s (~)0.071s31.01x
🐘 PostgresNitro10.957s (+0.8%)11.350s (+2.9%)0.393s31.02x
🐘 PostgresExpress10.961s (~)11.028s (~)0.067s31.02x
🐘 PostgresNext.js (Turbopack)11.002s11.359s0.357s31.02x
workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro14.507s (-0.6%)15.023s (~)0.517s41.00x
🐘 PostgresNext.js (Turbopack)14.508s15.023s0.514s41.00x
💻 LocalNext.js (Turbopack)14.596s15.029s0.434s41.01x
🐘 PostgresExpress14.621s (~)15.027s (~)0.406s41.01x
💻 LocalNitro14.983s (-0.5%)15.030s (-6.2% 🟢)0.047s41.03x
💻 LocalExpress14.999s (~)15.279s (+1.7%)0.280s41.03x
workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)13.770s14.020s0.251s71.00x
🐘 PostgresNitro13.792s (-1.3%)14.021s (-2.0%)0.229s71.00x
🐘 PostgresExpress14.142s (+1.0%)14.880s (+2.0%)0.739s71.03x
💻 LocalNext.js (Turbopack)15.743s16.031s0.288s61.14x
💻 LocalExpress16.535s (~)17.031s (~)0.496s61.20x
💻 LocalNitro16.756s (~)17.031s (~)0.275s61.22x
Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.225s2.010s0.785s151.00x
🐘 PostgresExpress1.255s (~)2.009s (~)0.754s151.02x
🐘 PostgresNitro1.278s (~)2.009s (~)0.731s151.04x
💻 LocalExpress1.513s (+1.6%)2.005s (~)0.493s151.24x
💻 LocalNext.js (Turbopack)1.541s2.006s0.465s151.26x
💻 LocalNitro1.545s (-5.3% 🟢)2.006s (-3.3%)0.460s151.26x
Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.325s (-1.1%)3.009s (~)0.684s101.00x
🐘 PostgresNext.js (Turbopack)2.355s3.007s0.653s101.01x
🐘 PostgresExpress2.365s (~)3.010s (~)0.645s101.02x
💻 LocalNext.js (Turbopack)2.878s3.342s0.464s91.24x
💻 LocalExpress2.970s (+0.6%)3.342s (-3.2%)0.372s91.28x
💻 LocalNitro3.135s (~)3.761s (-3.2%)0.626s81.35x
Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro3.466s (~)4.013s (~)0.547s81.00x
🐘 PostgresExpress3.511s (+0.7%)4.014s (~)0.503s81.01x
🐘 PostgresNext.js (Turbopack)3.622s4.014s0.392s81.05x
💻 LocalNext.js (Turbopack)7.191s7.765s0.575s42.07x
💻 LocalExpress7.682s (-7.9% 🟢)8.023s (-11.1% 🟢)0.341s42.22x
💻 LocalNitro8.748s (+4.8%)9.270s (+2.8%)0.523s42.52x
Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.233s2.008s0.775s151.00x
🐘 PostgresNitro1.262s (~)2.008s (~)0.746s151.02x
🐘 PostgresExpress1.269s (+0.9%)2.009s (~)0.740s151.03x
💻 LocalNext.js (Turbopack)1.522s2.006s0.484s151.23x
💻 LocalNitro1.541s (-17.4% 🟢)2.006s (-14.3% 🟢)0.465s151.25x
💻 LocalExpress1.545s (-18.4% 🟢)2.006s (-15.1% 🟢)0.461s151.25x
Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.318s (-0.9%)3.009s (~)0.691s101.00x
🐘 PostgresExpress2.354s (+0.5%)3.010s (~)0.656s101.02x
🐘 PostgresNext.js (Turbopack)2.432s3.009s0.577s101.05x
💻 LocalNext.js (Turbopack)2.815s3.310s0.495s101.21x
💻 LocalExpress2.839s (-9.4% 🟢)3.108s (-17.4% 🟢)0.269s101.22x
💻 LocalNitro3.042s (-0.8%)3.885s (~)0.843s81.31x
Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro3.475s (~)4.011s (~)0.536s81.00x
🐘 PostgresExpress3.493s (~)4.011s (~)0.517s81.01x
🐘 PostgresNext.js (Turbopack)3.616s4.011s0.395s81.04x
💻 LocalNext.js (Turbopack)7.662s8.270s0.607s42.21x
💻 LocalExpress7.892s (-10.3% 🟢)8.270s (-10.8% 🟢)0.378s42.27x
💻 LocalNitro8.903s (-2.6%)9.275s (-7.5% 🟢)0.372s42.56x
workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.778s1.024s0.246s591.00x
🐘 PostgresNitro0.806s (-1.8%)1.006s (~)0.200s601.04x
🐘 PostgresExpress0.837s (~)1.023s (~)0.186s591.08x
💻 LocalNext.js (Turbopack)0.865s1.039s0.174s581.11x
💻 LocalNitro1.000s (+2.0%)1.309s (+19.7% 🔺)0.309s461.29x
💻 LocalExpress1.015s (+3.1%)1.672s (+55.4% 🔺)0.657s361.31x
workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.914s (-0.7%)2.075s (-1.2%)0.161s441.00x
🐘 PostgresNext.js (Turbopack)1.925s2.149s0.225s421.01x
🐘 PostgresExpress1.968s (~)2.316s (+2.6%)0.348s391.03x
💻 LocalNext.js (Turbopack)2.737s3.008s0.271s301.43x
💻 LocalNitro3.017s (-0.6%)3.649s (-2.9%)0.632s251.58x
💻 LocalExpress3.020s (~)3.610s (+0.7%)0.589s251.58x
workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)3.864s4.076s0.212s301.00x
🐘 PostgresNitro3.866s (-5.8% 🟢)4.042s (-12.2% 🟢)0.176s301.00x
🐘 PostgresExpress4.204s (+5.4% 🔺)5.011s (+14.7% 🔺)0.807s241.09x
💻 LocalNext.js (Turbopack)8.536s9.017s0.481s142.21x
💻 LocalExpress8.969s (-2.6%)9.325s (-6.9% 🟢)0.355s132.32x
💻 LocalNitro9.222s (-0.8%)9.942s (-0.8%)0.720s132.39x
workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.247s1.007s0.760s601.00x
🐘 PostgresNitro0.284s (~)1.006s (~)0.722s601.15x
🐘 PostgresExpress0.294s (+4.1%)1.007s (~)0.713s601.19x
💻 LocalExpress0.562s (~)1.004s (~)0.442s602.27x
💻 LocalNext.js (Turbopack)0.575s1.004s0.429s602.33x
💻 LocalNitro0.580s (-4.1%)1.004s (-1.7%)0.425s602.34x
workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.488s1.007s0.519s901.00x
🐘 PostgresExpress0.488s (-4.3%)1.007s (~)0.519s901.00x
🐘 PostgresNitro0.496s (~)1.006s (~)0.510s901.02x
💻 LocalExpress2.378s (-5.4% 🟢)3.009s (~)0.631s304.88x
💻 LocalNitro2.571s (+1.3%)3.009s (~)0.438s305.27x
💻 LocalNext.js (Turbopack)2.576s3.009s0.433s305.28x
workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.792s (-3.3%)1.008s (-0.9%)0.217s1201.00x
🐘 PostgresNitro0.796s (+0.7%)1.007s (~)0.211s1201.01x
🐘 PostgresNext.js (Turbopack)0.798s1.015s0.217s1191.01x
💻 LocalNext.js (Turbopack)10.221s10.940s0.718s1112.91x
💻 LocalExpress10.431s (-6.8% 🟢)11.028s (-7.6% 🟢)0.597s1113.18x
💻 LocalNitro11.794s (+5.4% 🔺)12.327s (+5.7% 🔺)0.534s1014.90x
Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)0.183s1.003s0.010s1.016s0.834s101.00x
🐘 PostgresNext.js (Turbopack)0.190s1.000s0.001s1.009s0.819s101.04x
🐘 PostgresNitro0.203s (-0.8%)0.996s (~)0.001s (-33.3% 🟢)1.008s (~)0.805s101.11x
💻 LocalNitro0.208s (-2.6%)1.004s (~)0.012s (-1.6%)1.019s (~)0.810s101.14x
🐘 PostgresExpress0.227s (+10.5% 🔺)0.996s (~)0.001s (-12.5% 🟢)1.011s (~)0.784s101.24x
💻 LocalExpress0.248s (+24.5% 🔺)1.004s (~)0.020s (+67.8% 🔺)1.027s (+0.8%)0.779s101.36x
stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.619s (-0.7%)1.026s (+1.9%)0.004s (-5.8% 🟢)1.046s (+2.3%)0.427s581.00x
🐘 PostgresExpress0.627s (-0.5%)1.005s (~)0.012s (+205.3% 🔺)1.030s (+0.6%)0.403s591.01x
🐘 PostgresNext.js (Turbopack)0.640s1.044s0.004s1.058s0.418s571.03x
💻 LocalNext.js (Turbopack)0.696s1.012s0.011s1.026s0.331s591.12x
💻 LocalNitro0.747s (-10.9% 🟢)1.012s (~)0.010s (+8.2% 🔺)1.023s (-8.3% 🟢)0.276s591.21x
💻 LocalExpress0.763s (+0.8%)1.012s (-1.6%)0.010s (+1.4%)1.024s (-1.6%)0.260s591.23x
10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.926s1.199s0.000s1.208s0.282s511.00x
🐘 PostgresNitro0.955s (-1.4%)1.168s (-6.4% 🟢)0.000s (-52.9% 🟢)1.193s (-5.2% 🟢)0.238s511.03x
🐘 PostgresExpress0.967s (+0.6%)1.246s (-2.5%)0.000s (-52.1% 🟢)1.259s (-3.7%)0.292s481.04x
💻 LocalExpress1.214s (-0.9%)2.020s (~)0.000s (-10.0% 🟢)2.022s (~)0.808s301.31x
💻 LocalNitro1.233s (+0.8%)2.021s (~)0.000s (+333.3% 🔺)2.023s (~)0.790s301.33x
💻 LocalNext.js (Turbopack)1.313s2.022s0.000s2.025s0.712s301.42x
fan-out fan-in 10 streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.732s (-3.3%)2.139s (~)0.000s (-100.0% 🟢)2.152s (-1.0%)0.419s281.00x
🐘 PostgresNext.js (Turbopack)1.803s2.106s0.000s2.138s0.334s291.04x
🐘 PostgresExpress1.817s (+2.6%)2.261s (+3.8%)0.000s (+Infinity% 🔺)2.270s (+3.2%)0.453s271.05x
💻 LocalExpress3.438s (-0.9%)4.032s (~)0.001s (-33.3% 🟢)4.035s (~)0.597s151.98x
💻 LocalNitro3.503s (+3.4%)4.032s (~)0.001s (~)4.035s (~)0.532s152.02x
💻 LocalNext.js (Turbopack)3.861s4.235s0.000s4.239s0.378s152.23x

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalNext.js (Turbopack)15/21
🐘 PostgresNext.js (Turbopack)10/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres18/21
Next.js (Turbopack)🐘 Postgres17/21
Nitro🐘 Postgres18/21
Column Definitions
  • Workflow Time: Runtime reported by workflow (completedAt - createdAt) - primary metric
  • TTFB: Time to First Byte - time from workflow start until first stream byte received (stream benchmarks only)
  • Slurp: Time from first byte to complete stream consumption (stream benchmarks only)
  • Wall Time: Total testbench time (trigger workflow + poll for result)
  • Overhead: Testbench overhead (Wall Time - Workflow Time)
  • Samples: Number of benchmark iterations run
  • vs Fastest: How much slower compared to the fastest configuration for this benchmark

Worlds:

  • 💻 Local: In-memory filesystem world (local development)
  • 🐘 Postgres: PostgreSQL database world (local development)
  • ▲ Vercel: Vercel production/preview deployment
  • 🌐 Turso: Community world (local development)
  • 🌐 MongoDB: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Jazz: Community world (local development)

📋 View full workflow run

@VaguelySeriousVaguelySerious left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(AI) I'm not sure how https://github.com/vercel/front/pull/68191 is a mirror of this, but as long as both ends work, LGTM. I haven't tried it out locally, so I'd want to re-test once the live deployment is back online, or before release

Comment threadpackages/web-shared/src/index.ts Outdated
Comment threadpackages/web-shared/src/components/ui/data-inspector.tsx
Comment threadpackages/web-shared/test/stream-display.test.ts Outdated
…lapseRefs tests
- Remove unused formatStreamChunkForDisplay/sanitizeStreamChunkForDisplay
exports; keep only the formatArrayBufferViewForDisplay path actually used
by DataInspector.
- Replace broken role=tablist/role=tab on the Decoded/Bytes switcher
with aria-pressed toggle-button semantics.
- Export collapseRefs/isBytesDisplay and add regression tests covering
typed-array detection (top-level, nested in object/array/Map/Set,
DataView exclusion).
@karthikscale3

Copy link
Copy Markdown
ContributorAuthor

(AI) I'm not sure how vercel/front#68191 is a mirror of this, but as long as both ends work, LGTM. I haven't tried it out locally, so I'd want to re-test once the live deployment is back online, or before release

Front to close but not exactly 1:1. I will make a separate PR to close the gap.

karthikscale3and others added 3 commits April 28, 2026 15:55
* Replace eval with JSON.parse in serialization revive helper
devalue.stringify() always produces valid JSON — special values
(undefined, NaN, Infinity, -0) are encoded as negative integer
sentinels. JSON.parse yields the same flattened array form that
unflatten() expects, without the eval anti-pattern (VULN-918).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Drop redundant workflow package from changeset
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Emits Uint8Array chunks containing multi-byte UTF-8 (Latin Extended,
CJK, emoji, RTL Arabic) plus a UTF-8 encoded JSON document, and
asserts each chunk round-trips through TextDecoder({ fatal: true }).
Exercises the same decode path the web inspector relies on for
typed-array stream values.
Made-with: Cursor
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport-stableCherry-pick this PR to the stable branch when merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@karthikscale3@VaguelySerious@pranaygp
, '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); } })(); })(); Decode UTF-8 stream chunks by karthikscale3 · Pull Request #1852 · vercel/workflow · GitHub
Skip to content

Decode UTF-8 stream chunks - #1852

Merged
karthikscale3 merged 9 commits into
mainfrom
codex/decode-typed-array-stream-chunks
Apr 29, 2026
Merged

Decode UTF-8 stream chunks#1852
karthikscale3 merged 9 commits into
mainfrom
codex/decode-typed-array-stream-chunks

Conversation

@karthikscale3

@karthikscale3karthikscale3 commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

What changed

  • Added shared formatArrayBufferViewForDisplay + summarizeArrayBufferView helpers in @workflow/web-shared (src/lib/stream-display.ts). They UTF-8 decode typed-array values via TextDecoder({ fatal: true }) and fall back to a compact Uint8Array(N) [..] summary for binary data.
  • DataInspector (src/components/ui/data-inspector.tsx) now collapses any ArrayBufferView (other than DataView) into a non-expandable BytesDisplay opaque ref via collapseRefs, then renders it with a DecodedBytesChunk that shows decoded text by default and a Decoded / Bytes toggle to inspect the summarized raw value. Decoded text that parses as JSON is re-rendered through a nested DataInspector.
  • stream-viewer.tsx no longer formats stream chunks itself — chunks flow as raw hydrated values straight into DataInspector, so Events tab payloads, Trace detail fields, and stream chunks all share the same typed-array rendering path.
  • packages/web/app/lib/hooks/use-stream-reader.ts is dramatically simplified (95 lines removed): it no longer pre-formats chunks, it just keeps them hydrated for the inspector.
  • Decoded / Bytes toggle uses aria-pressed + aria-label toggle-button semantics (not role="tab", since the panel content lives above the buttons rather than as a sibling).
  • Public API in src/index.ts exposes only the DecodedStreamChunkSource type from this module; the rendering helpers are intentionally internal.
  • New regression tests in test/collapse-refs.test.ts (9 cases): top-level typed array, nested in plain object, array, Map values, Set entries, non-Uint8Array views (Int16Array), DataView exclusion, binary fallback summary, and primitive passthrough.

Why

AI agent workflow stream deltas (e.g. helloAgentWorkflow) arrive as Uint8Array values serialized through devalue. The previous viewer collapsed them into compact byte summaries like Uint8Array(87) [65, 73, ...], which made the actual text content unreadable. Decoded text alone would be lossy for binary payloads. Routing everything through DataInspector → collapseRefs keeps the readable default, makes the byte-like source explicit and inspectable, and gives Events / Trace / stream views consistent behavior.

Validation

  • pnpm --filter @workflow/web-shared test → 22 passed (22)
  • pnpm --filter @workflow/web-shared typecheck → clean
  • pnpm --filter @workflow/web-shared build → clean
  • pnpm exec biome check on touched files → only pre-existing complexity warnings on untouched code

Reference

@vercel

vercelBot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

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

ProjectDeploymentActionsUpdated (UTC)
example-nextjs-workflow-turbopackReadyReadyPreview, CommentApr 28, 2026 11:30pm
example-nextjs-workflow-webpackReadyReadyPreview, CommentApr 28, 2026 11:30pm
example-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-astro-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-express-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-fastify-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-hono-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-nitro-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-nuxt-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-sveltekit-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workbench-vite-workflowReadyReadyPreview, CommentApr 28, 2026 11:30pm
workflow-docsReadyReadyPreview, Comment, Open in v0Apr 28, 2026 11:30pm
workflow-swc-playgroundReadyReadyPreview, CommentApr 28, 2026 11:30pm
workflow-webReadyReadyPreview, CommentApr 28, 2026 11:30pm

@changeset-bot

changeset-botBot commented Apr 24, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: addd614

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

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

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

@github-actions

github-actionsBot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production9890671056
✅ 💻 Local Development10660861152
✅ 📦 Local Production10660861152
✅ 🐘 Local Postgres10660861152
✅ 🪟 Windows960096
✅ 📋 Other270018288
Total455303434896

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro8907
✅ example8907
✅ express8907
✅ fastify8907
✅ hono8907
✅ nextjs-turbopack9402
✅ nextjs-webpack9402
✅ nitro8907
✅ nuxt8907
✅ sveltekit8907
✅ vite8907
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable9006
✅ express-stable9006
✅ fastify-stable9006
✅ hono-stable9006
✅ nextjs-turbopack-canary77019
✅ nextjs-turbopack-stable9600
✅ nextjs-webpack-canary77019
✅ nextjs-webpack-stable9600
✅ nitro-stable9006
✅ nuxt-stable9006
✅ sveltekit-stable9006
✅ vite-stable9006
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable9006
✅ express-stable9006
✅ fastify-stable9006
✅ hono-stable9006
✅ nextjs-turbopack-canary77019
✅ nextjs-turbopack-stable9600
✅ nextjs-webpack-canary77019
✅ nextjs-webpack-stable9600
✅ nitro-stable9006
✅ nuxt-stable9006
✅ sveltekit-stable9006
✅ vite-stable9006
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable9006
✅ express-stable9006
✅ fastify-stable9006
✅ hono-stable9006
✅ nextjs-turbopack-canary77019
✅ nextjs-turbopack-stable9600
✅ nextjs-webpack-canary77019
✅ nextjs-webpack-stable9600
✅ nitro-stable9006
✅ nuxt-stable9006
✅ sveltekit-stable9006
✅ vite-stable9006
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack9600
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable9006
✅ e2e-local-postgres-nest-stable9006
✅ e2e-local-prod-nest-stable9006

📋 View full workflow run

@github-actions

github-actionsBot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

📊 Benchmark Results

📈 Comparing against baseline from main branch. Green 🟢 = faster, Red 🔺 = slower.

workflow with no steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro0.041s (-3.9%)1.005s (~)0.964s101.00x
💻 LocalExpress0.044s (-1.1%)1.005s (~)0.961s101.06x
💻 LocalNext.js (Turbopack)0.048s1.005s0.957s101.16x
🐘 PostgresExpress0.054s (-6.0% 🟢)1.011s (~)0.956s101.32x
🐘 PostgresNitro0.055s (-42.4% 🟢)1.010s (-3.2%)0.955s101.32x
🐘 PostgresNext.js (Turbopack)0.057s1.011s0.953s101.38x
workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)1.116s2.006s0.890s101.00x
💻 LocalNitro1.129s (~)2.006s (~)0.877s101.01x
💻 LocalExpress1.130s (~)2.006s (~)0.877s101.01x
🐘 PostgresNext.js (Turbopack)1.141s2.010s0.869s101.02x
🐘 PostgresNitro1.143s (~)2.009s (~)0.867s101.02x
🐘 PostgresExpress1.148s (~)2.011s (~)0.862s101.03x
workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)10.794s11.025s0.231s31.00x
💻 LocalNitro10.946s (~)11.023s (~)0.078s31.01x
💻 LocalExpress10.952s (~)11.024s (~)0.071s31.01x
🐘 PostgresNitro10.957s (+0.8%)11.350s (+2.9%)0.393s31.02x
🐘 PostgresExpress10.961s (~)11.028s (~)0.067s31.02x
🐘 PostgresNext.js (Turbopack)11.002s11.359s0.357s31.02x
workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro14.507s (-0.6%)15.023s (~)0.517s41.00x
🐘 PostgresNext.js (Turbopack)14.508s15.023s0.514s41.00x
💻 LocalNext.js (Turbopack)14.596s15.029s0.434s41.01x
🐘 PostgresExpress14.621s (~)15.027s (~)0.406s41.01x
💻 LocalNitro14.983s (-0.5%)15.030s (-6.2% 🟢)0.047s41.03x
💻 LocalExpress14.999s (~)15.279s (+1.7%)0.280s41.03x
workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)13.770s14.020s0.251s71.00x
🐘 PostgresNitro13.792s (-1.3%)14.021s (-2.0%)0.229s71.00x
🐘 PostgresExpress14.142s (+1.0%)14.880s (+2.0%)0.739s71.03x
💻 LocalNext.js (Turbopack)15.743s16.031s0.288s61.14x
💻 LocalExpress16.535s (~)17.031s (~)0.496s61.20x
💻 LocalNitro16.756s (~)17.031s (~)0.275s61.22x
Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.225s2.010s0.785s151.00x
🐘 PostgresExpress1.255s (~)2.009s (~)0.754s151.02x
🐘 PostgresNitro1.278s (~)2.009s (~)0.731s151.04x
💻 LocalExpress1.513s (+1.6%)2.005s (~)0.493s151.24x
💻 LocalNext.js (Turbopack)1.541s2.006s0.465s151.26x
💻 LocalNitro1.545s (-5.3% 🟢)2.006s (-3.3%)0.460s151.26x
Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.325s (-1.1%)3.009s (~)0.684s101.00x
🐘 PostgresNext.js (Turbopack)2.355s3.007s0.653s101.01x
🐘 PostgresExpress2.365s (~)3.010s (~)0.645s101.02x
💻 LocalNext.js (Turbopack)2.878s3.342s0.464s91.24x
💻 LocalExpress2.970s (+0.6%)3.342s (-3.2%)0.372s91.28x
💻 LocalNitro3.135s (~)3.761s (-3.2%)0.626s81.35x
Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro3.466s (~)4.013s (~)0.547s81.00x
🐘 PostgresExpress3.511s (+0.7%)4.014s (~)0.503s81.01x
🐘 PostgresNext.js (Turbopack)3.622s4.014s0.392s81.05x
💻 LocalNext.js (Turbopack)7.191s7.765s0.575s42.07x
💻 LocalExpress7.682s (-7.9% 🟢)8.023s (-11.1% 🟢)0.341s42.22x
💻 LocalNitro8.748s (+4.8%)9.270s (+2.8%)0.523s42.52x
Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.233s2.008s0.775s151.00x
🐘 PostgresNitro1.262s (~)2.008s (~)0.746s151.02x
🐘 PostgresExpress1.269s (+0.9%)2.009s (~)0.740s151.03x
💻 LocalNext.js (Turbopack)1.522s2.006s0.484s151.23x
💻 LocalNitro1.541s (-17.4% 🟢)2.006s (-14.3% 🟢)0.465s151.25x
💻 LocalExpress1.545s (-18.4% 🟢)2.006s (-15.1% 🟢)0.461s151.25x
Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.318s (-0.9%)3.009s (~)0.691s101.00x
🐘 PostgresExpress2.354s (+0.5%)3.010s (~)0.656s101.02x
🐘 PostgresNext.js (Turbopack)2.432s3.009s0.577s101.05x
💻 LocalNext.js (Turbopack)2.815s3.310s0.495s101.21x
💻 LocalExpress2.839s (-9.4% 🟢)3.108s (-17.4% 🟢)0.269s101.22x
💻 LocalNitro3.042s (-0.8%)3.885s (~)0.843s81.31x
Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro3.475s (~)4.011s (~)0.536s81.00x
🐘 PostgresExpress3.493s (~)4.011s (~)0.517s81.01x
🐘 PostgresNext.js (Turbopack)3.616s4.011s0.395s81.04x
💻 LocalNext.js (Turbopack)7.662s8.270s0.607s42.21x
💻 LocalExpress7.892s (-10.3% 🟢)8.270s (-10.8% 🟢)0.378s42.27x
💻 LocalNitro8.903s (-2.6%)9.275s (-7.5% 🟢)0.372s42.56x
workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.778s1.024s0.246s591.00x
🐘 PostgresNitro0.806s (-1.8%)1.006s (~)0.200s601.04x
🐘 PostgresExpress0.837s (~)1.023s (~)0.186s591.08x
💻 LocalNext.js (Turbopack)0.865s1.039s0.174s581.11x
💻 LocalNitro1.000s (+2.0%)1.309s (+19.7% 🔺)0.309s461.29x
💻 LocalExpress1.015s (+3.1%)1.672s (+55.4% 🔺)0.657s361.31x
workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.914s (-0.7%)2.075s (-1.2%)0.161s441.00x
🐘 PostgresNext.js (Turbopack)1.925s2.149s0.225s421.01x
🐘 PostgresExpress1.968s (~)2.316s (+2.6%)0.348s391.03x
💻 LocalNext.js (Turbopack)2.737s3.008s0.271s301.43x
💻 LocalNitro3.017s (-0.6%)3.649s (-2.9%)0.632s251.58x
💻 LocalExpress3.020s (~)3.610s (+0.7%)0.589s251.58x
workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)3.864s4.076s0.212s301.00x
🐘 PostgresNitro3.866s (-5.8% 🟢)4.042s (-12.2% 🟢)0.176s301.00x
🐘 PostgresExpress4.204s (+5.4% 🔺)5.011s (+14.7% 🔺)0.807s241.09x
💻 LocalNext.js (Turbopack)8.536s9.017s0.481s142.21x
💻 LocalExpress8.969s (-2.6%)9.325s (-6.9% 🟢)0.355s132.32x
💻 LocalNitro9.222s (-0.8%)9.942s (-0.8%)0.720s132.39x
workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.247s1.007s0.760s601.00x
🐘 PostgresNitro0.284s (~)1.006s (~)0.722s601.15x
🐘 PostgresExpress0.294s (+4.1%)1.007s (~)0.713s601.19x
💻 LocalExpress0.562s (~)1.004s (~)0.442s602.27x
💻 LocalNext.js (Turbopack)0.575s1.004s0.429s602.33x
💻 LocalNitro0.580s (-4.1%)1.004s (-1.7%)0.425s602.34x
workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.488s1.007s0.519s901.00x
🐘 PostgresExpress0.488s (-4.3%)1.007s (~)0.519s901.00x
🐘 PostgresNitro0.496s (~)1.006s (~)0.510s901.02x
💻 LocalExpress2.378s (-5.4% 🟢)3.009s (~)0.631s304.88x
💻 LocalNitro2.571s (+1.3%)3.009s (~)0.438s305.27x
💻 LocalNext.js (Turbopack)2.576s3.009s0.433s305.28x
workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.792s (-3.3%)1.008s (-0.9%)0.217s1201.00x
🐘 PostgresNitro0.796s (+0.7%)1.007s (~)0.211s1201.01x
🐘 PostgresNext.js (Turbopack)0.798s1.015s0.217s1191.01x
💻 LocalNext.js (Turbopack)10.221s10.940s0.718s1112.91x
💻 LocalExpress10.431s (-6.8% 🟢)11.028s (-7.6% 🟢)0.597s1113.18x
💻 LocalNitro11.794s (+5.4% 🔺)12.327s (+5.7% 🔺)0.534s1014.90x
Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)0.183s1.003s0.010s1.016s0.834s101.00x
🐘 PostgresNext.js (Turbopack)0.190s1.000s0.001s1.009s0.819s101.04x
🐘 PostgresNitro0.203s (-0.8%)0.996s (~)0.001s (-33.3% 🟢)1.008s (~)0.805s101.11x
💻 LocalNitro0.208s (-2.6%)1.004s (~)0.012s (-1.6%)1.019s (~)0.810s101.14x
🐘 PostgresExpress0.227s (+10.5% 🔺)0.996s (~)0.001s (-12.5% 🟢)1.011s (~)0.784s101.24x
💻 LocalExpress0.248s (+24.5% 🔺)1.004s (~)0.020s (+67.8% 🔺)1.027s (+0.8%)0.779s101.36x
stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.619s (-0.7%)1.026s (+1.9%)0.004s (-5.8% 🟢)1.046s (+2.3%)0.427s581.00x
🐘 PostgresExpress0.627s (-0.5%)1.005s (~)0.012s (+205.3% 🔺)1.030s (+0.6%)0.403s591.01x
🐘 PostgresNext.js (Turbopack)0.640s1.044s0.004s1.058s0.418s571.03x
💻 LocalNext.js (Turbopack)0.696s1.012s0.011s1.026s0.331s591.12x
💻 LocalNitro0.747s (-10.9% 🟢)1.012s (~)0.010s (+8.2% 🔺)1.023s (-8.3% 🟢)0.276s591.21x
💻 LocalExpress0.763s (+0.8%)1.012s (-1.6%)0.010s (+1.4%)1.024s (-1.6%)0.260s591.23x
10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.926s1.199s0.000s1.208s0.282s511.00x
🐘 PostgresNitro0.955s (-1.4%)1.168s (-6.4% 🟢)0.000s (-52.9% 🟢)1.193s (-5.2% 🟢)0.238s511.03x
🐘 PostgresExpress0.967s (+0.6%)1.246s (-2.5%)0.000s (-52.1% 🟢)1.259s (-3.7%)0.292s481.04x
💻 LocalExpress1.214s (-0.9%)2.020s (~)0.000s (-10.0% 🟢)2.022s (~)0.808s301.31x
💻 LocalNitro1.233s (+0.8%)2.021s (~)0.000s (+333.3% 🔺)2.023s (~)0.790s301.33x
💻 LocalNext.js (Turbopack)1.313s2.022s0.000s2.025s0.712s301.42x
fan-out fan-in 10 streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.732s (-3.3%)2.139s (~)0.000s (-100.0% 🟢)2.152s (-1.0%)0.419s281.00x
🐘 PostgresNext.js (Turbopack)1.803s2.106s0.000s2.138s0.334s291.04x
🐘 PostgresExpress1.817s (+2.6%)2.261s (+3.8%)0.000s (+Infinity% 🔺)2.270s (+3.2%)0.453s271.05x
💻 LocalExpress3.438s (-0.9%)4.032s (~)0.001s (-33.3% 🟢)4.035s (~)0.597s151.98x
💻 LocalNitro3.503s (+3.4%)4.032s (~)0.001s (~)4.035s (~)0.532s152.02x
💻 LocalNext.js (Turbopack)3.861s4.235s0.000s4.239s0.378s152.23x

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalNext.js (Turbopack)15/21
🐘 PostgresNext.js (Turbopack)10/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres18/21
Next.js (Turbopack)🐘 Postgres17/21
Nitro🐘 Postgres18/21
Column Definitions
  • Workflow Time: Runtime reported by workflow (completedAt - createdAt) - primary metric
  • TTFB: Time to First Byte - time from workflow start until first stream byte received (stream benchmarks only)
  • Slurp: Time from first byte to complete stream consumption (stream benchmarks only)
  • Wall Time: Total testbench time (trigger workflow + poll for result)
  • Overhead: Testbench overhead (Wall Time - Workflow Time)
  • Samples: Number of benchmark iterations run
  • vs Fastest: How much slower compared to the fastest configuration for this benchmark

Worlds:

  • 💻 Local: In-memory filesystem world (local development)
  • 🐘 Postgres: PostgreSQL database world (local development)
  • ▲ Vercel: Vercel production/preview deployment
  • 🌐 Turso: Community world (local development)
  • 🌐 MongoDB: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Jazz: Community world (local development)

📋 View full workflow run

@VaguelySeriousVaguelySerious left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(AI) I'm not sure how https://github.com/vercel/front/pull/68191 is a mirror of this, but as long as both ends work, LGTM. I haven't tried it out locally, so I'd want to re-test once the live deployment is back online, or before release

Comment threadpackages/web-shared/src/index.ts Outdated
Comment threadpackages/web-shared/src/components/ui/data-inspector.tsx
Comment threadpackages/web-shared/test/stream-display.test.ts Outdated
…lapseRefs tests
- Remove unused formatStreamChunkForDisplay/sanitizeStreamChunkForDisplay
exports; keep only the formatArrayBufferViewForDisplay path actually used
by DataInspector.
- Replace broken role=tablist/role=tab on the Decoded/Bytes switcher
with aria-pressed toggle-button semantics.
- Export collapseRefs/isBytesDisplay and add regression tests covering
typed-array detection (top-level, nested in object/array/Map/Set,
DataView exclusion).
@karthikscale3

Copy link
Copy Markdown
ContributorAuthor

(AI) I'm not sure how vercel/front#68191 is a mirror of this, but as long as both ends work, LGTM. I haven't tried it out locally, so I'd want to re-test once the live deployment is back online, or before release

Front to close but not exactly 1:1. I will make a separate PR to close the gap.

karthikscale3and others added 3 commits April 28, 2026 15:55
* Replace eval with JSON.parse in serialization revive helper
devalue.stringify() always produces valid JSON — special values
(undefined, NaN, Infinity, -0) are encoded as negative integer
sentinels. JSON.parse yields the same flattened array form that
unflatten() expects, without the eval anti-pattern (VULN-918).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Drop redundant workflow package from changeset
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Emits Uint8Array chunks containing multi-byte UTF-8 (Latin Extended,
CJK, emoji, RTL Arabic) plus a UTF-8 encoded JSON document, and
asserts each chunk round-trips through TextDecoder({ fatal: true }).
Exercises the same decode path the web inspector relies on for
typed-array stream values.
Made-with: Cursor
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport-stableCherry-pick this PR to the stable branch when merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@karthikscale3@VaguelySerious@pranaygp