* web: infinite scroll for the runs table Replace Previous/Next cursor paging with front-style infinite scroll: a useInfiniteList hook accumulates cursor pages with per-run dedup and generation-guarded resets, and useLoadMoreOnScroll drives loadMore from an IntersectionObserver sentinel (400px prefetch margin, guarded against double-fetch, observed against the table's scroll container). Footer now shows the loaded count and the analytics lookback window. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * web: back the runs infinite list with SWR so tab switches serve from cache Rewrite useInfiniteList on useSWRInfinite: pages are keyed by [cacheKey, cursor] in SWR's global cache, so unmount/remount (switching tabs) restores fetched pages instantly instead of refetching. Revalidation is conservative because analytics list queries are expensive: revalidateFirstPage and revalidateIfStale are off; freshness comes from the Refresh button and the visibility-change auto-reload, which map to reload() (reset to first page + revalidate). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * world: expose startTime/endTime on analytics runs listing The workflow-server /v2/analytics/runs endpoint has accepted a bounded startTime/endTime window since it shipped, and is significantly faster with one (the window prunes the ClickHouse scan: ~2s for 12h vs ~8s for the default 30-day entitlement window). The world client never exposed the params, so the CLI and web UI could only issue windowless requests. Pass them through so clients can send bounded windows (e.g. a period picker like front's workflows o11y). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * web: front-style period picker for the runs list Add a time-window picker (1h/6h/24h/3d/7d/30d, default 24h, URL-backed via ?period=) that sends an explicit startTime/endTime window through fetchRuns -> world.analytics.runs.list, keeping the ClickHouse scan bounded. The window is frozen per selection/refresh so all cursor pages share the same bounds, and it participates in the SWR cache key. Plan tiers are honored data-driven from the server's pageInfo: presets longer than the plan's observability lookback are disabled in the picker (labeled Observability Plus when an upgrade is available), and a 402 observability-upgrade-required response renders through the existing upgrade-required error handling. The footer now labels the selected window instead of the plan lookback. The runtime (local) fallback path ignores the window since the storage API has no time filter. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * web: allow status filtering without a workflow name filter The status dropdown was disabled on Vercel backends until a workflow was selected — a limitation of the runtime DynamoDB API's index design. The runs list now reads via world.analytics, whose ClickHouse query filters derived status independently of workflowName, so drop the guard. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * cli: time-window flags for runs listing; widen name lookups past the default window The analytics backend now defaults windowless runs listings to the trailing 24h. Replicate the web's window support in the CLI: - 'workflow inspect runs' gains --since/--until (relative durations like 30m/12h/7d/2w, or timestamps) which are sent as an explicit startTime/endTime window. Out-of-plan windows surface through the existing observability-upgrade-required handling; non-analytics backends warn that the flags are ignored. - 'workflow start <name>' resolves the workflow's latest run via a windowless (default-window) listing and now retries across the plan's whole observability window on a miss, so names idle for more than a day keep resolving. - Bulk 'workflow cancel' matches across the plan window up front — a run can sleep or wait on a hook for days without recent events, so the default recent window must not bound cancellation matching. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: tighten changeset descriptions Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * web: persist frozen listing windows across remounts; minimize lockfile diff Address review findings: - The frozen startTime/endTime lived in component state, but RunsTable fully remounts on tab switches, so every remount minted a new SWR cache key — the cached-pages restore never hit and cache entries grew unboundedly (one per key, including every 5s local-backend poll tick). Move the frozen windows to a module-scope store keyed by period: a remount reuses the stored window (same cache key, instant restore), and the window only advances on explicit refresh/reload. Non-analytics backends now send no window at all (the runtime APIs ignore it anyway), which also hides the period picker and window label there. - Regenerate pnpm-lock.yaml from main so the diff contains only the swr addition (plus its own use-sync-external-store dependency), dropping the unrelated docs-importer radix-ui re-resolutions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
@workflow/web
Observability Web UI Package bundled in the Workflow SDK.
Self-hosting
Security notice: The
@workflow/webpackage does not include authentication or authorization. All users who can reach the deployment share the same backend credentials configured via environment variables. If you self-host this UI, you must place it behind your own authentication layer (e.g. VPN, reverse proxy with auth, OAuth). Exposing it to untrusted users without authentication is at your own risk and may allow unauthorized access to your workflow data.
While this UI is bundled with the Workflow CLI, you can also self-host it.
There are multiple approaches:
- Run
npx workflow web --noBrowseron a server - Clone this repo and deploy as a normal Next.js app
- Deploy the published
@workflow/webpackage
All options require the environment to be configured with the right environment variables for the World you're using.
Option 1: Run with the CLI
npx workflow web --noBrowser
This will start the web UI on the default port 3456.
Option 2: Clone and deploy
- Build and deploy like any Next.js app.
- Configure the backend via environment variables (same variables the CLI writes).
Option 3: Deploy the published @workflow/web package
The published @workflow/web package contains a prebuilt .next directory and package.json.
You can install it and run next start from the package directory.
Example (Node server / container):
mkdir workflow-observability-ui
cd workflow-observability-ui
npm init -y
# You must provide React runtime dependencies in your host project.
npm i @workflow/web react react-dom
# Run Next.js from the installed package (it contains the .next output)
cd node_modules/@workflow/web
npx --yes next start -p "${PORT:-3456}"
If you prefer, you can set a start script in your host package.json like:
{
"scripts": {
"start": "cd node_modules/@workflow/web && next start -p $PORT"
}
}
Configuration (environment variables)
The UI reads configuration on the server via environment variables.
-
Vercel (remote observability):
WORKFLOW_TARGET_WORLD=vercelWORKFLOW_VERCEL_TEAMWORKFLOW_VERCEL_PROJECTWORKFLOW_VERCEL_ENV(optional; defaults toproduction)
-
Local (filesystem-backed observability):
WORKFLOW_TARGET_WORLD=localWORKFLOW_LOCAL_DATA_DIR(absolute path to the workflow data dir, e.g./path/to/project/.workflow-data)WORKFLOW_MANIFEST_PATH(optional; enables the Graph tab)
-
Postgres:
WORKFLOW_TARGET_WORLD=postgresWORKFLOW_POSTGRES_URL
For a complete list and CLI flags, see npx workflow inspect --help and npx workflow web --help.
If you're deploying this to Vercel, setting WORKFLOW_TARGET_WORLD to vercel is enough
for the server to infer your other project details at runtime. Note that observability will be scoped to the project and environment you're deploying to.