@@ -194,14 +194,95 @@ MyWorkflow.renderForTest {
194194
195195## Common Pitfalls
196196
197- 1 . ** Don't capture stale state** — In action lambdas, always use the ` state ` property from the
198- ` Updater ` receiver, never the ` renderState ` parameter from ` render() ` .
197+ 1 . ** Don't capture stale state** — This is the most common and dangerous pitfall. The ` renderState `
198+ parameter (and any local variable derived from it) is a snapshot from render time. By the time an
199+ action or eventHandler fires, the real state may have changed. ** Always read from ` state ` on the
200+ ` Updater ` receiver inside action/eventHandler lambdas.**
201+
202+ ``` kotlin
203+ // BAD — direct capture of renderState
204+ onSave = context.eventHandler(" onSave" ) {
205+ state = renderState.copy(saving = true ) // STALE!
206+ }
207+
208+ // BAD — indirect capture via local variable (easy to miss!)
209+ val currentName = renderState.name
210+ val isAdmin = renderState.role == Role .ADMIN
211+ onSave = context.eventHandler(" onSave" ) {
212+ state = state.copy(savedName = currentName) // STALE! currentName came from renderState
213+ }
214+
215+ // GOOD — read from `state` on the Updater receiver
216+ onSave = context.eventHandler(" onSave" ) {
217+ state = state.copy(saving = true )
218+ }
219+ ```
220+
221+ ** With sealed state hierarchies** , use ` safeAction ` to safely narrow the state type — it no-ops
222+ if the state has changed to a different subtype by the time the action fires:
223+
224+ ``` kotlin
225+ private fun onConfirm (item : Item ) = safeAction<MyState .Editing >(" onConfirm" ) { editingState ->
226+ state = MyState .Confirmed (editingState.draft, item)
227+ }
228+ ```
229+
230+ ** Rule of thumb** : if a variable was assigned from ` renderState ` (directly or transitively), it
231+ must NOT be referenced inside an ` action {} ` or ` eventHandler {} ` lambda. Re-derive it from
232+ ` state ` inside the lambda instead.
233+
1992342 . ** Don't perform side effects in ` render() ` ** — ` render ` may be called multiple times for the
200235 same state. Use ` runningWorker ` or ` runningSideEffect ` instead.
2012363 . ** Don't use ` runBlocking ` ** — Use coroutines and Workers for async work.
2022374 . ** Don't forget ` name ` on ` eventHandler ` ** — Required for Compose stability and debugging.
2032385 . ** Don't emit more than one output per action** — Call ` setOutput() ` at most once.
204239
240+ ## Performance Best Practices
241+
242+ ### Render Rules
243+
244+ - ** ` render() ` must be idempotent** — no long-running operations, disk access, or extensive object
245+ creation.
246+ - ** ` snapshotState() ` must not serialize** — return a lazy ` Snapshot ` ; serialization happens only
247+ when needed.
248+ - ** One render per action** — populate all state data in ` initialState() ` to avoid unnecessary
249+ intermediate render passes.
250+ - ** Don't render children to inform parent state** — extract shared logic into helper classes or
251+ ` Scoped ` objects accessible to both parent and child.
252+ - ** Filter upstream signals at the source** — filter Worker streams before handling, not in the
253+ output handler, to prevent unnecessary renders.
254+ - ** Hoist shared state** — when multiple leaf workflows share state, manage it at the lowest common
255+ ancestor and pass via props for a single render pass.
256+
257+ ### Worker & Action Rules
258+
259+ - Only create Workers when state changes are expected.
260+ - Combine multiple Workers sharing the same source into a single Worker.
261+ - Avoid ` Worker<Unit> ` except for timers.
262+ - Use ` combine() ` / ` combineTransform() ` to consolidate multiple flow lookups into single-pass
263+ state updates.
264+ - Prefer ` SharedFlow ` over ` StateFlow ` for one-time events (avoids automatic re-emission on
265+ collection).
266+
267+ ### eventHandler Rules
268+
269+ - Assign ` eventHandler ` directly to rendering callbacks only.
270+ - Use only when state changes are intended.
271+ - ** Never nest ` action() ` calls within ` eventHandler ` lambdas.**
272+
273+ ### Compose Stability
274+
275+ - Use stable types in renderings — prefer ` ImmutableList ` over ` List ` .
276+ - Don't pass ` Lazy ` delegates to composable functions.
277+ - Use ` RenderContext#remember ` to cache expensive view model computations when inputs are unchanged.
278+
279+ ### Dependency Injection
280+
281+ - Use ` dagger.Lazy<T> ` for conditionally-used dependencies to defer expensive instantiation
282+ (especially useful behind feature flags).
283+ - Use factories only for dependency injection, not stateful object construction — stateful objects
284+ should be properties for runtime optimization compatibility.
285+
205286## Naming Conventions
206287
207288- ** Workflow** : ` [Feature]Workflow ` (e.g., ` LoginWorkflow ` )
0 commit comments