* nice
* refactor(web-shared): simplify resizable detail panel internals
Inline single-use constants in DraggableBorder and make comments
self-contained.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Fix compressed workflow data display
* Add OSS web no-key hydration regression
* Scope compression normalization to read paths; tidy hydration
Address review feedback on the compressed-data fix:
- world-vercel: keep gzip/zstd decompression on the o11y/display read
paths (getStep/getRun/getEvent/getWorkflowRunEvents/getHook) but not on
the runtime event-append path (world.events.create, createStep,
updateStep). That path is runtime-only and re-hydrates every payload via
the decompress-aware helpers, so decompressing at the adapter was
redundant work on the TTFB-sensitive run_started/inline-delta path and
skewed the runtime's deserialize compression telemetry to `codec: none`.
deserializeStep is now shape-only; normalizeStepData runs in the read
filter. Adds a regression test pinning the write-path pass-through.
- serialized-data: drop dead `errorRef`/`metadataRef` normalization (refs
are descriptor objects, never compressed byte payloads).
- web: in the wait-entity path, filter events by correlationId before
hydrating so an encryption key doesn't decrypt the whole event page.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Peter Wielander <peter.wielander@vercel.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Optimize Next dev HMR rebuilds
* Fix Next dev HMR CI coverage
* Gate dev HMR logs behind opt-in flag
* Match workflow dev build logs to Next style
* Fix Next dev HMR changed-file classification
* Fix Windows port detection
* Relax HMR log wait in dev e2e
* Avoid canary workflow execution cache flakes
* Allow slower Turbopack HMR propagation in e2e
* Scope canary HMR fuzz execution assertions
* fix(web-shared): align metadata panel styling with attributes section
Render hook metadata as key-value rows inside a DetailCard, matching
the Attributes section instead of a raw JSON block with a separate label.
* fix(web-shared): align top detail panel rows with attributes styling
Reuse DetailKeyValueRow for Module, Step ID, timestamps, etc. — same
tighter spacing, typography, and no dividers as the Attributes section.
* fix(web-shared): collapse top detail rows into Metadata with mono values
Wrap Module, Step ID, timestamps, etc. in a Metadata DetailCard and
render all values in monospace, including copyable paths and IDs.
* fix(web-shared): use contained header style for Metadata section
Add a contained DetailCard variant with rounded bg header and drop my-2
in favor of py-2 padding on the section wrapper.
* refactor(web-shared): drop reserved badge and use cn for row classes
Remove ReservedBadge and showReservedBadge; keep reserved-key sorting only.
Use cn() for conditional mono font classes in DetailKeyValueRow.
* refactor(web-shared): drop contained DetailCard variant
Use the default section DetailCard for Metadata, matching Attributes.
* feat(web-shared): extend cn with custom tailwind-merge class groups
Add a dedicated cn module that understands text-heading, text-label,
text-copy, text-button, and material utilities when merging classes.
* chore: note cn tailwind-merge update in changeset
* fix(web-shared): remove my-2 from DetailCard summary rows
Move vertical spacing to py-2 on the section container instead.
* fix(web-shared): scope Metadata spacing override
Restore shared DetailCard summary spacing for Input, Output, Events, and
Attributes while keeping Metadata tighter with a summaryClassName override.
* refactor(web-shared): use CVA variants for detail row value styling
Replace the mono boolean and one-off Metadata summary override with CVA-backed row variants and semantic mono row wrapper.
* fix(web-shared): use section padding for DetailCard spacing
Move spacing from summary margins to section padding and add content top
spacing so collapsed and expanded detail cards both breathe consistently.
* fix(web-shared): remove expanded Metadata content gap
Keep DetailCard's default expanded content spacing for data panels, but let
Metadata rows start directly below the title via contentClassName merge.
* refactor(web-shared): use Tailwind classes for disabled detail card
* refactor(web-shared): use DetailCard compound content
* refactor(web-shared): rebuild DetailCard as a compound component
Replace the monolithic DetailCard (summary/trailing/disabled props plus
child-type reflection for content) with a context-driven compound API:
DetailCard + DetailCard.Trigger + DetailCard.Content. Drop the dead
trailing branch, expose data-slot/data-state, and migrate all call sites.
* refactor(web-shared): rename DetailCard to Collapsible
It's a generic collapsible section, not a card-specific component. Rename
the component, its parts, data-slots, and the file accordingly.
* refactor(web-shared): split Collapsible into all-in-one + parts
Export a batteries-included <Collapsible label> for the common case so
consumers don't recompose the trigger/content every time, plus
CollapsibleRoot/CollapsibleTrigger/CollapsibleContent for the few call
sites that need to override part styling. Drop the dot-notation namespace.
* refactor(web-shared): move Collapsible into ui directory
It's a generic UI primitive, not sidebar-specific.
* refactor(web-shared): import cn from lib/cn directly
Drop the cn re-export from lib/utils; consumers import it from its
actual source instead of routing through utils.
* refactor(web-shared): drop cn tailwind-merge changes from this PR
Move the extended cn (lib/cn) work to a separate PR; this branch keeps
using the existing cn from lib/utils.
* ship it
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* Redrive on transient workflow-server transport failures instead of failing the run
A firewall in front of workflow-server shedding load with sustained 429/503
makes undici's shared RetryAgent exhaust its retries and throw
UND_ERR_REQ_RETRY. That raw error was rethrown unwrapped, so it was
classified as USER_ERROR and the replay terminal branch wrote run_failed —
permanently failing a run on a transient blip (or, in an outage, falling back
to the ~5min queue visibility-timeout redrive).
- world-vercel: map exhausted-retry / socket / connect / DNS / timeout
failures to a typed WorkflowWorldError (code TRANSPORT/TIMEOUT) by walking
the fetch() cause chain.
- core: add isRetryableWorldError (429 / 5xx / TRANSPORT / TIMEOUT) and
rethrow such errors from the replay terminal branch so the queue redrives
quickly (1s->60s backoff) instead of failing the run. Reuse it in start()
and step_started handling.
- world-vercel: surface the Vercel firewall x-vercel-mitigated
(challenge/deny) header alongside x-vercel-id in error diagnostics and logs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Address review: back off + cap on every retry path; fix mock; refine scope
Revises the transport-error handling per PR review (VaguelySerious,
karthikscale3).
Blocking fix — step_started no longer self-enqueues a throttled defer for
transient world errors. Returning `{ type: 'throttled', timeoutSeconds: 1 }`
acked the delivery and enqueued a fresh message, resetting the delivery count
so the path never backed off and never reached MAX_QUEUE_DELIVERIES — an
unbounded flat-1s loop if step_started kept failing. It now throws, so the
error flows through the replay loop's retryable-world-error rethrow and earns
both the delivery-count backoff and the max-delivery cap. Throwing is safe on
step_started (the body hasn't run; a write that landed dedupes to skipped).
Also in this revision:
- Backoff that lasts: raise the queue handler-error retry ceiling 60s -> 900s.
VQS clamps each redelivery to its 900s SQS limit and adds its own post-32
exponential, so ramping our base toward 900s stretches survival from ~3.7h to
most of the 24h message-visibility window. Corrected the stale
MAX_QUEUE_DELIVERIES comment to match the real VQS schedule.
- Stop amplifying firewall challenges: the undici RetryAgent no longer retries
429 in-process (a challenge is a 429 the client can't solve). 429s surface
immediately as ThrottleError carrying x-vercel-mitigated / x-vercel-id, so
the diagnostic header now reaches us for the challenge case too.
- Track world faults as WORLD_CONTRACT_ERROR (not USER_ERROR) in
classifyRunError so an outage isn't attributed to user code.
- Fix queue.test.ts mock that `biome check --write` had rewritten from a
newable `function` into an arrow (broke `new QueueClient`); pin with a
biome-ignore.
All Vercel-specific logic stays in @workflow/world-vercel; @workflow/core
operates only on the generic WorkflowWorldError abstraction.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Update .changeset/transport-error-redrive.md
Signed-off-by: Peter Wielander <mittgfu@gmail.com>
* Trim changeset to a single sentence per review
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Route firewall-challenge 429s to the retryable transport path, not ThrottleError
A 429 carrying `x-vercel-mitigated: challenge` is a firewall challenge our
server-to-server client cannot solve, so it recurs for the life of the
incident. Mapping it to `ThrottleError` meant the `step_started` write deferred
it as `{ type: 'throttled' }`, which self-enqueues a FRESH queue message and
resets the delivery count — so it never backed off past `retryAfter` and never
reached `MAX_QUEUE_DELIVERIES`, hot-looping against an already-overloaded
firewall (the exact amplification this PR set out to remove, and contrary to
the "step_started can't loop unbounded" invariant, which only held for 5xx).
Map a challenge to a retryable transport `WorkflowWorldError` (`code:
'TRANSPORT'`) in both the v3 `makeRequest` and v4 `throwForErrorResponse`
(the hot event-write path) error mappings, via a shared `isFirewallChallenge429`
helper. It then propagates through the V1/V2 step paths and the replay loop's
retryable-world-error rethrow, earning the delivery-count backoff AND the
delivery cap. A genuine application-level 429 (no `challenge` mitigation) stays
a `ThrottleError` and keeps its `Retry-After`-paced defer.
Also correct the survival-window comments: with the 900s ceiling,
MAX_QUEUE_DELIVERIES=48 spans ~9-10h (~35,000s), not "the better part of 24h";
reaching 24h would need a higher delivery cap, not a higher per-hop ceiling.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Signed-off-by: Peter Wielander <mittgfu@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Peter Wielander <mittgfu@gmail.com>
Co-authored-by: Peter Wielander <peter.wielander@vercel.com>
* fix(world-vercel): retry idempotent event POSTs in-process to avoid step re-execution
undici's RetryAgent never retries a POST, so a transient transport blip (UND_ERR_REQ_RETRY, ECONNRESET, socket/headers timeout, transient 5xx) when committing a step's terminal event bubbles out, the queue redelivers, and the step's user code re-executes with attempt++ even though it already ran to completion.
workflow-server makes these writes idempotent in outcome: entity handlers run before the event-log row is inserted and state transitions are conditional writes excluding terminal states, so a retry whose original landed throws before any row is written and surfaces as a 409 the SDK already handles. This adds a bounded in-process retry (new event-retry.ts) gated by a validated per-event EVENT_RETRY_ELIGIBILITY map, excluding step_started (double-increments attempt), step_retrying (appends a duplicate row), and hook_received (no server guard).
Complements #2666, which routes completion-persistence failures to queue redelivery instead of recording them as user failures; this avoids the redelivery (and re-execution) for transient blips.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(world-vercel): address review on event-POST retry
- Don't retry external cancellation: drop AbortError from the transient set (keep self-timeout TimeoutError), so a caller-requested abort isn't re-issued or stalled by the backoff budget.
- Add DEBUG-gated logging on each retry and on retry exhaustion so an in-process retry vs. a fall-through to queue redelivery is distinguishable in logs.
- Clarify docs: a landed retry surfaces as 409 for most types, but run_started/attr_set return 200 success (not a 409).
- Add createWorkflowRunEvent integration tests (events-retry.test.ts): eventType is threaded into the retry wrapper, and the 404->HookNotFoundError mapping still fires after the retry loop.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Peter Wielander <mittgfu@gmail.com>
* Add selection-driven span-detail primitives
Extract the run/step/hook/sleep fetch+hydrate core out of useWorkflowResourceData
into a plain async fetchSpanDetailResource (no React state), and add a
selection-driven state machine in web-shared:
- deriveSpanDetailView / resourceNeedsFetchedDetail: pure view-model deriver
whose status (idle/loading/ready/error) is a function of (selection, fetched
detail), so it can never lag the selection.
- useSelectedSpanDetail: fetches a selected span's detail directly with a
request-token to drop stale/out-of-order responses.
* Drive trace detail panel from the span-detail state machine
Replace the cross-package selection round-trip (EntityDetailPanel useEffect ->
onSpanSelect -> page spanSelection state -> useWorkflowResourceData -> context)
with a single injected fetchSpanDetail capability:
- EntityDetailPanel consumes useSelectedSpanDetail; its loading state now stays
in phase with the selected span, so Input/Output no longer vanish and pop back
in while navigating.
- SidebarDataContext drops spanDetailData/Loading/Error + onSpanSelect for a
single fetchSpanDetail; RunDetailView injects it and drops the duplicate
spanSelection state.
- WorkflowTraceViewer / RunTraceView take fetchSpanDetail too.
* Test span-detail view-model transitions; add changeset
Cover deriveSpanDetailView (idle/loading/ready/error, stale-detail rejection,
hooks ready inline) and resourceNeedsFetchedDetail.
* Trim redundant/narrative comments in span-detail state machine
Comment-only cleanup: drop PR-narration and cross-file duplication from the
deriveSpanDetailView / useSelectedSpanDetail / EntityDetailPanel / fetchSpanDetail
doc comments, keeping the non-obvious intent (request-token, error scoping,
decrypt closure).
* delete pointless coments
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
- Add deprecation banners (with migration-guide link) to the DurableAgent and
WorkflowChatTransport API references in v4 and v5; keep the full API surface intact
- Bring v4 headline guides to parity with v5's WorkflowAgent migration (ai/index,
foundations/streaming, the cookbook recipe + index)
- Convert standard agent examples (defining-tools, message-queueing) to WorkflowAgent
and reframe the streamText-vs comparison page
- Banner + repoint the deep recipes that stream custom UIMessageChunk data parts
(chat-session-modeling, human-in-the-loop, agent-cancellation, serializable-steps) —
that pattern doesn't map to WorkflowAgent's ModelCallStreamPart model, so their
legacy DurableAgent examples are kept behind a clear deprecation banner
- Point all WorkflowChatTransport examples at the @ai-sdk/workflow 1:1 port
- Rename the cookbook agent-patterns recipe to WorkflowAgent
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>