- decompress() now gates only on DecompressionStream (read path), not
CompressionStream — reads work in decompress-only runtimes (Copilot).
- Cross-deployment writes (start({deploymentId}), resumeHook) restrict to
the portable gzip codec via a new compressionPortableOnly flag. zstd
decode needs node:zlib >= 22.15, a property of the reader's runtime that
the SDK (engines: Node 18+) can't guarantee for a different deployment;
same-deployment writes still use zstd since reader == writer (vercel bot).
- Browser zstd WASM is now vendored into web-shared dist and referenced via
a relative new URL('./zstd.wasm', import.meta.url) — a bare package
specifier was left unrewritten by Vite and 404'd. Verified the Vite build
emits the asset (karthikscale3).
- hydrateResourceIOWithKey accepts an optional key and always registers the
zstd decoder, so unencrypted compressed payloads (e.g. local world) are
inflated; web no-key hydration paths now route through it (karthikscale3).
- Clarify the sync-decompress doc contract (best-effort via
process.getBuiltinModule, not "always on Node") (Copilot).
Tests: cross-deployment gzip fallback, unencrypted compressed web
hydration, and zstd WASM ↔ node:zlib compatibility.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Switch the payload compression codec to zstd, which benchmarks 3–7×
faster than gzip at an equal-or-better ratio on representative workloads
(compression runs at every step boundary, so the write CPU is a per-step
tax). zstd uses node:zlib (>= 22.15); gzip via the portable
CompressionStream remains the fallback when zstd is unavailable, and
WORKFLOW_COMPRESSION_CODEC=gzip forces it. Reads dispatch on the format
prefix, so 'zstd' and 'gzip' payloads are both always decodable.
zstd is Node-only (Web CompressionStream has no zstd), so the browser
o11y read path registers a WASM-backed decoder (@tootallnate/zstd-wasm)
via a new registerZstdDecoder hook; node:zlib handles Node-side reads
(runtime replay, CLI, server o11y). A new workflow.serialization.codec
span attribute reports which codec applied. gzip and zstd read support
co-ship, so the existing specVersion-5 capability gate is unchanged.
Verified end-to-end: spec-5 runs store zstd-prefixed payloads on disk
and replay/complete correctly; the WASM decoder round-trips node:zlib
zstd output. Benchmarks updated to compare zstd vs gzip.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(web-shared): RelativeTimeCard with shared ContextCard provider
Add a ContextCard provider/trigger and rebuild the timestamp tooltip as a
RelativeTimeCard, giving animated, collision-aware morphing hover cards.
Mount the shared provider in EventListView and AttributePanel.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Match vercel/front timestamp format for run/activity fields
Render absolute Created/Started/Completed (and sibling) timestamps using
date-fns in vercel/front's request/activity format (e.g.
"JUN 10 10:16:02.69 GMT-4") via the shared formatLocalMillisecondTime helper.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web-shared): register dark-theme/light-theme Tailwind variants
The context-card arrow tip stroke uses `dark-theme:[--context-card-tip-stroke:#252525]`,
but Tailwind v4 has no built-in `dark-theme` variant, so the utility was silently
dropped and the stroke fell back to its light `#DBDBDB` value — rendering as a white
caret in dark mode. Register the `dark-theme`/`light-theme` custom variants in
styles.css (mirroring vercel/front's geistcn tailwind.css, extended to match the
`.dark`/`[data-theme="dark"]` selectors this package and next-themes use).
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web-shared): match context card shadow to vercel/front
The --ds-shadow-tooltip token was guessed when added standalone, producing
an oversized/heavy drop shadow. Reproduce front's exact resolved value for
both light and dark themes (including the background-border layer).
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web-shared): bridge context card hover gap to stop flicker
The card is positioned `sideOffset` away from the trigger, leaving a
transparent un-hoverable gap that caused the hover card to flicker
(open → close → open) when moving the cursor onto it. Add a transparent
hover bridge inside the floating wrapper that extends the hover surface
by `sideOffset` to meet the trigger edge, keeping the visual spacing
while making the hover surface continuous.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Revert "fix(web-shared): bridge context card hover gap to stop flicker"
This reverts commit 2b961b1610.
* docs: simplify changeset description
Co-authored-by: Cursor <cursoragent@cursor.com>
* docs: remove vercel/front references from comments
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web-shared): only clear active context card when the active trigger unmounts
The unmount cleanup had an inverted guard: an unmounting inactive trigger
would clear the shared active card, hiding another trigger's card (and an
unmounting active trigger left a stale card). Guard on === id instead.
Co-authored-by: Cursor <cursoragent@cursor.com>
* docs: remove theme-variant comment
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
* Animate in-progress segments in the timeline
Add an animated diagonal "barber-pole" stripe overlay to in-progress
(running/received) segments in the new trace viewer timeline, so it's
obvious at a glance which work is still live.
The animation lives in a colocated CSS module (timeline.module.css),
imported by the component — web-shared is in consumers' transpilePackages,
so the keyframes ship with the component rather than relying on the global
styles.css (which Geist-using hosts don't import).
Also fixes a latent status bug this surfaced: the run-segment builders
collapsed every non-failed run to "running", so completed runs rendered as
"running" (and, with the new animation, kept animating). They now map to a
new terminal `completed` status (blue, static) via a fail-closed
runSegmentStatus helper — only genuinely in-progress runs animate.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Create chatty-walls-appear.md
Signed-off-by: Mitul Shah <mitulxshah@gmail.com>
---------
Signed-off-by: Mitul Shah <mitulxshah@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(web-shared): hydrate FatalError/RetryableError and Error subclasses in o11y
The web o11y reviver set was missing entries for the recently-added
serialization types (FatalError, RetryableError, the built-in Error
subclasses, AggregateError, DOMException), causing devalue.unflatten to
throw "Unknown type X" and the UI to surface "Failed to load resource
details" whenever a step or run failed with one of these error types.
Adds the missing revivers to getWebRevivers() and a regression test that
round-trips real values through the runtime's dehydrateStepError back
through the web reviver set.
* fix(web-shared): address review feedback on error revivers
- Pass `cause` through ErrorOptions to the subclass constructor instead of
assigning afterwards, matching `getCommonRevivers` in core. This gives
the resulting `cause` property the same engine-set, non-enumerable
semantics as a freshly thrown Error in the consumer realm.
- Guard `RetryableError.retryAfter` against missing/undefined values from
older runtime payloads — without it, `new Date(undefined)` produces an
Invalid Date rather than the property being absent. Add a defensive
test that drives the reviver directly with a payload missing the field.
The previous pre-release versions (4.x.y-beta.N) caused two issues:
- semver.inc('4.0.0-beta.N', 'major') returns 4.0.0, not 5.0.0
- Pre-release numbers carried over (beta.61 -> beta.62 instead of beta.0)
Setting all versions to 4.0.0 (non-pre-release) ensures a clean major
bump to 5.0.0-beta.0. Also removes @workflow/swc-playground-wasm from
the changeset and pre.json since it is a private package.
* feat: export semantic error types and add API reference documentation
Add missing error exports (HookNotFoundError, EntityConflictError,
RunExpiredError, TooEarlyError, ThrottleError, RunNotSupportedError,
WorkflowWorldError) to workflow/internal/errors. Create new error
classes for world-level semantics. Tighten TSDoc comments on all
error classes. Add API reference docs for all error types.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use @setup declarations, workflow/errors import, and errors/ doc section
- Replace @skip-typecheck with proper `declare` + `// @setup` lines
so code samples are typechecked but setup lines hidden from readers
- Add `workflow/errors` export to package.json (public API, replaces
`workflow/internal/errors` in docs)
- Add `workflow/errors` path mapping in docs-typecheck type-checker
- Add HookConflictError to re-export list
- Move all error docs under api-reference/workflow/errors/ subdirectory
- Update all internal cross-references and links
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: move error docs to top-level workflow-errors section
- Move semantic error docs to api-reference/workflow-errors/ (matching
the workflow/errors import path, like workflow-api for workflow/api)
- Keep FatalError and RetryableError in api-reference/workflow/ since
they're imported from workflow, not workflow/errors
- Fix all cross-reference links
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update HTTP debug logger JSDoc to clarify scope
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: make TooEarlyError.retryAfter a number (seconds) matching WorkflowWorldError
TooEarlyError.retryAfter is now seconds (number) instead of a Date,
consistent with ThrottleError and WorkflowWorldError. The conversion
from seconds to Date is done at the consumer site (step-handler) rather
than at construction time.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address review feedback on docs accuracy
- WorkflowWorldError docs: add status, code, url, retryAfter properties
to TSDoc; clarify that .is() only matches direct instances (not
subclasses); use instanceof in catch-all example
- TooEarlyError/ThrottleError docs: mark retryAfter as optional (?)
to match actual type definitions
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix trace viewer for v1 events
* add changeset
* add a test
* add a test for web-shared
* add lock file
* update logic to look for run_created event first