feat: add an optional effect map to createStore (RD-05)

createStore now takes a third, optional StoreEffects map. Each key is a
Model tag. The store runs that tag's handler after update() returns, and
only when the store enters the tag: the previous tag differs from the new
tag, and the message is not Seed (the mount/restore message in every
machine that has one).

This closes the gap where a component had to call dispatch(msg) and then
a private runIfSubmitting() by hand, or state got silently stuck. No call
site changes here. RD-06 and RD-08 migrate the 5 components that
duplicate that pattern today.

The effect map is a conditional type, not a generic constraint, so a
tagless Model (store.spec.ts's plain number store) still resolves it to
never and needs no third argument. Both tag checks use a typeof/in guard
for the same reason.

Regenerated libs/shared/docs/behaviour-spec.mdx for the 5 new spec titles.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
eho
2026-09-04 16:34:28 +02:00
co-authored by Claude Sonnet 5
parent 0697afb737
commit a71887a843
5 changed files with 311 additions and 8 deletions
+6 -1
View File
@@ -20,7 +20,7 @@ tested where._
Every bullet below is a real test name from the suite — an `it()` title (frontend) or a test
method name (backend), read as a sentence. Nothing here is hand-written prose: this page
**is** the suite, reshaped for a business reader. 505 frontend behaviours across
**is** the suite, reshaped for a business reader. 510 frontend behaviours across
9 contexts; 261 backend behaviours across 42 test
classes.
@@ -752,6 +752,11 @@ classes.
- applies the pure update on dispatch
- dispatch from inside an effect does not self-loop
- fires the effect when the store enters the tag
- does not fire when the tag is unchanged
- does not fire for a Seed message
- a dispatch from inside the effect lands
- the narrowed state is passed to the effect
#### currentRole (dev mechanism)