Skip to content

docs: rewrite README, refresh godoc, redesign examples, and fix interceptor wrapper inheritance#75

Merged
xuxife merged 8 commits into
mainfrom
docs/refresh-readme-and-godoc
May 12, 2026
Merged

docs: rewrite README, refresh godoc, redesign examples, and fix interceptor wrapper inheritance#75
xuxife merged 8 commits into
mainfrom
docs/refresh-readme-and-godoc

Conversation

@xuxife
Copy link
Copy Markdown
Collaborator

@xuxife xuxife commented May 7, 2026

Summary

This PR bundles three threads of documentation work plus one related bug fix.

1. Documentation cleanup

  • README: rewrite to be shorter and more skimmable. New top-of-page snippet is a realistic HTTP-fetch → file-write example that shows data flow between steps via `Input`. Drop the Nested Workflows section to keep the landing page focused on the 80% use case.
  • Godoc comments: refresh across all non-test files so they reflect actual runtime behaviour. Notable corrections:
    • `step.go`: `AfterStep` doc said "called before Do" — corrected to "after Do".
    • `step.go`: `Retry` example used `opt.MaxAttempts`; the real field is `Attempts`.
    • `condition.go`: clarify that `Skipped`/`Canceled` are settled inline by the scheduler and never enter the interceptor chain.
    • `workflow.go`: keep all existing rationale (interceptor inheritance, tick loop, defer cleanup) and tighten/expand it; add doc comments where missing.
    • `interceptor.go`, `retry.go`, `error.go`, `wrap.go`, `branch.go`, `state.go`, `build_step.go`, `mock.go`, `func.go`, `noop.go`, `name.go`: fill in missing doc comments and tighten existing wording.

2. Interceptor wrapper-inheritance fix

  • Library fix (`feat: resolve InterceptorReceiver via Unwrap chain`): `executeWithRetry` previously located the `InterceptorReceiver` on the running step via a direct type assertion. That silently broke parent → child interceptor inheritance whenever the sub-workflow was wrapped in a Steper-only wrapper such as `flow.Name` / `NamedStep` (the embedded `Steper` interface does not promote `PrependInterceptors`). The fix walks the Step tree via `Unwrap` (same protocol as `As[T]` / `Has[T]`) and uses the first `InterceptorReceiver` found in pre-order. Pure bug fix — no existing behavior changes; previously broken wrapper scenarios now work.
  • Spec update: `openspec/specs/step-interceptor/spec.md` documents the resolution rule and adds a scenario covering `NamedStep` inheritance.
  • Regression test: `TestWorkflow_AsStep_InheritsThroughNamedStep`.

3. Example suite redesign

  • Whole `example/` directory rewritten around a reader journey instead of an API-clinic listing: get the mental model → move data → decide what runs → build bigger workflows → operate / debug / test. 14 old files → 12 new files, each focused on one question with a consistent template ("What you'll learn" / mental model / "when to reach for it" / small focused `Example*` functions).
  • Intro chapters (01–04) lead with user-defined Steper structs — anything with a `Do(context.Context) error` method is a Step. From 05 onward, `flow.Func` and friends appear when the focus of an example is something other than the Step body.
  • New `example/README.md` is the navigation landing page and learning path.
  • Old wrapping/composite/sub-options examples were either folded into their thematic homes (composite-Step antipattern lives next to the recommended sub-workflow form) or moved to godoc.
  • Verified with `go test -race -count=5 ./example/...` and `go vet ./...`.

Test plan

  • `go build ./...` passes
  • `go vet ./...` passes
  • `go test ./...` passes (including the new regression test, the new `Example*` doctests, and the new `TestMyPipeline_unitTest`)
  • `go test -race -count=5 ./example/...` stable
  • CI green

Xingfei Xu and others added 4 commits May 7, 2026 05:27
Make the README significantly tighter and pivot the example to a more
realistic snippet (HTTP fetch → file write) that highlights data flow
between steps via Input. Drop the Nested Workflows section to keep the
landing-page focused on the 80% use case.

Refresh godoc comments across all non-test files so they match actual
runtime behavior. Notable corrections:

- step.go: AfterStep doc said "called before Do"; corrected to "after Do".
- step.go: Retry example used opt.MaxAttempts; the real field is Attempts.
- workflow.go: expand the worker / tick / interceptor inheritance docs
  with the rationale already captured in code comments.
- condition.go: clarify that Skipped/Canceled are settled inline by the
  scheduler and never enter the interceptor chain.
- interceptor.go, retry.go, error.go, wrap.go, branch.go, state.go,
  build_step.go, mock.go, func.go, noop.go, name.go: fill in missing
  doc comments and tighten existing ones.

Deprecate SubWorkflow in favor of embedding *Workflow directly.

No code behavior changes; verified with go build and go vet.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
executeWithRetry previously located the InterceptorReceiver on the
running step via a direct type assertion. That silently breaks parent →
child interceptor inheritance whenever the sub-workflow is wrapped in a
Steper-only wrapper such as flow.Name / NamedStep — the embedded Steper
interface does not promote PrependInterceptors, so the assertion fails
and the parent's chain is never installed on the child.

Walk the Step tree via Unwrap (the same protocol used by As[T] / Has[T])
and use the first InterceptorReceiver found in pre-order. This is a
pure bug-fix: existing inheritance scenarios are unchanged; previously
broken wrapper scenarios now work.

Update the step-interceptor spec to document the resolution rule and
add a scenario covering NamedStep inheritance. Add a regression test
TestWorkflow_AsStep_InheritsThroughNamedStep that asserts the parent's
interceptor fires for both the wrapping NamedStep and the inner step.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add example/14_interceptor_test.go with three runnable godoc examples:

- ExampleStepInterceptor      — minimal logger wrapping each Step's full
                                lifetime (across retries).
- ExampleAttemptInterceptor   — per-attempt observation alongside Retry,
                                making the two-layer model concrete.
- ExampleInterceptorReceiver  — parent → child inheritance and opt-out
                                via IsolateInterceptors. Uses flow.Name
                                to label sub-workflows; works thanks to
                                the Unwrap-chain receiver lookup.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 03_io_data_flow_test.go: use a typed ctxKey instead of a bare string
  for context.WithValue (vet-clean and a better Go example).
- 05_branch_if_switch_test.go: discard error return from w.Do via
  '_ = w.Do(...)' for consistency with the rest of the suite.
- 07_timeout_test.go: explain why testTimer.Start ignores the requested
  duration.
- 08_workflow_option_test.go: fix stale doc — the field is SkipAsError,
  not OKToSkip.
- 12_composite_step_test.go: drop ExampleCompositeViaWorkflow; it relied
  on the BuildStep / Reset implicit machinery that is being removed in a
  separate branch, and the surviving ExampleCompositeStep already covers
  the composite-vs-workflow story.
- Renumber 10..14 → 09..13 to close the gap left by the removal of an
  earlier example file. The new interceptor example lands at 14.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@xuxife xuxife changed the title docs: rewrite README and refresh godoc comments docs: rewrite README, refresh godoc, and add interceptor examples May 11, 2026
Replace the 14-file API-clinic layout with a 12-file path organised by the
question a reader brings: get the mental model, move data, decide what
runs, build bigger workflows, then operate / debug / test.

  01_quickstart            — end-to-end 3-minute tour (parallel fetch + merge)
  02_steps_and_deps        — Step / Steps / DependsOn / Pipe / BatchPipe / Func / Name
  03_data_flow             — typed Input / Output, Func / FuncIO / FuncI / FuncO
  04_callbacks             — BeforeStep / AfterStep, where they sit in the stack
  05_conditions            — Condition / When / Skip / Cancel from inside Do
  06_branching             — If / Switch with runtime data
  07_retry_and_timeout     — Retry, per-attempt and per-step timeouts (mock clock)
  08_workflow_in_workflow  — sub-workflow as a Step + the composite-Step antipattern
  09_workflow_options      — MaxConcurrency, DontPanic
  10_observability         — Step / Attempt interceptors (renamed from 14_interceptor)
  11_debugging             — ErrWorkflow + Workflow.StateOf
  12_testing_workflows     — flow.Mock (with both an Example and a real Test)

Each file follows a consistent template: top-of-file doc explains
"What you'll learn" + mental model + when to reach for it; body has
small focused Example* functions. Add example/README.md as a navigation
landing page.

What got dropped:
  - The four ExampleWorkflow_* sub-cases in old 08 (DefaultOption,
    SkipAsError) — moved out of the path; their behaviour is documented on
    the Workflow type's godoc.
  - The Wrap / MultiWrap / Update demonstrations in old 09 — those were
    really godoc material for Steper / Unwrap, not reader-path examples.
  - The dedicated CompositeStep example in old 12 — folded into 08 as a
    deliberate antipattern alongside the recommended sub-workflow form.

Verified with `go test -race -count=5 ./example/...` and `go vet ./...`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@xuxife xuxife changed the title docs: rewrite README, refresh godoc, and add interceptor examples docs: rewrite README, refresh godoc, redesign examples, and fix interceptor wrapper inheritance May 12, 2026
Xingfei Xu and others added 3 commits May 12, 2026 02:44
…pters

Reframe 01_quickstart and 03_data_flow to put the central pitch up
front: anything with a Do(context.Context) error method is a Step.
There's no interface to embed, no generics, no decorators — your
business types ARE the workflow.

01_quickstart now defines FetchUser, FetchPosts, and BuildProfile as
plain structs. BuildProfile holds *FetchUser and *FetchPosts pointers
directly, so data flows through fields rather than callbacks.

02_steps_and_deps switches to a tiny shared `stage` struct (vs the
previous flow.Func) so the wiring chapters still focus on dependency
shapes while staying honest about how Steps are defined.

03_data_flow now leads with the recommended struct-with-pointer-fields
pattern (ExampleSteper_directFields) and demotes Input / Output
callbacks to a clearly-marked alternative (ExampleAddStep_Input) for
runtime-wired or generic helpers.

04_callbacks switches its example Steps from flow.Func to small structs
(greeter, lookupItem) so the BeforeStep / AfterStep mechanics aren't
mixed up with "what is a Step".

example/README.md updated: add a Conventions note explaining that
struct-based Steps are the production form and Func variants are
convenience helpers used from 05 onward when the focus is something
other than the Step body.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
In 01_quickstart, BuildProfile now exposes Name and Posts as plain
input fields and uses Step(...).Input(...) to copy upstream outputs
into them, rather than holding *FetchUser / *FetchPosts pointers and
reaching into upstream objects from Do.

This is the more idiomatic pattern: each Step is self-contained
(testable in isolation by setting input fields and calling Do
directly), and the data wiring is declared next to DependsOn so the
dependency and the data flow live in one place.

In 03_data_flow:
  - ExampleAddStep_Input is now the leading example: plain user-defined
    structs (fetchFeed / countItems / announceCount) with input/output
    fields, wired with Input callbacks.
  - ExampleFunction_inputOutput follows it as the convenience variant
    using flow.Func / FuncIO / FuncI / FuncO when you don't want to
    declare a struct per Step. The mechanics are identical.

example/README.md updated to match the new framing.

Verified with `go test -race -count=3 ./example/...` and `go vet ./...`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The earlier deprecation rationale ("SubWorkflow is unnecessary; embed
*Workflow directly") was wrong. The two patterns are NOT equivalent:

- Embedding `flow.SubWorkflow` keeps the inner Workflow unexported (`w
  Workflow`, value type) and exposes a tight surface (Add / Do / Reset /
  PrependInterceptors). The zero value works.
- Embedding `*flow.Workflow` promotes every public field of Workflow
  (MaxConcurrency, DontPanic, SkipAsError, Clock, …) onto the outer
  struct. It also requires the embedded pointer to be initialised
  before use (typically inside BuildStep), so the zero value would
  panic.

Both are valid; pick by trade-off. Drop the Deprecated marker so the
godoc no longer pushes users toward a strictly less-encapsulated
alternative; keep the example template intact.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@XiangyuKuangMSFT XiangyuKuangMSFT left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@xuxife xuxife added this pull request to the merge queue May 12, 2026
Merged via the queue into main with commit 4e37baa May 12, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants