@@ -205,7 +205,7 @@ setters. A store combines:
205205- a state type;
206206- a serializable action type;
207207- one pure update function;
208- - versioned codecs used by the runtime and snapshot boundary .
208+ - one explicit recovery choice at the store definition .
209209
210210``` koka
211211pub struct task_editor_state(editing : bool, draft : string)
@@ -223,26 +223,34 @@ fun reduce_task_editor(current : task_editor_state, action : task_editor_action)
223223 Finish_edit -> current(editing = False)
224224 Cancel_edit(title) -> Task_editor_state(False, title)
225225
226- pub val task_editor_store : store_spec<task_editor_state,task_editor_action> = Store_spec (
226+ pub val task_editor_store : store_spec<task_editor_state,task_editor_action> = replay_store (
227227 name = "editor",
228- state_codec = State_codec(
229- schema = "todo/task-editor",
230- version = 1,
231- decode = decode_task_editor,
232- encode = encode_task_editor),
233228 action_codec = Action_codec(
234229 schema = "todo/task-editor-action",
235230 version = 1,
236231 decode = decode_task_editor_action,
237232 encode = encode_task_editor_action),
233+ replay = fn(action) {
234+ match action
235+ Begin_edit(_) -> Replay_start
236+ Change_draft(_) -> Replay_replace("draft")
237+ Finish_edit -> Replay_reset
238+ Cancel_edit(_) -> Replay_reset
239+ },
238240 reduce = reduce_task_editor)
239241```
240242
241243Store definitions use labelled fields deliberately. ` name ` owns runtime
242- identity, ` state_codec ` owns snapshot compatibility, ` action_codec ` owns the
243- observable wire action, and ` reduce ` is the pure transition. Avoid positional
244- ` Store_spec(...) ` , ` State_codec(...) ` , and ` Action_codec(...) ` calls: they are
245- shorter but make schema/version and encode/decode order too easy to misread.
244+ identity, ` action_codec ` owns the observable wire action, and ` reduce ` is the
245+ pure transition. ` replay ` declares a bounded editor session: begin replaces an
246+ old session, repeated draft changes replace the same stable slot, and
247+ finish/cancel return to the call site's latest ` initial ` value.
248+
249+ Use ` snapshot_store(...) ` when state cannot be safely represented by
250+ ` Replay_start ` / ` Replay_replace(slot) ` / ` Replay_reset ` . Counters, arbitrary
251+ toggle history, and accumulative collections normally keep an explicit state
252+ codec. The runtime never truncates arbitrary actions because doing so can
253+ change reducer semantics.
246254
247255The component-facing call mirrors React's reducer pair:
248256
@@ -271,9 +279,11 @@ button(
271279 dispatch = dispatch))
272280```
273281
274- The codecs are defined once beside the store. They are not passed through every
275- component call. Explicit scope/path/tree access is reserved for framework and
276- testing code.
282+ The required codecs are defined once beside the store. They are not passed
283+ through every component call. Explicit scope/path/tree access is reserved for
284+ framework and testing code. See
285+ [ ` docs/store-recovery.md ` ] ( docs/store-recovery.md ) for the recovery decision,
286+ replay modes, migration behavior, and complete examples.
277287
278288Feature render and panel APIs return only ` vnode ` . The app boundary owns the
279289runtime tree through ` runtime_frame ` , and one ` run_component(...) ` pass collects
@@ -364,12 +374,13 @@ logger. The browser runtime uses `run_runtime_action_observed(...)` to log the
364374same stream, while ` run_runtime_action(...) ` deliberately handles and discards
365375it for callers that do not need observation.
366376
367- This is an intent log, not a replay engine. Dispatch is observed before the
368- reducer/workflow runs, so an action remains visible even when confirmation
369- rejects it. Replaying actions that invoke browser or service effects needs a
370- separate policy for permissions, deduplication, and recorded responses. Until
371- that policy exists, the runtime does not automatically persist or replay the
372- action stream.
377+ This observation stream is an intent log, not a general replay engine. Dispatch
378+ is observed before the reducer/workflow runs, so an action remains visible even
379+ when confirmation rejects it. A component may explicitly choose
380+ ` replay_store(...) ` ; that store persists only its own scoped pure actions and
381+ does not consume the observation stream. Replaying actions that invoke browser
382+ or service effects still needs a separate policy for permissions,
383+ deduplication, and recorded responses.
373384
374385## State snapshots, HMR, and reloads
375386
@@ -382,6 +393,8 @@ defensive:
382393- malformed snapshot data is ignored instead of reaching a component decoder;
383394- component state is restored only when its keyed scope and store schema still
384395 match;
396+ - replay stores use ` respo/replay:<action-schema> ` entries and rebuild state
397+ from the current ` initial ` plus decoded component actions;
385398- ` respo/component-scope ` metadata preserves ordinary-child ownership across
386399 HMR/reload so stale child branches can be swept on the next feature render.
387400
0 commit comments