docs: restructure to Diátaxis and add versioned deploys - #348
Conversation
Replace the flat /guide/ layout with the four Diátaxis modes (tutorial,
how-to, reference, explanation) and close the coverage gaps: signals,
queries, updates, search attributes, schedules, cancellation scopes,
continue-as-new, and time-skipping tests were all implemented but
documented nowhere.
Correctness fixes found while writing against the source:
- The activity implementation map is nested by workflow; the README
showed a workflow-scoped activity at the root level, which does not
type-check.
- An activity that declares an `errors` map returns AsyncResult to the
workflow, not a plain value (activities-proxy.ts) — the old docs
showed try/catch.
- `match({ err })` is `errCases`; `signalWithStart` takes `signalName`;
`getHandle` returns an AsyncResult.
- unthrown peer range was documented as ^4.1.0, actual ^5.0.0-beta.6.
- Zod 4: `z.record` needs two args, `.email()` is deprecated.
- `tag(...)` was used in ~20 examples with no import shown.
- The Nexus page claimed "planned for v0.5.0" at 8.0.0-beta.1.
Versioned deploys are ported from btravstack/unthrown: while a
prerelease is in progress, the latest stable tag builds at the root and
main builds under /beta/, with a version dropdown linking the two. Two
things could not be copied verbatim:
- unthrown drops prereleases with `grep -v -- '-'` over whole tags.
Every tag here contains a hyphen inside "@temporal-contract/", so that
would discard all of them; the version is now filtered after the
package prefix is stripped.
- This repo's nav is nested one level deeper, so the injection script's
anchor needs eight spaces of indentation, not six.
Verified end to end locally: main builds at /beta/ with correct asset
paths and canonicals, and a real worktree at
@temporal-contract/contract@7.0.0 builds at the root with the dropdown
injected.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR overhauls the project documentation to follow the Diátaxis framework (tutorial / how-to / reference / explanation), and updates the docs deployment workflow to support versioned docs (latest stable tag at /temporal-contract/, main prerelease docs at /temporal-contract/beta/) with a version switcher that preserves the current page when swapping versions.
Changes:
- Restructure docs content into Diátaxis sections and replace/remove legacy
/guide/*content. - Add versioned GitHub Pages deploy logic (stable tag + beta
main) including a legacy-theme injection script and a client-side “same-page” version switch enhancement. - Update in-repo links (package READMEs/examples/docs) and dependency guidance (unthrown peer dep range) to align with current APIs.
Reviewed changes
Copilot reviewed 63 out of 63 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/worker/README.md | Update docs links to new reference structure. |
| packages/contract/README.md | Point “getting started” links to new tutorial/explanation pages. |
| packages/client/README.md | Update docs link to new tutorial entry point. |
| examples/README.md | Update docs link to new tutorial entry point. |
| examples/order-processing-worker/README.md | Update docs link to new tutorial entry point. |
| knip.json | Ignore .github/scripts/** for knip (CI-invoked scripts). |
| docs/index.md | Rewrite landing page content/examples and add “Where to start” navigation aligned with Diátaxis. |
| docs/api/index.md | Refresh API landing page links to new hand-written reference/explanation pages. |
| docs/reference/contract-surface.md | New grouped reference for @temporal-contract/contract exports and option semantics. |
| docs/reference/client-surface.md | New grouped reference for @temporal-contract/client exports and typed handle/schedule APIs. |
| docs/reference/glossary.md | New glossary defining contract/result/Temporal terminology used across the docs. |
| docs/how-to/install.md | New install guide including peer deps and ESM/import rules. |
| docs/how-to/define-a-contract.md | New how-to guide for contract authoring patterns and validation behavior. |
| docs/how-to/implement-activities.md | New how-to guide for activity handler shape, qualify, and activity error behavior. |
| docs/how-to/model-domain-errors.md | New how-to guide explaining declared errors and how they change call sites. |
| docs/how-to/use-signals-queries-and-updates.md | New how-to guide for declaring/handling/calling signals/queries/updates. |
| docs/how-to/tune-activity-options.md | New how-to guide for activity option merge order and timeout/retry guidance. |
| docs/how-to/run-child-workflows.md | New how-to guide for child workflow execution patterns and error handling. |
| docs/how-to/continue-as-new.md | New how-to guide for continue-as-new patterns and determinism-safe triggers. |
| docs/how-to/handle-cancellation.md | New how-to guide for client + workflow + activity cancellation patterns. |
| docs/how-to/schedule-workflows.md | New how-to guide for typed schedules creation and lifecycle operations. |
| docs/how-to/intercept-client-calls.md | New how-to guide for typed client interceptors, patching, retries, and ordering. |
| docs/how-to/configure-a-worker.md | New how-to guide for worker creation, workflows path resolution, and runtime configuration. |
| docs/how-to/upgrade-to-v8.md | New upgrade guide documenting unthrown 5 + defect-channel changes for v8. |
| docs/how-to/migrate-from-neverthrow.md | New migration guide mapping neverthrow → unthrown APIs and patterns. |
| docs/how-to/add-activity-middleware.md | New how-to guide for activity middleware positioning, composition, and context injection. |
| docs/how-to/index-workflows-with-search-attributes.md | New how-to guide for declaring/registering/using typed search attributes. |
| docs/explanation/why-temporal-contract.md | New explanation of the contract boundary value proposition. |
| docs/explanation/the-result-model.md | New explanation of ok/err/defect semantics across Temporal boundaries. |
| docs/explanation/validation-boundaries.md | New explanation of where schemas run and why dual validation is intentional. |
| docs/explanation/workflow-determinism.md | New explanation of replay determinism constraints and safe alternatives. |
| docs/explanation/architecture.md | New explanation of package split and workflow-bundling constraints. |
| docs/explanation/nexus.md | Replace legacy Nexus page with an explicit “not implemented” explanation and guidance. |
| docs/.vitepress/theme/version-switch.ts | Add click-intercept logic to keep the same page when switching docs versions. |
| docs/.vitepress/theme/index.ts | Hook version switch setup into the theme entry. |
| .github/workflows/deploy-docs.yml | Build/deploy versioned docs (stable tag root + beta main under /beta/) and assemble into one Pages artifact. |
| .github/scripts/inject-docs-version-nav.ts | Inject version dropdown + version-switch enhancement into legacy stable-tag VitePress theme/config when needed. |
| .agents/rules/dependencies.md | Update documented peer-dependency ranges to unthrown ^5. |
| docs/guide/why-temporal-contract.md | Remove legacy guide page (replaced by Diátaxis explanation content). |
| docs/guide/testing.md | Remove legacy guide page (replaced by Diátaxis how-to/testing content). |
| docs/guide/nexus-integration.md | Remove legacy guide page (replaced by Diátaxis explanation/nexus content). |
| docs/guide/installation.md | Remove legacy guide page (replaced by Diátaxis install how-to). |
| docs/guide/getting-started.md | Remove legacy guide page (replaced by Diátaxis tutorial/how-to). |
| docs/guide/defining-contracts.md | Remove legacy guide page (replaced by Diátaxis how-to/reference split). |
| docs/guide/core-concepts.md | Remove legacy guide page (replaced by Diátaxis explanation/reference content). |
| docs/examples/basic-order-processing.md | Remove legacy example page (examples covered via new docs + examples section). |
Addresses PR review. `AsyncResult` is a success-only thenable — its
internal promise never rejects (core.ts:616) — so `await` collapses it
to a Result and a bare `await handle.signals.x(...)` or
`await schedule.pause(...)` discards the failure entirely.
Copilot flagged four sites; the same bug was present at ~15. All are
now unwrapped with `.get()` / `.getOrThrow()` or an explicit branch, and
the trap is documented in the-result-model.md ("`await` is not an
extractor") plus inline warnings on the schedule, signals, and upgrade
pages.
Also fixes the interceptor short-circuit, which returned
`Err(new RuntimeClientError(...))`. That is both a type error —
`ClientCallError` excludes it since 8.0 — and the wrong channel. It now
defects via `fromSafePromise`, and the note points at the `defect`
helper passed as the second argument to every `*ErrCases` callback.
Auditing that turned up a further class of broken example: all the
`*ErrCases` combinators require an `ExhaustiveMatch`, so the single-arm
`.with(tag(...))` calls in the interceptor and middleware pages would
not compile against their wide unions. Those now close with `P._`, and
the abbreviated before/after snippets in the migration guides are
labelled as such.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review fixes — 4deee0bAll four comments were valid. Each led to a wider fix than the single line flagged. 1. Discarded
|
| File | Sites |
|---|---|
how-to/schedule-workflows.md |
4 × schedule.create, plus pause/unpause/trigger/delete/describe |
how-to/use-signals-queries-and-updates.md |
2 × signals.cancelRequested |
how-to/handle-cancellation.md |
cancel, terminate, nonCancellableScope |
tutorial/adding-signals-and-queries.md |
signals.approve |
how-to/index-workflows-with-search-attributes.md |
schedule.create |
how-to/upgrade-to-v8.md |
schedule.pause |
explanation/workflow-determinism.md |
nonCancellableScope |
Two left deliberately: temporalClient.schedule.getHandle(...).pause() and the raw-SDK client.workflow.execute example both return real promises.
Documented the rule once conceptually in explanation/the-result-model.md → "await is not an extractor", with inline callouts on the pages where it bites.
2. RuntimeClientError on the wrong channel
Right, and it contradicted reference/client-surface.md two pages over, which documents that ClientCallError excludes it. Now defects via fromSafePromise. Also documented the defect helper passed as the second argument to *ErrCases callbacks and fromPromise qualifiers, which was previously undocumented.
3. Non-exhaustive matchers (found while auditing #2)
All *ErrCases combinators constrain M extends ExhaustiveMatch<...>, so single-arm .with(tag(...)) does not compile against a wide union. Two examples were broken (fallback in the interceptor page, retryOnce in the middleware page) — both now close with P._. The before/after snippets in the migration guides are labelled abbreviated rather than padded out, since their point is the rename.
pnpm lint, pnpm typecheck, pnpm knip, and the docs build are all clean.
One caveat worth stating plainly: these are prose examples, not compiled fixtures, so "clean build" means VitePress rendered them and links resolve — it does not mean the snippets typecheck. I verified each change against the source signatures by hand. A doc-snippet typecheck harness would close that gap properly, but it is out of scope here.
Reworks the documentation into the four Diátaxis modes and adds unthrown-style versioned deploys, so the stable line and the in-flight beta are published side by side.
Documentation
/guide/(15 pages) is replaced by 31 pages:your-first-workflow,adding-signals-and-queriesCoverage gaps closed. Signals, queries, updates, search attributes,
TypedScheduleClient, cancellation scopes, continue-as-new, and time-skipping tests were all implemented inpackages/but appeared in zero guide pages.Correctness fixes
Each found by writing against the source rather than the old docs:
README.md:55showed a workflow-scoped activity implemented at the root level, which does not type-check (ContractResultActivitiesImplementations).errorsmap returnsAsyncResultto the workflow, not a plain value — seeactivities-proxy.ts:30-58. The old docs showedtry/catch. This changes the call site, so it is now called out as a deliberate trade inmodel-domain-errors.mdand noted in the tutorial.match({ err })iserrCases;signalWithStarttakessignalName, notsignal;getHandlereturns anAsyncResult, not a bare handle.unthrowndocumented as^4.1.0; actual peer is^5.0.0-beta.6(README.md,.agents/rules/dependencies.md).z.record(z.string())needs two args;z.string().email()is deprecated.tag(...)used in ~20 examples with no import shown.8.0.0-beta.1.Versioned deploys
Ported from btravstack/unthrown. While
.changeset/pre.jsonexists, the site builds twice — the latest stable tag at the root,mainunder/beta/— with a dropdown linking them. With no prerelease in progress,maindeploys alone to the root exactly as before.Two things could not be copied verbatim:
grep -v -- '-'over whole tags. Every tag here contains a hyphen inside@temporal-contract/, so that would have discarded all of them and produced an emptygit worktree add. The version is now filtered after the package prefix is stripped.withMermaid(defineConfig({...})), so thebtravstackline is at eight spaces, not six. The script exits 1 on a mismatch rather than guessing, so this would have hard-failed the build.New files:
.github/scripts/inject-docs-version-nav.ts,docs/.vitepress/theme/version-switch.ts.docs/.vitepress/config.tsgainsDOCS_BASE/DOCS_VERSIONSsupport, and all hardcoded site URLs now derive from the resolved base so the beta site canonicalizes to/beta/.Verification
Not just inspected — run locally:
pnpm lint,pnpm typecheck,pnpm knipclean (knip now ignores.github/scripts/**, which is CI-invoked only).pnpm --filter ./docs exec turbo buildclean, 34 pages, no dead links.DOCS_BASE=/temporal-contract/beta/— assets and canonical URLs correctly under/beta/, dropdown rendered with both entries.@temporal-contract/contract@7.0.0, ran the injection script,pnpm install --frozen-lockfile, and built it — root base, dropdown injected, its own/guide/*pages intact.Notes for review
/guide/*URLs will 404. No redirect shims, per the agreed scope. All in-repo links are updated (package READMEs, examples,docs/api/index.md); the four packageCHANGELOG.mdfiles still contain/guide/links and were deliberately left alone as historical records.packages/client/src/interceptors.ts:12,16documentstapErrandflatMapErrin prose, but unthrown 5 only has the*ErrCasesforms.🤖 Generated with Claude Code