From 2677653759aa34c5c9fe28950fe1a02cec294551 Mon Sep 17 00:00:00 2001 From: Andrew Barba Date: Fri, 31 Jul 2026 11:27:35 -0400 Subject: [PATCH 1/7] fix(world-local): bound stalled queue deliveries (#3255) Signed-off-by: Andrew Barba --- .changeset/world-local-bounded-deliveries.md | 5 + docs/content/docs/v5/configuration/worlds.mdx | 12 ++ docs/content/worlds/v5/local.mdx | 8 ++ packages/world-local/src/queue.test.ts | 125 +++++++++++++++++- packages/world-local/src/queue.ts | 67 ++++++---- 5 files changed, 192 insertions(+), 25 deletions(-) create mode 100644 .changeset/world-local-bounded-deliveries.md diff --git a/.changeset/world-local-bounded-deliveries.md b/.changeset/world-local-bounded-deliveries.md new file mode 100644 index 0000000000..47499e6a22 --- /dev/null +++ b/.changeset/world-local-bounded-deliveries.md @@ -0,0 +1,5 @@ +--- +'@workflow/world-local': patch +--- + +Bound stalled local queue deliveries and redeliver their durable messages instead of waiting indefinitely. diff --git a/docs/content/docs/v5/configuration/worlds.mdx b/docs/content/docs/v5/configuration/worlds.mdx index 04fdf1316f..20c1e27e24 100644 --- a/docs/content/docs/v5/configuration/worlds.mdx +++ b/docs/content/docs/v5/configuration/worlds.mdx @@ -86,6 +86,18 @@ The Local World is the default outside Vercel and is intended for development. - Default: unlimited - Maximum seconds a local queue message stays hidden before the handler rechecks the run. +### `WORKFLOW_LOCAL_HEADERS_TIMEOUT_MS` + +- Factory option: none +- Default: `30000` +- Maximum milliseconds to wait for a local queue handler to begin responding before the durable message is redelivered. Set to `0` to disable. + +### `WORKFLOW_LOCAL_BODY_TIMEOUT_MS` + +- Factory option: none +- Default: `30000` +- Maximum gap in milliseconds between response body chunks from a local queue handler before the durable message is redelivered. Set to `0` to disable. + ### `recoverActiveRuns` - Environment variable: `WORKFLOW_LOCAL_RECOVER_ACTIVE_RUNS` diff --git a/docs/content/worlds/v5/local.mdx b/docs/content/worlds/v5/local.mdx index 5b04cb81c3..d98386442f 100644 --- a/docs/content/worlds/v5/local.mdx +++ b/docs/content/worlds/v5/local.mdx @@ -62,6 +62,14 @@ Maximum number of concurrent queue message handlers. Default: `1000` Maximum number of seconds a local queue message can stay hidden before the handler rechecks the run. Default: unlimited. +### `WORKFLOW_LOCAL_HEADERS_TIMEOUT_MS` + +Maximum time in milliseconds to wait for a local queue handler to begin responding before redelivering the durable message. Default: `30000`. Set to `0` to disable. + +### `WORKFLOW_LOCAL_BODY_TIMEOUT_MS` + +Maximum gap in milliseconds between response body chunks from a local queue handler before redelivering the durable message. Default: `30000`. Set to `0` to disable. + ### `WORKFLOW_LOCAL_RECOVER_ACTIVE_RUNS` Whether pending and running runs found in the data directory are re-enqueued when the world starts. Set to `0` or `false` to skip recovery and leave stale runs untouched. Default: `true` diff --git a/packages/world-local/src/queue.test.ts b/packages/world-local/src/queue.test.ts index fa345cd129..fd8acc862f 100644 --- a/packages/world-local/src/queue.test.ts +++ b/packages/world-local/src/queue.test.ts @@ -1,9 +1,16 @@ +import { createServer, type Server } from 'node:http'; +import type { AddressInfo } from 'node:net'; import { setWorkflowBasePath } from '@workflow/utils'; import type { WorkflowInvokePayload } from '@workflow/world'; import { MessageId, ValidQueueName } from '@workflow/world'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { z } from 'zod/v4'; -import { createQueue } from './queue'; +import { + createQueue, + DEFAULT_BODY_TIMEOUT_MS, + DEFAULT_HEADERS_TIMEOUT_MS, + getQueueAgentOptions, +} from './queue'; // Mock node:timers/promises so setTimeout resolves immediately vi.mock('node:timers/promises', () => ({ @@ -474,3 +481,119 @@ describe('transport-level delivery failures are retried (regression)', () => { expect(attempts[0]).toBe(1); }); }); + +describe('queue transport timeouts', () => { + const envKeys = [ + 'WORKFLOW_LOCAL_HEADERS_TIMEOUT_MS', + 'WORKFLOW_LOCAL_BODY_TIMEOUT_MS', + ] as const; + + let server: Server | undefined; + + afterEach(async () => { + for (const key of envKeys) delete process.env[key]; + if (server !== undefined) { + const toClose = server; + server = undefined; + toClose.closeAllConnections(); + await new Promise((resolve) => toClose.close(resolve)); + } + vi.restoreAllMocks(); + }); + + it('bounds queue requests by default', () => { + expect(getQueueAgentOptions()).toMatchObject({ + bodyTimeout: DEFAULT_BODY_TIMEOUT_MS, + headersTimeout: DEFAULT_HEADERS_TIMEOUT_MS, + }); + }); + + it('honors environment overrides, including 0', () => { + process.env.WORKFLOW_LOCAL_HEADERS_TIMEOUT_MS = '1234'; + process.env.WORKFLOW_LOCAL_BODY_TIMEOUT_MS = '0'; + expect(getQueueAgentOptions()).toMatchObject({ + bodyTimeout: 0, + headersTimeout: 1234, + }); + }); + + it('falls back for invalid environment overrides', () => { + process.env.WORKFLOW_LOCAL_HEADERS_TIMEOUT_MS = 'not-a-number'; + process.env.WORKFLOW_LOCAL_BODY_TIMEOUT_MS = '-1'; + expect(getQueueAgentOptions()).toMatchObject({ + bodyTimeout: DEFAULT_BODY_TIMEOUT_MS, + headersTimeout: DEFAULT_HEADERS_TIMEOUT_MS, + }); + }); + + it('redelivers when a handler accepts a request but never responds', async () => { + let requests = 0; + server = createServer((_request, response) => { + requests++; + if (requests === 1) return; + response.setHeader('content-type', 'application/json'); + response.end(JSON.stringify({ ok: true })); + }); + await new Promise((resolve) => { + server?.listen(0, '127.0.0.1', resolve); + }); + const { port } = server.address() as AddressInfo; + + process.env.WORKFLOW_LOCAL_HEADERS_TIMEOUT_MS = '150'; + const consoleError = vi + .spyOn(console, 'error') + .mockImplementation(() => {}); + const localQueue = createQueue({ + baseUrl: `http://127.0.0.1:${port}`, + }); + try { + await localQueue.queue('__wkf_workflow_test' as any, workflowPayload); + await vi.waitFor(() => expect(requests).toBe(2), { timeout: 5_000 }); + expect(consoleError).toHaveBeenCalledWith( + expect.stringContaining('Queue delivery failed at the transport'), + expect.objectContaining({ + error: expect.stringContaining('fetch failed'), + }) + ); + } finally { + await localQueue.close(); + } + }); + + it('redelivers when a handler response body stalls', async () => { + let requests = 0; + server = createServer((_request, response) => { + requests++; + response.setHeader('content-type', 'application/json'); + if (requests === 1) { + response.write('{"ok":'); + return; + } + response.end(JSON.stringify({ ok: true })); + }); + await new Promise((resolve) => { + server?.listen(0, '127.0.0.1', resolve); + }); + const { port } = server.address() as AddressInfo; + + process.env.WORKFLOW_LOCAL_BODY_TIMEOUT_MS = '150'; + const consoleError = vi + .spyOn(console, 'error') + .mockImplementation(() => {}); + const localQueue = createQueue({ + baseUrl: `http://127.0.0.1:${port}`, + }); + try { + await localQueue.queue('__wkf_workflow_test' as any, workflowPayload); + await vi.waitFor(() => expect(requests).toBe(2), { timeout: 5_000 }); + expect(consoleError).toHaveBeenCalledWith( + expect.stringContaining('Queue delivery failed at the transport'), + expect.objectContaining({ + error: expect.stringMatching(/terminated|fetch failed/), + }) + ); + } finally { + await localQueue.close(); + } + }); +}); diff --git a/packages/world-local/src/queue.ts b/packages/world-local/src/queue.ts index 2bfb645da7..cf651a9a12 100644 --- a/packages/world-local/src/queue.ts +++ b/packages/world-local/src/queue.ts @@ -61,6 +61,39 @@ const WORKFLOW_LOCAL_QUEUE_CONCURRENCY = parseInt(process.env.WORKFLOW_LOCAL_QUEUE_CONCURRENCY ?? '0', 10) || DEFAULT_CONCURRENCY_LIMIT; +/** Default time-to-first-byte deadline for local queue deliveries. */ +export const DEFAULT_HEADERS_TIMEOUT_MS = 30_000; + +/** Default maximum gap between response body chunks for local deliveries. */ +export const DEFAULT_BODY_TIMEOUT_MS = 30_000; + +function envTimeoutMs(name: string, fallback: number): number { + const raw = process.env[name]; + if (raw === undefined || raw === '') return fallback; + const parsed = Number.parseInt(raw, 10); + return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback; +} + +/** + * Bounds a stalled delivery below the queue handler's retry horizon. A + * transport timeout is retried by the delivery loop with the same durable + * message; `0` remains available for applications that need unbounded calls. + */ +export function getQueueAgentOptions() { + return { + bodyTimeout: envTimeoutMs( + 'WORKFLOW_LOCAL_BODY_TIMEOUT_MS', + DEFAULT_BODY_TIMEOUT_MS + ), + connections: 1000, + headersTimeout: envTimeoutMs( + 'WORKFLOW_LOCAL_HEADERS_TIMEOUT_MS', + DEFAULT_HEADERS_TIMEOUT_MS + ), + keepAliveTimeout: 30_000, + } as const; +} + export type DirectHandler = (req: Request) => Promise; export type LocalQueue = Queue & { @@ -95,16 +128,7 @@ function isDetachedArrayBufferQueueError(error: unknown): boolean { } export function createQueue(config: Partial): LocalQueue { - // Create a custom agent optimized for high-concurrency local workflows: - // - headersTimeout: 0 allows long-running steps - // - connections: 1000 allows many parallel connections to the same host - // - pipelining: 1 (default) for HTTP/1.1 compatibility - // - keepAliveTimeout: 30s keeps connections warm for rapid step execution - const httpAgent = new Agent({ - headersTimeout: 0, - connections: 1000, - keepAliveTimeout: 30_000, - }); + const httpAgent = new Agent(getQueueAgentOptions()); const transport = new TypedJsonTransport(); const generateId = monotonicFactory(); const semaphore = new Sema(WORKFLOW_LOCAL_QUEUE_CONCURRENCY); @@ -180,10 +204,8 @@ export function createQueue(config: Partial): LocalQueue { // ok, a timeoutSeconds re-delivery, or an HTTP error response). This — // not the loop counter — is the attempt the handler sees via // `x-vqs-message-attempt`, which it counts against MAX_QUEUE_DELIVERIES. - // Transport-level failures (below) never reach the handler, so they must - // not advance this, or a burst of "fetch failed"/ETIMEDOUT timeouts under - // local load would exhaust the handler's delivery budget before its first - // real execution. + // Failures before response headers do not advance this; body failures do, + // because the handler has already accepted that delivery. let delivery = 0; try { for (let loop = 0; loop < MAX_LOCAL_SAFETY_LIMIT; loop++) { @@ -196,6 +218,7 @@ export function createQueue(config: Partial): LocalQueue { }; const directHandler = directHandlers.get(prefix); let response: Response; + let text: string; try { if (directHandler) { @@ -220,14 +243,13 @@ export function createQueue(config: Partial): LocalQueue { } as any ); } + delivery++; + text = await response.text(); } catch (err) { - // The delivery never reached the handler: undici threw before a - // response. Under heavy local concurrency the single-process dev - // server can't accept every connection, so undici reports - // `TypeError: fetch failed` with an ETIMEDOUT/ECONNRESET cause. - // These are transient — back off and retry the *same* delivery - // rather than dropping the message (which would leave the step - // never started, with no retry). Two failures are not retryable: + // A transport can fail before response headers or while consuming + // the body. Both are transient — back off and retry the same + // durable message rather than leaving its run stalled. Two + // failures are not retryable: // - shutdown: close() aborted the agent / the backoff sleep. // - a detached-ArrayBuffer proxy misconfig, which never succeeds — // rethrow so the outer catch surfaces the actionable guidance. @@ -255,9 +277,6 @@ export function createQueue(config: Partial): LocalQueue { continue; } - delivery++; - const text = await response.text(); - if (response.ok) { try { const timeoutSeconds = Number(JSON.parse(text).timeoutSeconds); From 11dc036854749d154e73024d0109c3bfce462308 Mon Sep 17 00:00:00 2001 From: Pranay Prakash Date: Fri, 31 Jul 2026 10:09:36 -0700 Subject: [PATCH 2/7] ci: stop deploying changeset-release/main, run its e2e against production (#3243) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ci: stop deploying changeset-release/main, run its e2e against production The changesets action force-pushes `changeset-release/main`, and it can point at exactly main's HEAD SHA. Vercel keeps one commit status per project per SHA, so when both a production deployment (from main) and a preview deployment (from changeset-release/main) are built for the same commit, whichever finishes last owns the status. On 2026-07-30 the preview finished last, so `vercel/wait-for-deployment-action` — which reads the deployment ID out of that status — handed production e2e runs a preview deployment ID and forked runs across environments. Disable git deployments for that branch in every Vercel project rooted in this repo, and give the changeset PR's Vercel e2e lanes a deployment to test that actually exists: main's production deployment for the PR's base SHA, resolved by SHA so a mid-flight production build is waited out rather than silently replaced by an older one. Signed-off-by: Pranay Prakash * ci: resolve changeset-release e2e deployments with the wait action, tokenless Per review: with changeset-release/main no longer deployed, main SHAs can never again be deployed to a second environment of these projects, so the per-SHA commit status the action reads is unambiguous for exactly this lane. Reuse vercel/wait-for-deployment-action with environment: production and sha pinned to the PR base SHA instead of the Vercel-API polling script, drop the script and its VERCEL_TOKEN usage, and inherit the action's inactive/skipped-build handling. Signed-off-by: Pranay Prakash --------- Signed-off-by: Pranay Prakash --- .changeset/skip-changeset-release-deploys.md | 4 ++ .github/workflows/benchmarks.yml | 9 +++ .github/workflows/docs-checks.yml | 7 ++ .github/workflows/tarballs-checks.yml | 7 ++ .github/workflows/tests.yml | 75 ++++++++++++++++---- AGENTS.md | 8 +++ docs/vercel.json | 8 +++ packages/web/vercel.json | 8 +++ tarballs/vercel.json | 5 ++ workbench/astro/vercel.json | 5 ++ workbench/example/vercel.json | 5 ++ workbench/express/vercel.json | 5 ++ workbench/fastify/vercel.json | 5 ++ workbench/hono/vercel.json | 5 ++ workbench/nest/vercel.json | 5 ++ workbench/nextjs-turbopack/vercel.json | 5 ++ workbench/nextjs-webpack/vercel.json | 5 ++ workbench/nitro-v2/vercel.json | 5 ++ workbench/nitro-v3/vercel.json | 5 ++ workbench/nuxt/vercel.json | 5 ++ workbench/sveltekit/vercel.json | 5 ++ workbench/swc-playground/vercel.json | 5 ++ workbench/tanstack-start/vercel.json | 5 ++ workbench/vite/vercel.json | 5 ++ 24 files changed, 193 insertions(+), 13 deletions(-) create mode 100644 .changeset/skip-changeset-release-deploys.md create mode 100644 docs/vercel.json create mode 100644 packages/web/vercel.json diff --git a/.changeset/skip-changeset-release-deploys.md b/.changeset/skip-changeset-release-deploys.md new file mode 100644 index 0000000000..88d2fa197f --- /dev/null +++ b/.changeset/skip-changeset-release-deploys.md @@ -0,0 +1,4 @@ +--- +--- + +Stop deploying the `changeset-release/main` branch, and run that PR's Vercel e2e lanes against main's production deployment instead. diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 537b35f826..9a400c7a25 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -46,6 +46,7 @@ jobs: needs: ci-scope if: >- github.event_name == 'pull_request' && + !startsWith(github.head_ref, 'changeset-release/') && !contains(github.event.pull_request.labels.*.name, 'workflow-server-test') && needs.ci-scope.outputs.fast-path != 'true' timeout-minutes: 5 @@ -103,7 +104,14 @@ jobs: name: Benchmark (${{ matrix.target.world }}, ${{ matrix.target.app }}) runs-on: ubuntu-latest needs: ci-scope + # `changeset-release/*` PRs are not deployed (every project's vercel.json + # sets `git.deploymentEnabled` false for that branch, because the branch is + # force-pushed to main's HEAD SHA and a same-SHA preview deployment + # clobbers the per-SHA Vercel commit status), so there is nothing to wait + # for below. Benchmarking them would also be meaningless: the code is + # main's, so it would only compare main's baseline against itself. if: >- + !startsWith(github.head_ref, 'changeset-release/') && !contains(github.event.pull_request.labels.*.name, 'workflow-server-test') && (needs.ci-scope.outputs.fast-path != 'true' || github.event_name == 'workflow_dispatch') @@ -185,6 +193,7 @@ jobs: if: >- always() && !cancelled() && github.event_name == 'pull_request' && + !startsWith(github.head_ref, 'changeset-release/') && !contains(github.event.pull_request.labels.*.name, 'workflow-server-test') && needs.ci-scope.outputs.fast-path != 'true' timeout-minutes: 10 diff --git a/.github/workflows/docs-checks.yml b/.github/workflows/docs-checks.yml index bf0b86d6a2..baf2e899bd 100644 --- a/.github/workflows/docs-checks.yml +++ b/.github/workflows/docs-checks.yml @@ -46,6 +46,13 @@ jobs: docs-preview-smoke: name: Docs Preview Smoke Checks runs-on: ubuntu-latest + # `changeset-release/*` PRs are not deployed (every project's vercel.json + # sets `git.deploymentEnabled` false for that branch, because the branch is + # force-pushed to main's HEAD SHA and a same-SHA preview deployment + # clobbers the per-SHA Vercel commit status), so there is no preview to + # smoke-test. Their docs content is identical to main's, which this job + # already checked against production on the push that produced the bump. + if: ${{ !startsWith(github.head_ref, 'changeset-release/') }} timeout-minutes: 5 permissions: id-token: write diff --git a/.github/workflows/tarballs-checks.yml b/.github/workflows/tarballs-checks.yml index e4df6fa34d..f2945c6698 100644 --- a/.github/workflows/tarballs-checks.yml +++ b/.github/workflows/tarballs-checks.yml @@ -17,6 +17,13 @@ jobs: tarballs-preview-smoke: name: Tarballs Preview Smoke Checks runs-on: ubuntu-latest + # `changeset-release/*` PRs are not deployed (every project's vercel.json + # sets `git.deploymentEnabled` false for that branch, because the branch is + # force-pushed to main's HEAD SHA and a same-SHA preview deployment + # clobbers the per-SHA Vercel commit status), so there is no preview to + # smoke-test. The version-bumped tarballs get this same check on the push + # to main that follows the version PR merge. + if: ${{ !startsWith(github.head_ref, 'changeset-release/') }} timeout-minutes: 10 permissions: contents: read diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index bd4967820a..f45b94544a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -379,8 +379,18 @@ jobs: - name: Build CLI run: pnpm turbo run build --filter='@workflow/cli' + # `changeset-release/*` PRs (the changesets "Version Packages" PR) are + # deliberately not deployed: the branch is force-pushed and can point at + # main's HEAD SHA, and Vercel keeps one commit status per project per + # SHA, so a same-SHA preview deployment overwrites the production one + # that the step below reads the deployment ID from. Every project's + # vercel.json therefore sets `git.deploymentEnabled` false for that + # branch, which leaves these PRs with no deployment of their own to wait + # for. Their content is main plus a version-bump commit, so they run + # against the production deployment main already produced instead. - name: Waiting for the Vercel deployment id: waitForDeployment + if: ${{ !startsWith(github.head_ref, 'changeset-release/') }} uses: vercel/wait-for-deployment-action@0e2b0c5c5cce31f1648108aeec56467187aca037 with: project-slug: ${{ matrix.app.project-slug }} @@ -388,6 +398,24 @@ jobs: check-interval: 15 environment: ${{ github.ref == 'refs/heads/main' && 'production' || 'preview' }} + # The base SHA, not the PR head: that is the main commit whose + # production deployment carries the code under test. Waiting on it also + # means a production build still in flight is waited out instead of + # testing whatever happens to be aliased to production. The commit-status + # ID this action reads is unambiguous for main SHAs once this repo stops + # deploying `changeset-release/main` (see above) — that branch was the + # only one that ever deployed a commit main also deployed. + - name: Waiting for the Vercel deployment (changeset-release PR) + id: prodDeployment + if: ${{ startsWith(github.head_ref, 'changeset-release/') }} + uses: vercel/wait-for-deployment-action@0e2b0c5c5cce31f1648108aeec56467187aca037 + with: + project-slug: ${{ matrix.app.project-slug }} + timeout: 1000 + check-interval: 15 + environment: production + sha: ${{ github.event.pull_request.base.sha }} + - name: Record E2E start time id: e2eStart run: echo "ms=$(($(date +%s) * 1000))" >> "$GITHUB_OUTPUT" @@ -396,10 +424,12 @@ jobs: run: pnpm run test:e2e --reporter=verbose --reporter=json --reporter=./packages/core/e2e/github-reporter.ts "--outputFile=e2e-vercel-prod-$APP_NAME.json" env: NODE_OPTIONS: "--enable-source-maps" - DEPLOYMENT_URL: ${{ steps.waitForDeployment.outputs.deployment-url }} - VERCEL_DEPLOYMENT_ID: ${{ steps.waitForDeployment.outputs.deployment-id }} + DEPLOYMENT_URL: ${{ steps.waitForDeployment.outputs.deployment-url || steps.prodDeployment.outputs.deployment-url }} + VERCEL_DEPLOYMENT_ID: ${{ steps.waitForDeployment.outputs.deployment-id || steps.prodDeployment.outputs.deployment-id }} APP_NAME: ${{ matrix.app.name }} - WORKFLOW_VERCEL_ENV: ${{ github.ref == 'refs/heads/main' && 'production' || 'preview' }} + # changeset-release PRs test main's production deployment, so they + # must be treated as a production run everywhere downstream. + WORKFLOW_VERCEL_ENV: ${{ (github.ref == 'refs/heads/main' || startsWith(github.head_ref, 'changeset-release/')) && 'production' || 'preview' }} WORKFLOW_VERCEL_AUTH_TOKEN: ${{ secrets.VERCEL_LABS_TOKEN }} WORKFLOW_VERCEL_TEAM: "team_nO2mCG4W8IxPIeKoSsqwAxxB" WORKFLOW_VERCEL_PROJECT: ${{ matrix.app.project-id }} @@ -413,8 +443,11 @@ jobs: # a single pre-minted token that would expire mid-suite. # See: https://vercel.com/docs/deployment-protection/methods-to-bypass-deployment-protection/trusted-sources # Point PRs at the protected workflow-server preview; unset on main - # so production runs hit the public vercel-workflow.com URL. - VERCEL_WORKFLOW_SERVER_URL: ${{ github.ref != 'refs/heads/main' && secrets.VERCEL_WORKFLOW_SERVER_URL || '' }} + # so production runs hit the public vercel-workflow.com URL. Also + # unset for changeset-release PRs: they test a production deployment + # that is wired to the production workflow-server, and the harness + # has to read run state from the same server the app writes it to. + VERCEL_WORKFLOW_SERVER_URL: ${{ github.ref != 'refs/heads/main' && !startsWith(github.head_ref, 'changeset-release/') && secrets.VERCEL_WORKFLOW_SERVER_URL || '' }} - name: Capture runtime logs on failure if: failure() @@ -423,8 +456,8 @@ jobs: WORKFLOW_VERCEL_AUTH_TOKEN: ${{ secrets.VERCEL_LABS_TOKEN }} WORKFLOW_VERCEL_TEAM: "team_nO2mCG4W8IxPIeKoSsqwAxxB" WORKFLOW_VERCEL_PROJECT: ${{ matrix.app.project-id }} - WORKFLOW_VERCEL_ENV: ${{ github.ref == 'refs/heads/main' && 'production' || 'preview' }} - VERCEL_DEPLOYMENT_ID: ${{ steps.waitForDeployment.outputs.deployment-id }} + WORKFLOW_VERCEL_ENV: ${{ (github.ref == 'refs/heads/main' || startsWith(github.head_ref, 'changeset-release/')) && 'production' || 'preview' }} + VERCEL_DEPLOYMENT_ID: ${{ steps.waitForDeployment.outputs.deployment-id || steps.prodDeployment.outputs.deployment-id }} E2E_START_MS: ${{ steps.e2eStart.outputs.ms }} run: node .github/scripts/fetch-e2e-runtime-logs.mjs @@ -485,8 +518,11 @@ jobs: - name: Build CLI run: pnpm turbo run build --filter='@workflow/cli' + # See the note on e2e-vercel-prod: `changeset-release/*` PRs have no + # deployment of their own and test main's production deployment. - name: Waiting for the Vercel deployment id: waitForDeployment + if: ${{ !startsWith(github.head_ref, 'changeset-release/') }} uses: vercel/wait-for-deployment-action@0e2b0c5c5cce31f1648108aeec56467187aca037 with: project-slug: "example-nextjs-workflow-turbopack" @@ -494,21 +530,34 @@ jobs: check-interval: 15 environment: ${{ github.ref == 'refs/heads/main' && 'production' || 'preview' }} + - name: Waiting for the Vercel deployment (changeset-release PR) + id: prodDeployment + if: ${{ startsWith(github.head_ref, 'changeset-release/') }} + uses: vercel/wait-for-deployment-action@0e2b0c5c5cce31f1648108aeec56467187aca037 + with: + project-slug: "example-nextjs-workflow-turbopack" + timeout: 1000 + check-interval: 15 + environment: production + sha: ${{ github.event.pull_request.base.sha }} + - name: Run Multi-Region E2E Tests run: pnpm vitest run packages/core/e2e/e2e-region.test.ts --reporter=verbose --reporter=json --reporter=./packages/core/e2e/github-reporter.ts "--outputFile=e2e-vercel-multi-region-$APP_NAME.json" env: NODE_OPTIONS: "--enable-source-maps" - DEPLOYMENT_URL: ${{ steps.waitForDeployment.outputs.deployment-url }} - VERCEL_DEPLOYMENT_ID: ${{ steps.waitForDeployment.outputs.deployment-id }} + DEPLOYMENT_URL: ${{ steps.waitForDeployment.outputs.deployment-url || steps.prodDeployment.outputs.deployment-url }} + VERCEL_DEPLOYMENT_ID: ${{ steps.waitForDeployment.outputs.deployment-id || steps.prodDeployment.outputs.deployment-id }} APP_NAME: "nextjs-turbopack" - WORKFLOW_VERCEL_ENV: ${{ github.ref == 'refs/heads/main' && 'production' || 'preview' }} + # changeset-release PRs test main's production deployment, so they + # must be treated as a production run everywhere downstream. + WORKFLOW_VERCEL_ENV: ${{ (github.ref == 'refs/heads/main' || startsWith(github.head_ref, 'changeset-release/')) && 'production' || 'preview' }} WORKFLOW_VERCEL_AUTH_TOKEN: ${{ secrets.VERCEL_LABS_TOKEN }} WORKFLOW_VERCEL_TEAM: "team_nO2mCG4W8IxPIeKoSsqwAxxB" WORKFLOW_VERCEL_PROJECT: "prj_yjkM7UdHliv8bfxZ1sMJQf1pMpdi" WORKFLOW_VERCEL_PROJECT_SLUG: "example-nextjs-workflow-turbopack" # See the note on e2e-vercel-prod: PRs point at the protected # workflow-server preview; unset on main for production. - VERCEL_WORKFLOW_SERVER_URL: ${{ github.ref != 'refs/heads/main' && secrets.VERCEL_WORKFLOW_SERVER_URL || '' }} + VERCEL_WORKFLOW_SERVER_URL: ${{ github.ref != 'refs/heads/main' && !startsWith(github.head_ref, 'changeset-release/') && secrets.VERCEL_WORKFLOW_SERVER_URL || '' }} - name: Capture runtime logs on failure if: failure() @@ -517,8 +566,8 @@ jobs: WORKFLOW_VERCEL_AUTH_TOKEN: ${{ secrets.VERCEL_LABS_TOKEN }} WORKFLOW_VERCEL_TEAM: "team_nO2mCG4W8IxPIeKoSsqwAxxB" WORKFLOW_VERCEL_PROJECT: "prj_yjkM7UdHliv8bfxZ1sMJQf1pMpdi" - WORKFLOW_VERCEL_ENV: ${{ github.ref == 'refs/heads/main' && 'production' || 'preview' }} - VERCEL_DEPLOYMENT_ID: ${{ steps.waitForDeployment.outputs.deployment-id }} + WORKFLOW_VERCEL_ENV: ${{ (github.ref == 'refs/heads/main' || startsWith(github.head_ref, 'changeset-release/')) && 'production' || 'preview' }} + VERCEL_DEPLOYMENT_ID: ${{ steps.waitForDeployment.outputs.deployment-id || steps.prodDeployment.outputs.deployment-id }} run: node .github/scripts/fetch-e2e-runtime-logs.mjs - name: Upload E2E results diff --git a/AGENTS.md b/AGENTS.md index 23bffde13b..78a02fb262 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -225,6 +225,14 @@ Both branches trigger the release workflow (`.github/workflows/release.yml`) on When backporting changes to `stable`, any conflicts involving docs app files (outside of `docs/content/`) or `skills/` files should be resolved by keeping the `stable` branch version (discarding the incoming change from `main`). Conflicts in `docs/content/` should be resolved normally. The backport GitHub Action handles this automatically. +#### The `changeset-release/main` branch is never deployed + +Every Vercel project rooted in this repo sets `git.deploymentEnabled` to `false` for `changeset-release/main` in its `vercel.json`. **When you add a new Vercel project, add that key to its `vercel.json` too.** + +The changesets action force-pushes `changeset-release/main`, and it can point at exactly main's HEAD SHA. Vercel keeps one commit status per project per SHA, so a preview deployment of that branch overwrites the production deployment's status for the same commit — and `vercel/wait-for-deployment-action`, which reads the deployment ID out of that status, then hands a production e2e run a preview deployment ID, forking the run across environments. + +Because those PRs have no deployment of their own, CI treats them specially: the Vercel e2e lanes in `tests.yml` run `vercel/wait-for-deployment-action` a second way — `environment: production` with `sha` pinned to the PR's base SHA — so they test main's production deployment and run as `production`. (The commit-status ID that action reads is unambiguous for main SHAs precisely because this repo no longer deploys `changeset-release/main`, the only branch that ever deployed a commit main also deployed.) The deployment-dependent jobs in `docs-checks.yml`, `tarballs-checks.yml`, and `benchmarks.yml` are skipped. Anything new that waits on a deployment needs the same treatment. + ### Changesets - `workflow` and `@workflow/core` use changesets' "fixed" versioning strategy — they always have the same version number diff --git a/docs/vercel.json b/docs/vercel.json new file mode 100644 index 0000000000..5218b12d16 --- /dev/null +++ b/docs/vercel.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://openapi.vercel.sh/vercel.json", + "git": { + "deploymentEnabled": { + "changeset-release/main": false + } + } +} diff --git a/packages/web/vercel.json b/packages/web/vercel.json new file mode 100644 index 0000000000..5218b12d16 --- /dev/null +++ b/packages/web/vercel.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://openapi.vercel.sh/vercel.json", + "git": { + "deploymentEnabled": { + "changeset-release/main": false + } + } +} diff --git a/tarballs/vercel.json b/tarballs/vercel.json index 6ae129a221..ddb1e52c2a 100644 --- a/tarballs/vercel.json +++ b/tarballs/vercel.json @@ -1,5 +1,10 @@ { "$schema": "https://openapi.vercel.sh/vercel.json", + "git": { + "deploymentEnabled": { + "changeset-release/main": false + } + }, "buildCommand": "pnpm turbo build --filter=tarballs", "installCommand": "pnpm install --frozen-lockfile", "outputDirectory": "dist", diff --git a/workbench/astro/vercel.json b/workbench/astro/vercel.json index 613ec74002..85d07c17cd 100644 --- a/workbench/astro/vercel.json +++ b/workbench/astro/vercel.json @@ -1,4 +1,9 @@ { + "git": { + "deploymentEnabled": { + "changeset-release/main": false + } + }, "env": { "WORKFLOW_PUBLIC_MANIFEST": "1" } diff --git a/workbench/example/vercel.json b/workbench/example/vercel.json index be8eccd75d..6298e30c3c 100644 --- a/workbench/example/vercel.json +++ b/workbench/example/vercel.json @@ -1,4 +1,9 @@ { + "git": { + "deploymentEnabled": { + "changeset-release/main": false + } + }, "rewrites": [ { "source": "/api/webhook/:webhookId", diff --git a/workbench/express/vercel.json b/workbench/express/vercel.json index 613ec74002..85d07c17cd 100644 --- a/workbench/express/vercel.json +++ b/workbench/express/vercel.json @@ -1,4 +1,9 @@ { + "git": { + "deploymentEnabled": { + "changeset-release/main": false + } + }, "env": { "WORKFLOW_PUBLIC_MANIFEST": "1" } diff --git a/workbench/fastify/vercel.json b/workbench/fastify/vercel.json index 613ec74002..85d07c17cd 100644 --- a/workbench/fastify/vercel.json +++ b/workbench/fastify/vercel.json @@ -1,4 +1,9 @@ { + "git": { + "deploymentEnabled": { + "changeset-release/main": false + } + }, "env": { "WORKFLOW_PUBLIC_MANIFEST": "1" } diff --git a/workbench/hono/vercel.json b/workbench/hono/vercel.json index 613ec74002..85d07c17cd 100644 --- a/workbench/hono/vercel.json +++ b/workbench/hono/vercel.json @@ -1,4 +1,9 @@ { + "git": { + "deploymentEnabled": { + "changeset-release/main": false + } + }, "env": { "WORKFLOW_PUBLIC_MANIFEST": "1" } diff --git a/workbench/nest/vercel.json b/workbench/nest/vercel.json index 613ec74002..85d07c17cd 100644 --- a/workbench/nest/vercel.json +++ b/workbench/nest/vercel.json @@ -1,4 +1,9 @@ { + "git": { + "deploymentEnabled": { + "changeset-release/main": false + } + }, "env": { "WORKFLOW_PUBLIC_MANIFEST": "1" } diff --git a/workbench/nextjs-turbopack/vercel.json b/workbench/nextjs-turbopack/vercel.json index aa4f892410..ff2e7944b0 100644 --- a/workbench/nextjs-turbopack/vercel.json +++ b/workbench/nextjs-turbopack/vercel.json @@ -1,4 +1,9 @@ { + "git": { + "deploymentEnabled": { + "changeset-release/main": false + } + }, "env": { "WORKFLOW_PUBLIC_MANIFEST": "1" }, diff --git a/workbench/nextjs-webpack/vercel.json b/workbench/nextjs-webpack/vercel.json index 613ec74002..85d07c17cd 100644 --- a/workbench/nextjs-webpack/vercel.json +++ b/workbench/nextjs-webpack/vercel.json @@ -1,4 +1,9 @@ { + "git": { + "deploymentEnabled": { + "changeset-release/main": false + } + }, "env": { "WORKFLOW_PUBLIC_MANIFEST": "1" } diff --git a/workbench/nitro-v2/vercel.json b/workbench/nitro-v2/vercel.json index 613ec74002..85d07c17cd 100644 --- a/workbench/nitro-v2/vercel.json +++ b/workbench/nitro-v2/vercel.json @@ -1,4 +1,9 @@ { + "git": { + "deploymentEnabled": { + "changeset-release/main": false + } + }, "env": { "WORKFLOW_PUBLIC_MANIFEST": "1" } diff --git a/workbench/nitro-v3/vercel.json b/workbench/nitro-v3/vercel.json index 613ec74002..85d07c17cd 100644 --- a/workbench/nitro-v3/vercel.json +++ b/workbench/nitro-v3/vercel.json @@ -1,4 +1,9 @@ { + "git": { + "deploymentEnabled": { + "changeset-release/main": false + } + }, "env": { "WORKFLOW_PUBLIC_MANIFEST": "1" } diff --git a/workbench/nuxt/vercel.json b/workbench/nuxt/vercel.json index 613ec74002..85d07c17cd 100644 --- a/workbench/nuxt/vercel.json +++ b/workbench/nuxt/vercel.json @@ -1,4 +1,9 @@ { + "git": { + "deploymentEnabled": { + "changeset-release/main": false + } + }, "env": { "WORKFLOW_PUBLIC_MANIFEST": "1" } diff --git a/workbench/sveltekit/vercel.json b/workbench/sveltekit/vercel.json index 613ec74002..85d07c17cd 100644 --- a/workbench/sveltekit/vercel.json +++ b/workbench/sveltekit/vercel.json @@ -1,4 +1,9 @@ { + "git": { + "deploymentEnabled": { + "changeset-release/main": false + } + }, "env": { "WORKFLOW_PUBLIC_MANIFEST": "1" } diff --git a/workbench/swc-playground/vercel.json b/workbench/swc-playground/vercel.json index 613ec74002..85d07c17cd 100644 --- a/workbench/swc-playground/vercel.json +++ b/workbench/swc-playground/vercel.json @@ -1,4 +1,9 @@ { + "git": { + "deploymentEnabled": { + "changeset-release/main": false + } + }, "env": { "WORKFLOW_PUBLIC_MANIFEST": "1" } diff --git a/workbench/tanstack-start/vercel.json b/workbench/tanstack-start/vercel.json index 613ec74002..85d07c17cd 100644 --- a/workbench/tanstack-start/vercel.json +++ b/workbench/tanstack-start/vercel.json @@ -1,4 +1,9 @@ { + "git": { + "deploymentEnabled": { + "changeset-release/main": false + } + }, "env": { "WORKFLOW_PUBLIC_MANIFEST": "1" } diff --git a/workbench/vite/vercel.json b/workbench/vite/vercel.json index 613ec74002..85d07c17cd 100644 --- a/workbench/vite/vercel.json +++ b/workbench/vite/vercel.json @@ -1,4 +1,9 @@ { + "git": { + "deploymentEnabled": { + "changeset-release/main": false + } + }, "env": { "WORKFLOW_PUBLIC_MANIFEST": "1" } From 3c7875ad738f8900af1802dbc45779f44e050a83 Mon Sep 17 00:00:00 2001 From: christopherkindl <53372002+christopherkindl@users.noreply.github.com> Date: Fri, 31 Jul 2026 19:21:15 +0200 Subject: [PATCH 3/7] [docs] upgrade @vercel/geistdocs to 1.19.0 (#3170) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: upgrade @vercel/geistdocs to 1.17.1 Picks up the new footer (Footer no longer takes a config prop), the heading font-weight change to 450, and the tightened navbar OSS-menu marks. Also switches the site's own navbar logo from the vendored geistcn LogoWorkflow fallback to the package's LogoWorkflowSdk, using its new tuned default height instead of a hardcoded 15. Fixes a resulting regression: navbarOssProducts entries lacked an `id`, so resolveOssProducts' `product.id !== activeProduct` filter evaluated to `undefined !== undefined` (false) for every entry and emptied the OSS flyout. Added stable `id`/`label` values to each entry. * refactor: use default navbarOssProducts list instead of a custom override The manual navbarOssProducts array (with local logo imports/heights) is no longer needed now that the package's DEFAULT_OSS_PRODUCTS list already includes all these SDKs with proper id/label/section values. navbarActiveProduct: 'workflow-sdk' now handles self-exclusion instead. * fix: use bg-background-200 for docs surfaces to match the template The docs, cookbook, and v5 route layouts plus the shared DocsLayout container hardcoded bg-background-100 (pure white), so /docs/* pages rendered on a lighter surface than the rest of the site. Switch them to bg-background-200, matching the geistdocs template's page background. * style: adopt package text-heading-* utilities for homepage headings The marketing homepage headings hardcoded font-semibold (weight 600) plus manual responsive sizes/tracking, so they rendered heavier than the docs headings that now use Geist's 450 heading weight. Swap each display heading to the package's text-heading-* utilities, which bundle the 450 weight, line-height, and tracking, mapped across breakpoints to the nearest design-system size. Inline label/emphasis spans keep their own weight. * style: adopt package text-heading-* utilities for worlds headings Extends the homepage heading change to the /worlds section: the world listing, detail, compare, and building-a-world pages plus their components hardcoded font-semibold display headings. Swap each to the package's text-heading-* utilities (450 weight + line-height + tracking), mapped across breakpoints to the nearest design-system size. Mono stat numbers, per-benchmark item labels, and the dialog title keep their own weight. * style: remove the bordered grid framing from the homepage The homepage sections were wrapped in a grid divide-y border-y sm:border-x container, drawing side borders and divider lines between every section. Drop that framing so the sections flow with whitespace separation. * style: remove vertical column dividers from homepage sections Drop the divide-x column dividers still drawn inside the use-cases (3-col), feature-grid (2-col), and templates sections, so no vertical lines remain after the section-grid removal. Section padding keeps the columns visually separated. * style: make the homepage "Get started" CTA button rounded-full * style: use text-heading-* for the feature-grid paragraph text The two 2-col feature blurbs ("Deep integration with AI SDK.", "Durable agents by default.") hardcoded their size/leading/tracking plus font-medium/font-semibold weights. Those manual sizes already equal text-heading-20/24, so swap to text-heading-20 lg:text-heading-24 — same sizes, but the Geist 450 heading weight (lead drops 600 -> 500 via the utility's [&>strong] rule). The lead stays gray-1000 for emphasis; body stays gray-900. * style: fade out the run-anywhere provider logos at the left/right edges Add linear-gradient masks to the flanking cloud-provider logo groups in the "Run anywhere, no lock-in" viz so they fade to transparent toward the outer edges, leaving the centered code block untouched. * style: widen the right-edge fade on the Vercel dashboard viz The "Workflow SDK on Vercel" dashboard is offset off the right edge, so the existing to_left black_10% mask fell off-screen and the visible right edge hard-clipped. Widen it to black_40% so the dashboard fades out gradually at the visible right edge. * style: add spacing between the Vercel, use-cases, and templates sections Wrap the UseCases and Templates sections with a top margin so there's clear separation between "Workflow SDK on Vercel", "Build anything with AI Agents", and "Get started" now that the section dividers are gone. * style: widen the homepage layout from 1080px to 1200px * style: align use-cases code block and templates cards with the Vercel section Switch the "Build anything with" and "Get started" sections from grid-cols-3 / [1fr_2fr] to [1fr_1.5fr], matching the "Workflow SDK on Vercel" section above so their code block and cards share the same right-hand column. The wider text column also lets "Build anything with" sit on one line. Normalize both to outer padding + column gap so the code block and cards line up exactly. * style: extend use-cases/templates content to the right layout edge Drop the right padding at md+ (md:pr-0) so the code block and template cards reach the same right edge as the "Workflow SDK on Vercel" dashboard above, which bleeds to the container edge. Mobile keeps its padding. * style: remove the divider between the two feature cards Drop divide-y/lg:divide-y-0 from the feature grid so no border shows between "Deep integration with AI SDK" and "Durable agents by default". * refactor: position homepage sections on a shared 12-col grid Replace the ad-hoc [1fr_1.5fr] + md:pr-0 + lg:pl-* positioning on the Vercel, use-cases, and templates sections with a shared grid-cols-12 layout (text col-span-5, visual col-span-7), matching the vercel.com marketing grid convention. The Vercel dashboard becomes a proper grid cell instead of an absolutely-offset right-bleed, so all three sections' visuals align by the grid columns with no magic values. * style: align homepage width with the navbar content Widen the homepage container from max-w-[1200px] to the site's max-w-[1448px] (matching the navbar/footer) and reduce the section gutters from sm:px-12 to sm:px-6, so section content lines up with the navbar's content edges (right edge flush at the same column as the navbar and footer). Also convert the "Reliability-as-code" section to the shared grid-cols-12 layout (col-span-5 text / col-span-7 code example), replacing its lg:grid-cols-[330px_1fr] magic values. * refactor: handle homepage horizontal padding at the root container Move the mobile/desktop gutter (px-4 sm:px-6) onto the homepage root container and remove the horizontal padding from every section component. Section content still aligns with the navbar/footer content edges, but the gutter is now defined once instead of repeated per section. Inner-element padding (tab buttons, visual internals) is unchanged. * style: left-align content sections on mobile + fix run-anywhere/o11y viz - Left-align the centered content sections on mobile only (FeatureCardWide, TweetWall heading, Frameworks, Run-anywhere heading/buttons), restoring their centered layout at sm and up. - Make the "Inspect every run" timeline span edge-to-edge by shifting its gantt from a 14-col grid (content in cols 2-13) to a flush 12-col grid. - Constrain the run-anywhere viz cluster to the code block width so the provider cards (AWS/Docker/etc.) overlap behind the code block again. * style: anchor run-anywhere provider cards to overlap the code block Position the flanking provider-card groups relative to the centered code block (right/left calc(50%+140px)) instead of the section edges, so the cards sit behind and overlap the code block regardless of the section width. * style: make the reliability-as-code example fill its column to the right edge Drop max-w-3xl mx-auto from the workflow/non-workflow code examples so they fill the col-span-7 cell, aligning the code block's right edge with the layout's right content edge (matching the tabs and other sections). * style: split feature-card copy into a title + description Break the AI SDK / durable-agents feature blurbs into a heading and a separate muted description with a gap (matching the other sections) instead of one inline paragraph, and drop the trailing periods from the feature titles so they read as headings. * update * update * style: give the tweet cards a bg-background-100 surface * fix(swc-playground): pin monaco-editor to 0.55.1 The lockfile refresh resolved the unpinned `monaco-editor: "latest"` from 0.55.1 to 0.56.0, breaking the workflow-swc-playground Turbopack build. 0.56.0 rewrote its exports map to reroot subpaths under `esm/vs/` ("./*": "./esm/vs/*.js"). monaco-vim@0.4.4 deep-imports `monaco-editor/esm/vs/editor/editor.api` and `.../common/commands/shiftCommand`, which now map to `esm/vs/esm/vs/...` — a path that does not exist. Under 0.55.1 ("./*": "./*") both specifiers resolve to real files. monaco-vim 0.4.4 is the latest published release, so pinning monaco-editor is the only available fix. * update --------- Signed-off-by: christopherkindl <53372002+christopherkindl@users.noreply.github.com> --- docs/app/[lang]/(home)/components/cta.tsx | 6 +- .../app/[lang]/(home)/components/features.tsx | 6 +- .../[lang]/(home)/components/frameworks.tsx | 6 +- docs/app/[lang]/(home)/components/hero.tsx | 6 +- .../(home)/components/implementation.tsx | 10 +- .../[lang]/(home)/components/intro/intro.tsx | 8 +- .../(home)/components/intro/non-workflow.tsx | 2 +- .../(home)/components/intro/workflow.tsx | 2 +- .../(home)/components/observability.tsx | 2 +- .../[lang]/(home)/components/run-anywhere.tsx | 12 +- .../(home)/components/templates/index.tsx | 10 +- .../[lang]/(home)/components/tweet-wall.tsx | 8 +- .../(home)/components/use-cases-client.tsx | 10 +- .../vercel-com-visuals/feature-grid.tsx | 38 +- .../vercel-com-visuals/o11y-visual.tsx | 22 +- .../vercel-com-visuals/vercel-section.tsx | 43 +- docs/app/[lang]/(home)/page.tsx | 4 +- docs/app/[lang]/cookbook/layout.tsx | 2 +- docs/app/[lang]/docs/layout.tsx | 2 +- docs/app/[lang]/layout.tsx | 2 +- docs/app/[lang]/v5/cookbook/layout.tsx | 2 +- docs/app/[lang]/v5/docs/layout.tsx | 2 +- docs/app/[lang]/worlds/compare/page.tsx | 6 +- docs/app/[lang]/worlds/page.tsx | 6 +- docs/components/geistdocs/docs-layout.tsx | 2 +- docs/components/worlds/WorldDetailHero.tsx | 2 +- docs/components/worlds/WorldInstructions.tsx | 2 +- .../worlds/WorldTestingPerformance.tsx | 4 +- docs/components/worlds/WorldsDashboard.tsx | 2 +- docs/components/worlds/WorldsFilteredGrid.tsx | 4 +- .../worlds/building-a-world-page.tsx | 2 +- docs/geistdocs.tsx | 4 +- docs/lib/geistdocs/config.tsx | 22 +- docs/package.json | 2 +- pnpm-lock.yaml | 2489 ++++++++++++++--- workbench/swc-playground/package.json | 2 +- 36 files changed, 2171 insertions(+), 583 deletions(-) diff --git a/docs/app/[lang]/(home)/components/cta.tsx b/docs/app/[lang]/(home)/components/cta.tsx index cc9cddd8fd..574fbd865e 100644 --- a/docs/app/[lang]/(home)/components/cta.tsx +++ b/docs/app/[lang]/(home)/components/cta.tsx @@ -2,11 +2,11 @@ import { Button } from '@vercel/geistdocs/components/button'; import Link from 'next/link'; export const CTA = () => ( -
-

+
+

Create your first workflow today.

-
diff --git a/docs/app/[lang]/(home)/components/features.tsx b/docs/app/[lang]/(home)/components/features.tsx index ece1bfbcba..48c027532b 100644 --- a/docs/app/[lang]/(home)/components/features.tsx +++ b/docs/app/[lang]/(home)/components/features.tsx @@ -17,12 +17,10 @@ const data = [ ]; export const Features = () => ( -
+
{data.map((item) => (
-

- {item.title} -

+

{item.title}

{item.description}

))} diff --git a/docs/app/[lang]/(home)/components/frameworks.tsx b/docs/app/[lang]/(home)/components/frameworks.tsx index d727e0cad9..c063e3b801 100644 --- a/docs/app/[lang]/(home)/components/frameworks.tsx +++ b/docs/app/[lang]/(home)/components/frameworks.tsx @@ -719,16 +719,16 @@ export const Next = (props: ComponentProps<'svg'>) => ( export const Frameworks = () => { return ( -
+
-

+

Universally Compatible

Works with the frameworks you already use with more coming soon.

-
+
{ return ( -
+
-

+

{title}

-

+

use workflow {' '} diff --git a/docs/app/[lang]/(home)/components/implementation.tsx b/docs/app/[lang]/(home)/components/implementation.tsx index eb665bfce7..271410c43a 100644 --- a/docs/app/[lang]/(home)/components/implementation.tsx +++ b/docs/app/[lang]/(home)/components/implementation.tsx @@ -27,7 +27,7 @@ export async function userSignup(email) { }, { code: `import { Resend } from 'resend'; -import { FatalError } from 'workflow'; +import { FatalError } from 'workflow'; export async function sendWelcomeEmail(email) { "use step" @@ -52,22 +52,22 @@ export async function sendWelcomeEmail(email) { ]; export const Implementation = () => ( -

+
-

+

Effortless setup

With a simple declarative API to define and use your workflows.

-
+
{data.map((item) => (
-

+

{item.caption}

{ ); return ( -
-
-

+
+
+

Reliability-as-code

@@ -154,7 +154,7 @@ export const Intro = async () => { code with simple directives.

-
+
+
diff --git a/docs/app/[lang]/(home)/components/intro/workflow.tsx b/docs/app/[lang]/(home)/components/intro/workflow.tsx index 7dd7db61c0..4be4e05959 100644 --- a/docs/app/[lang]/(home)/components/intro/workflow.tsx +++ b/docs/app/[lang]/(home)/components/intro/workflow.tsx @@ -154,7 +154,7 @@ export const WorkflowExample = ({ }; return ( -
+
diff --git a/docs/app/[lang]/(home)/components/observability.tsx b/docs/app/[lang]/(home)/components/observability.tsx index 3721b2eacf..9d671ae7a8 100644 --- a/docs/app/[lang]/(home)/components/observability.tsx +++ b/docs/app/[lang]/(home)/components/observability.tsx @@ -49,7 +49,7 @@ const rows = [ ]; export const Observability = () => ( -
+

Observability. Inspect every run end‑to‑end. Pause, replay, and time‑travel through steps with traces, diff --git a/docs/app/[lang]/(home)/components/run-anywhere.tsx b/docs/app/[lang]/(home)/components/run-anywhere.tsx index 98aff92911..05f6b23f07 100644 --- a/docs/app/[lang]/(home)/components/run-anywhere.tsx +++ b/docs/app/[lang]/(home)/components/run-anywhere.tsx @@ -116,10 +116,10 @@ const code = `export async function welcome(userId: string) { }`; export const RunAnywhere = () => ( -
+
-
-

+
+

Run anywhere, no lock‑in

@@ -127,7 +127,7 @@ export const RunAnywhere = () => ( fully portable. For zero-config, secure, and scalable workflows, deploy on Vercel.

-
+
-
+
{[DigitalOcean, AWS].map((Logo, index) => (
( }} />
-
+
{[Docker, Vercel].map((Logo, index) => (
( -
-
-

+
+
+

Get started

-

+

See Workflow SDK in action with one of the example templates.

-
+
{data.map((item) => (
@@ -236,11 +236,11 @@ function TweetCard({ url, name, username, image, tweet }: Tweet) { } export const TweetWall = () => ( -
-

+
+

What builders say about Workflow SDK

-
+
{TWEETS.map((tweet) => (
diff --git a/docs/app/[lang]/(home)/components/use-cases-client.tsx b/docs/app/[lang]/(home)/components/use-cases-client.tsx index 2f1dcb1bb9..b7ce0f0483 100644 --- a/docs/app/[lang]/(home)/components/use-cases-client.tsx +++ b/docs/app/[lang]/(home)/components/use-cases-client.tsx @@ -27,12 +27,12 @@ export const UseCasesClient = ({ useCases }: { useCases: UseCase[] }) => { }; return ( -
-
-

+
+
+

Build anything with