sergdort/combinefeedback/combinefeedback-state-machines

Claude Code and agent skill by sergdort/combinefeedback

Trust tier 3
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

Use whenever building, changing, or reviewing app logic that uses the CombineFeedback Swift library.

Install

npx vskill@latest install sergdort/combinefeedback/combinefeedback-state-machines

SKILL.md

CombineFeedback State Machines

Use this skill when designing or refining application logic built with the Swift CombineFeedback library. Focus on modeling state machines: state boundaries, events, reducers, feedback lifecycles, and reusable child machines.

This skill is for agents helping library consumers. Do not assume access to the CombineFeedback source repository. Respect the user's app architecture, existing agent instructions, and any state machines already defined in the app.

Scope

Use this skill for:

  • Designing a new CombineFeedback feature state machine.
  • Splitting a large state machine into smaller machines.
  • Refactoring recurring lifecycle behavior into reusable child machines.
  • Choosing between OnEvent, OnChange, SideEffect, and Feedback.custom.
  • Producing a machine decomposition sketch and transition/invariant contract.

Do not use this skill for:

  • SwiftUI/store binding guidance.
  • Testing strategy or test-store patterns (use the companion skill

combinefeedback-state-machine-testing).

  • Generic finite-state-machine theory unrelated to CombineFeedback.

If the user is exploring or refactoring, proactively challenge state boundaries and look for reusable lifecycle machines. If the user is already implementing an agreed plan, do not reopen architecture unless the plan conflicts with CombineFeedback's model.

Verify Before Compile-Ready Code

CombineFeedback APIs may vary by installed version. Before producing compile-ready code in a consumer app, inspect the local package version, docs, or source if available. For design sketches, use current CombineFeedback vocabulary but keep snippets conceptual when version details are uncertain.

Current primitives to expect:

  • StateMachine: composes reducers and feedbacks into a behavior.
  • State: the single source of truth for the machine.
  • Event: external inputs and feedback-produced occurrences.
  • Reducer: pure state transition logic, usually (inout State, Event) -> Void.
  • Feedback: observes state/event streams and emits events.
  • OnChange: runs latest/cancellable effects from changes in derived state.
  • OnEvent: runs latest effects from a specific event stream.
  • SideEffect: fire-and-forget async work that does not emit events.
  • Feedback.custom: escape hatch for advanced stream or cancellation behavior.
  • Scope and IfLet: compose child machines into parent machines.

In CombineFeedback, reducers mutate state only; feedbacks observe state/events and emit events back into the loop.

Design Workflow

When asked to design or improve a CombineFeedback machine, work in this order:

  1. Identify the behavior boundary.
  2. Separate recurring lifecycle behavior from feature-specific policy.
  3. Choose State shape using enums for exclusive modes and structs for

orthogonal facts.

  1. Name public Event cases for user intent, external input, and effect results.
  2. Put all state transitions in reducers.
  3. Decide which feedback primitive owns each effect lifecycle.
  4. Compose reusable child machines with explicit child state and child event cases.
  5. End with a decomposition sketch and a lightweight transition/invariant

contract.

Do not require broad repository reconnaissance. Use the context already provided by the user. Inspect targeted files only when needed to avoid inventing APIs, duplicating an existing reusable machine, or violating local naming/style.

Modeling Rules

Events Trigger Intent, State Owns Lifecycle

Use OnEvent when an effect is a direct reaction to an external event and the effect's lifetime is tied only to that event stream.

Use reducer -> lifecycle/request state -> OnChange when the system must start, cancel, or replace work as state changes. This is the default for loading, pagination, validation, polling, retry, and debounced search lifecycles.

Examples:

  • A button tap that should fire analytics: SideEffect.
  • A one-off event-local async command: OnEvent.
  • A fetch that should cancel when leaving .fetching: reducer sets state,

OnChange observes derived request state.

  • A polling loop that starts on appear and stops on disappear: reducer sets a

polling mode, OnChange observes that mode.

Make Impossible States Unrepresentable

Use sum types for mutually exclusive lifecycle modes:

swift
enum State {
case idle
case fetching(Params)
case success(Success)
case failure(ErrorState)
}

Avoid modeling one lifecycle as independent isLoading, value?, error?, and request? fields. Those combinations create invalid states the reducer must defend manually.

Use product types for facts that can coexist:

swift
struct State {
var query: String
var selectedFilter: Filter
var results: Fetch<SearchResults, SearchRequest>.State
}

Do not store values that can be derived from state. Derive request state from the mode that owns it:

swift
extension Fetch.State {
var request: Params? {
guard case let .fetching(params) = self else { return nil }
return params
}
}
Prefer Reusable Machines For Recurring Lifecycle Behavior

Reusable child machines are a good fit for behavior like:

  • Fetching one resource.
  • Pagination.
  • Username or form-field validation.
  • Debounced search.
  • Polling.
  • Retry/backoff lifecycle.

Keep one-off feature policy local to the feature. Do not extract a reusable machine if its public API would leak parent-specific concepts or create more glue than clarity.

Reusable child machines should own their lifecycle policy and the effect needed to fulfill it. Inject dependencies through the machine initializer:

swift
struct Fetch<Success, Params>: StateMachine {
let fetch: (Params) async throws -> Success
}

The parent supplies concrete services when composing the child.

Compose Child Machines Explicitly

When a parent owns a reusable child machine, expose child state and child events explicitly so Scope can route them:

swift
struct MoviesScreen: StateMachine {
struct State {
var movies: Fetch<MoviePage, Page>.State
var selectedMovieID: Movie.ID?
}
enum Event {
case movies(Fetch<MoviePage, Page>.Event)
case movieTapped(Movie.ID)
}
}

Parent-specific events can still translate into child events when that is the clearest domain API, but do not manually duplicate child lifecycle state in the parent.

Choosing Feedback Primitives

Use this decision rule:

  • OnChange: state-driven lifecycle. Entering a state starts work; leaving or

changing the derived request cancels/replaces work.

  • OnEvent: direct reaction to an event stream. Good for external triggers when

no durable lifecycle state needs to own cancellation.

  • SideEffect: fire-and-forget work that cannot emit events, such as analytics

or logging.

  • Feedback.custom: advanced stream coordination not expressible with

OnChange, OnEvent, or SideEffect.

Prefer OnChange for effect lifecycles that are part of the state machine's meaning. Prefer OnEvent only when modeling the effect as durable state would be artificial.

Canonical Concept: Fetch

Use Fetch as the canonical reusable lifecycle example. This is conceptual Swift-like pseudo-code, not a drop-in generic implementation.

swift
struct Fetch<Success, Params>: StateMachine where Params: Equatable {
let fetch: (Params) async throws -> Success
enum State {
case idle
case fetching(Params)
case success(Success)
case failure(ErrorState)
var request: Params? {
guard case let .fetching(params) = self else { return nil }
return params
}
}
enum Event {
case fetch(Params)
case cancel
case response(Result<Success, ErrorState>)
}
var body: some StateMachine<State, Event> {
Reducer { state, event in
switch event {
case let .fetch(params):
state = .fetching(params)
case .cancel:
state = .idle
case let .response(.success(value)):
state = .success(value)
case let .response(.failure(error)):
state = .failure(error)
}
}
OnChange(of: \.request) { params in
do {
return .response(.success(try await fetch(params)))
} catch {
return .response(.failure(ErrorState(error)))
}
}
}
}

Important modeling points:

  • .fetch(params) is still an event. Events express public intent.
  • .fetching(params) is lifecycle state. State owns whether work should exist.
  • request is derived, not stored separately.
  • OnChange gives latest/cancellation behavior when request changes or becomes

nil.

  • .cancel cancels by moving state to a mode whose derived request is nil.
  • Stale response handling can be added with request identity if the local effect

source is non-cooperative or can emit after cancellation, but do not make that complexity the default V1 pattern.

Required Output Shape

When this skill is used for design work, end with a concise design sketch that contains these sections.

Machine Decomposition

List each machine and its ownership:

text
MoviesScreen
- Owns screen-specific selection and presentation policy.
- Composes Fetch<MoviePage, Page> for page loading.
Fetch<MoviePage, Page>
- Owns loading lifecycle and fetch dependency.
- Emits response events back into its own reducer.
State Shape

Show the proposed state structure and call out which parts are exclusive modes versus orthogonal facts.

Event Contract

List public intent events, child events, and feedback result events. Prefer literal domain names for user/external events and clear result names for effect outputs.

Feedback Plan

For each effect, state which primitive owns it and why:

text
Fetch request: OnChange(of: State.request)
Reason: fetch lifetime follows .fetching(params); cancel is represented by
leaving .fetching.
Transition And Invariant Contract

Use a lightweight contract, not a huge table:

text
Fetch transitions
- idle + fetch(params) -> fetching(params)
- fetching + cancel -> idle
- fetching + response(.success(value)) -> success(value)
- fetching + response(.failure(error)) -> failure(error)
- success/failure + fetch(params) -> fetching(params)
Invariants
- Work exists only when State.request is non-nil.
- request is derived from .fetching, never stored independently.
- success and failure are mutually exclusive modes.
Open Modeling Questions

Ask only questions that affect the state machine boundary or lifecycle, such as:

  • Should success data remain visible while refreshing?
  • Should cancellation return to .idle or preserve previous content?
  • Is pagination a separate child machine or part of the screen's domain policy?
  • Can the effect source emit after cancellation, requiring request identity?

Model-Level Traps To Avoid

Avoid warnings that the API already prevents. Focus on traps the type system and library cannot fully prevent:

  • Boolean/optional soup for a lifecycle that should be an enum.
  • Stored request flags that duplicate derived state.
  • OnEvent for work that must cancel when state leaves a mode.
  • Reusable machines that leak parent-specific policy.
  • Giant screen machines that hide recurring lifecycle submachines.
  • Reopening architecture when the user has already supplied an agreed plan.

Security

  • Security scanPassed, 95 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
686
Repository forks
53
7-day trend score
33

Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/sergdort/combinefeedback/combinefeedback-state-machines/badge)](https://verified-skill.com/skills/sergdort/combinefeedback/combinefeedback-state-machines)

Something wrong with this skill or its page? Tell us.