2. Derive UI state
Goal: calculate UI state from the source of truth, and understand the yield* rule that Craft and Effect share.
Use computedEffect for synchronous Effect-backed derivations. Its factory is a generator when it reads a Craft value and returns the Effect to run:
import { Effect } from 'effect';
import { state } from '@craft-ts/core';
import { computedEffect } from '@craft-ts/effect';
const tasks = yield* state('tasks', [] as Task[]);
const remaining = computedEffect('remaining', function* () {
const currentTasks = yield* tasks();
return Effect.succeed(
currentTasks.filter((task) => !task.done).length,
);
});The template can bind remaining directly. computedEffect runs the returned synchronous Effect in place, and Craft re-runs only the binding that depends on it.
The shared dependency vocabulary
Both runtimes use generators, but they solve different problems:
const tasks = yield* TaskList(); // Craft service
const access = yield* AccessPolicyService; // Effect service inside an Effect
const value = yield* resource.value(); // Craft reader inside a derivationThe rule is the same: yield what the current function does not own. A Craft factory yields Craft dependencies; an Effect program yields Effect dependencies. The adapter connects the two at a deliberate boundary.
Do not duplicate domain state in the component
The component should not subscribe to an Effect, convert an Effect to a signal by hand, or start a fiber in a template callback. Those approaches hide loading, cancellation and failure state from Craft. Instead:
- Keep the domain operation as
Effect<A, E, R>. - Expose it through a Craft Effect-aware primitive.
- Derive the display state from the resulting Craft resource.
The next step defines the domain operation and the services it requires.
What you gained
Derived state that stays reactive and a clear division: Craft derives the UI; Effect composes the domain program.