machinalayout 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/README.md +295 -49
  2. package/dist/chunk-2ZQ2RFFI.js +400 -0
  3. package/dist/chunk-33CKBEJH.js +186 -0
  4. package/dist/chunk-BJOQRPPX.js +382 -0
  5. package/dist/chunk-KYWOCAHK.js +205 -0
  6. package/dist/chunk-RJYRJ3LD.js +0 -0
  7. package/dist/chunk-SVWYWI7I.js +59 -0
  8. package/dist/chunk-VREK57S3.js +13 -0
  9. package/dist/chunk-ZVDE7PX4.js +222 -0
  10. package/dist/debugOverlay-pJpj0n5H.d.ts +125 -0
  11. package/dist/deus/index.d.ts +14 -0
  12. package/dist/deus/index.js +26 -0
  13. package/dist/dispatch/index.d.ts +49 -0
  14. package/dist/dispatch/index.js +217 -0
  15. package/dist/handoff/index.d.ts +44 -0
  16. package/dist/handoff/index.js +83 -0
  17. package/dist/index.d.ts +54 -236
  18. package/dist/index.js +753 -583
  19. package/dist/inspect/index.d.ts +8 -0
  20. package/dist/inspect/index.js +97 -0
  21. package/dist/react/index.d.ts +41 -0
  22. package/dist/react/index.js +9 -0
  23. package/dist/react-native/index.d.ts +30 -0
  24. package/dist/react-native/index.js +84 -0
  25. package/dist/screenCatalog-ZjonGiOi.d.ts +46 -0
  26. package/dist/text/index.d.ts +10 -0
  27. package/dist/text/index.js +9 -0
  28. package/dist/text/react/index.d.ts +14 -0
  29. package/dist/text/react/index.js +7 -0
  30. package/dist/text/react-native/index.d.ts +16 -0
  31. package/dist/text/react-native/index.js +155 -0
  32. package/dist/text/vue/index.d.ts +113 -0
  33. package/dist/text/vue/index.js +202 -0
  34. package/dist/types-B90jb3RW.d.ts +184 -0
  35. package/dist/types-C4poVJpR.d.ts +74 -0
  36. package/dist/types-DLYAhNXw.d.ts +32 -0
  37. package/dist/vue/index.d.ts +173 -0
  38. package/dist/vue/index.js +112 -0
  39. package/docs/adapter-packaging-a0-plan.md +352 -0
  40. package/docs/adapters.md +19 -0
  41. package/docs/api-coherence-m8-audit.md +397 -0
  42. package/docs/deusmachina.md +108 -0
  43. package/docs/error-codes.md +95 -0
  44. package/docs/grid-arrange-m5a-contract.md +480 -0
  45. package/docs/grid-arrange.md +51 -0
  46. package/docs/inspection-and-handoff.md +126 -0
  47. package/docs/layout-interpolation.md +52 -0
  48. package/docs/machina-dispatch-d0-contract.md +496 -0
  49. package/docs/machina-dispatch.md +143 -0
  50. package/docs/named-layers.md +40 -0
  51. package/docs/react-adapter.md +63 -58
  52. package/docs/react-native-adapter.md +56 -0
  53. package/docs/react-native-text-renderer.md +50 -0
  54. package/docs/reference-alignment-m7a-contract.md +384 -0
  55. package/docs/reference-alignment.md +44 -0
  56. package/docs/responsive-variants.md +54 -0
  57. package/docs/screen-catalog-and-viewports.md +124 -0
  58. package/docs/stack-geometry-helpers.md +115 -0
  59. package/docs/vue-adapter.md +55 -0
  60. package/docs/vue-text-renderer.md +55 -0
  61. package/package.json +127 -60
@@ -0,0 +1,496 @@
1
+ > D1 status (2026-05-12): Implemented in `src/dispatch` with subpath export `machinalayout/dispatch`.
2
+
3
+ # MachinaDispatch D0 Contract
4
+
5
+ ## 1. Executive summary
6
+
7
+ MachinaDispatch D0 defines a tiny, pure, framework-independent **event dispatch table** module for table-driven UI transitions.
8
+
9
+ It is intentionally narrow:
10
+
11
+ - Input: `state + event + dispatch tables`.
12
+ - Output: `next state`.
13
+ - Canonical operation: `dispatchEvent(state, event, tables)`.
14
+
15
+ It is **not** a router, store, middleware layer, effect engine, or state framework. It only maps events to one-field state updates through compact **columnar dispatch tables**.
16
+
17
+ ## 2. Actual problem
18
+
19
+ Many UI transitions are repetitive and local, such as:
20
+
21
+ - set a route-like field,
22
+ - toggle a boolean,
23
+ - increment/decrement a number,
24
+ - derive a field from an event suffix.
25
+
26
+ In practice, these flows often begin as repeated event `if`/`switch` chains. The Oct Storefront M1→M7 evolution showed that these patterns become clearer and safer when represented as data tables and pure reducers.
27
+
28
+ D0 captures that lesson: repeated UI event logic should become data, while remaining a tiny pure dispatcher.
29
+
30
+ ## 3. Non-goals
31
+
32
+ D1 must explicitly exclude:
33
+
34
+ - React hook,
35
+ - Vue composable,
36
+ - React Native hook/composable,
37
+ - global store,
38
+ - subscriptions,
39
+ - middleware,
40
+ - async actions/loaders,
41
+ - browser history ownership,
42
+ - URL parser,
43
+ - nested routes,
44
+ - guards,
45
+ - effects,
46
+ - undo/redo,
47
+ - persistence,
48
+ - devtools,
49
+ - reducer composition framework,
50
+ - Dominatus/Octomata-style state machine system,
51
+ - router cathedral behavior.
52
+
53
+ ## 4. Design principles
54
+
55
+ 1. Pure functions only.
56
+ 2. No framework dependency.
57
+ 3. No React/Vue/RN hooks in D1.
58
+ 4. No subscriptions.
59
+ 5. No global store.
60
+ 6. No browser history.
61
+ 7. No async loaders/actions.
62
+ 8. No middleware.
63
+ 9. No guards.
64
+ 10. No nested routes.
65
+ 11. No effects.
66
+ 12. No state machine.
67
+ 13. No Dominatus port.
68
+ 14. No router cathedral.
69
+
70
+ Operationally:
71
+
72
+ - If no rule matches, return the original state object by reference.
73
+ - If one rule matches and produces a changed value, return a shallow copy with one field updated.
74
+ - Never mutate input state or tables.
75
+
76
+ ## 5. Columnar table model
77
+
78
+ Canonical authoring model is **columnar operation-specific tables**, not repeated row objects.
79
+
80
+ Rationale:
81
+
82
+ - Reduces repeated keys (`event`, `field`, `op`) in small tables.
83
+ - Keeps intent close to spreadsheet-like authoring.
84
+ - Improves scanability for simple UI transition rules.
85
+
86
+ Canonical shape:
87
+
88
+ ```ts
89
+ const dispatch = defineDispatchTables<AppState>({
90
+ set: {
91
+ events: ["nav.home", "nav.settings"],
92
+ fields: ["route", "route"],
93
+ values: ["home", "settings"],
94
+ },
95
+ toggle: {
96
+ events: ["filter.new"],
97
+ fields: ["newOnly"],
98
+ },
99
+ increment: {
100
+ events: ["cart.add", "cart.remove"],
101
+ fields: ["cartCount", "cartCount"],
102
+ by: [1, -1],
103
+ },
104
+ setSuffix: {
105
+ prefixes: ["product.inspect."],
106
+ fields: ["selectedProduct"],
107
+ allowedSuffixes: [PRODUCT_IDS],
108
+ },
109
+ });
110
+ ```
111
+
112
+ Table “views”:
113
+
114
+ - `set`: `event | field | value`
115
+ - `toggle`: `event | field`
116
+ - `increment`: `event | field | by`
117
+ - `setSuffix`: `prefix | field | allowedSuffixes`
118
+ - `incrementSuffix`: `prefix | field | by | allowedSuffixes`
119
+
120
+ Naming decision:
121
+
122
+ - Use plural column names (`events`, `fields`, `values`, `prefixes`) because each property is a column vector.
123
+
124
+ ## 6. Type proposal
125
+
126
+ D1 should start from this model (with small refinements):
127
+
128
+ ```ts
129
+ export type SetDispatchTable<TState> = {
130
+ events: readonly string[];
131
+ fields: readonly (keyof TState)[];
132
+ values: readonly unknown[];
133
+ };
134
+
135
+ export type ToggleDispatchTable<TState> = {
136
+ events: readonly string[];
137
+ fields: readonly (keyof TState)[];
138
+ };
139
+
140
+ export type IncrementDispatchTable<TState> = {
141
+ events: readonly string[];
142
+ fields: readonly (keyof TState)[];
143
+ by?: readonly number[];
144
+ };
145
+
146
+ export type PrefixSetDispatchTable<TState> = {
147
+ prefixes: readonly string[];
148
+ fields: readonly (keyof TState)[];
149
+ allowedSuffixes?: readonly (readonly string[] | undefined)[];
150
+ };
151
+
152
+ export type PrefixIncrementDispatchTable<TState> = {
153
+ prefixes: readonly string[];
154
+ fields: readonly (keyof TState)[];
155
+ by?: readonly number[];
156
+ allowedSuffixes?: readonly (readonly string[] | undefined)[];
157
+ };
158
+
159
+ export type MachinaDispatchTables<TState> = {
160
+ set?: SetDispatchTable<TState>;
161
+ toggle?: ToggleDispatchTable<TState>;
162
+ increment?: IncrementDispatchTable<TState>;
163
+ setSuffix?: PrefixSetDispatchTable<TState>;
164
+ incrementSuffix?: PrefixIncrementDispatchTable<TState>;
165
+ };
166
+
167
+ export function defineDispatchTables<TState>(
168
+ tables: MachinaDispatchTables<TState>,
169
+ ): MachinaDispatchTables<TState>;
170
+ ```
171
+
172
+ D0 decisions:
173
+
174
+ - `by` defaults to `1` when omitted per row.
175
+ - `allowedSuffixes` is per-row (`index` aligned with `prefixes`/`fields`).
176
+ - Suffix mismatch against an allowed list is a **non-match**, not an error.
177
+ - `incrementSuffix` is included in D1 (simple and useful for catalog-driven count updates).
178
+ - `defineDispatchTables` should preserve type inference and perform optional lightweight runtime table-shape validation in development; `dispatchEvent` must still validate defensively.
179
+
180
+ Type limits acknowledged:
181
+
182
+ - `keyof TState` does not guarantee runtime value type at a field.
183
+ - `toggle` and `increment` require runtime value checks.
184
+ - `values` remain `unknown[]` in D1 for simplicity.
185
+ - Advanced field-aware type gymnastics are deferred.
186
+
187
+ ## 7. Dispatch semantics
188
+
189
+ Primary function for D1:
190
+
191
+ ```ts
192
+ dispatchEvent<TState extends Record<string, unknown>>(
193
+ state: TState,
194
+ event: string,
195
+ tables: MachinaDispatchTables<TState>,
196
+ ): TState
197
+ ```
198
+
199
+ Matching order is fixed:
200
+
201
+ 1. `set`
202
+ 2. `toggle`
203
+ 3. `increment`
204
+ 4. `setSuffix`
205
+ 5. `incrementSuffix`
206
+
207
+ Rules:
208
+
209
+ - Within a group, scan arrays in index order.
210
+ - First matching row in a group wins.
211
+ - First matching group by fixed order wins.
212
+ - No multi-action event fan-out.
213
+ - No global explicit priority system in D1.
214
+
215
+ Result behavior:
216
+
217
+ - If no match: return original `state` reference.
218
+ - If match and computed value is `Object.is(currentValue, nextValue)`: return original `state` reference.
219
+ - Else: return shallow copied state with only the target field updated.
220
+
221
+ Helper decision for D1:
222
+
223
+ - Include internal/public helpers if they reduce duplication and clarify behavior:
224
+ - `resolveEventValue<TValue>(event, { events, values })`
225
+ - `matchEventPrefix(event, prefix, allowedSuffixes?)`
226
+ - These helpers are acceptable D1 exports from `machinalayout/dispatch` if kept narrow and pure.
227
+
228
+ ## 8. Operation semantics
229
+
230
+ ### `set`
231
+
232
+ - Match: exact event string equality.
233
+ - Update: `next[field] = value`.
234
+
235
+ ### `toggle`
236
+
237
+ - Match: exact event equality.
238
+ - Runtime requirement: current field value is boolean.
239
+ - Update: `next[field] = !state[field]`.
240
+ - Invalid runtime value => throw dispatch error.
241
+
242
+ ### `increment`
243
+
244
+ - Match: exact event equality.
245
+ - Runtime requirement: current field value is number.
246
+ - Delta `by`:
247
+ - default `1` if `by` omitted,
248
+ - else use row delta,
249
+ - must be finite.
250
+ - Update: `next[field] = state[field] + by`.
251
+ - Invalid field value or invalid `by` => throw dispatch error.
252
+
253
+ ### `setSuffix`
254
+
255
+ - Match: event starts with row `prefix`.
256
+ - `suffix = event.slice(prefix.length)`.
257
+ - If row allowed list exists: suffix must be present, else non-match.
258
+ - Update: `next[field] = suffix`.
259
+
260
+ ### `incrementSuffix`
261
+
262
+ - Match: event starts with row `prefix`.
263
+ - Suffix handling same as `setSuffix`.
264
+ - Runtime requirement: target field is number.
265
+ - Delta `by` default is `1`, row override when provided and finite.
266
+ - Update: numeric increment.
267
+ - Included in D1.
268
+
269
+ ## 9. Error model
270
+
271
+ D1 should define a dedicated error type, separate from layout errors:
272
+
273
+ ```ts
274
+ export type MachinaDispatchErrorCode =
275
+ | "InvalidDispatchTable"
276
+ | "InvalidDispatchField"
277
+ | "InvalidDispatchValue"
278
+ | "InvalidDispatchEvent";
279
+
280
+ export class MachinaDispatchError extends Error {
281
+ readonly code: MachinaDispatchErrorCode;
282
+ }
283
+ ```
284
+
285
+ Throw conditions:
286
+
287
+ - `InvalidDispatchTable`
288
+ - column length mismatch within a table group,
289
+ - structurally invalid table columns at runtime.
290
+ - `InvalidDispatchField`
291
+ - referenced field key is missing in runtime state object.
292
+ - `InvalidDispatchValue`
293
+ - toggle target is not boolean,
294
+ - increment target is not number,
295
+ - increment delta is non-finite.
296
+ - `InvalidDispatchEvent`
297
+ - non-string event input,
298
+ - non-string prefix/event entries in table misuse.
299
+
300
+ Suffix not in `allowedSuffixes` is non-match (not an error).
301
+
302
+ No diagnostics subsystem is required in D1.
303
+
304
+ ## 10. Immutability semantics
305
+
306
+ D1 contract:
307
+
308
+ - Never mutate `state`.
309
+ - Never mutate `tables`.
310
+ - No match => return exact same state reference.
311
+ - Match with identity-equal value via `Object.is` => return same state reference.
312
+ - Match with changed value => return new shallow object with one updated field.
313
+
314
+ This minimizes unnecessary UI rerenders in userland frameworks.
315
+
316
+ ## 11. Relationship to routing
317
+
318
+ Routing-like behavior is represented as state assignment:
319
+
320
+ ```ts
321
+ type AppState = { route: "library" | "settings" };
322
+
323
+ const tables = defineDispatchTables<AppState>({
324
+ set: {
325
+ events: ["nav.library", "nav.settings"],
326
+ fields: ["route", "route"],
327
+ values: ["library", "settings"],
328
+ },
329
+ });
330
+
331
+ const next = dispatchEvent(state, "nav.settings", tables);
332
+ ```
333
+
334
+ No browser history, nested route tree, loader/action graph, or guard engine is owned by MachinaDispatch.
335
+
336
+ ## 12. Relationship to MachinaLayout
337
+
338
+ Composition without coupling:
339
+
340
+ ```ts
341
+ const nextState = dispatchEvent(state, event, dispatch);
342
+ const routeRows = rowsByRoute[nextState.route];
343
+ const layout = resolveLayoutRows(routeRows, rootRect);
344
+ ```
345
+
346
+ Boundaries:
347
+
348
+ - MachinaDispatch does not call layout resolvers.
349
+ - MachinaLayout does not call dispatch functions.
350
+ - Userland composes both.
351
+
352
+ ## 13. Package/subpath plan
353
+
354
+ Recommended packaging target for D1:
355
+
356
+ - public subpath: `machinalayout/dispatch`
357
+ - build outputs:
358
+ - `dist/dispatch/index.js`
359
+ - `dist/dispatch/index.d.ts`
360
+
361
+ Policy:
362
+
363
+ - no framework peer dependencies,
364
+ - no adapter dependencies,
365
+ - subpath-only initially to avoid root API bloat.
366
+
367
+ ## 14. Examples
368
+
369
+ ### A) Routing as state assignment
370
+
371
+ ```ts
372
+ const tables = defineDispatchTables<{ route: "home" | "settings" }>({
373
+ set: {
374
+ events: ["nav.home", "nav.settings"],
375
+ fields: ["route", "route"],
376
+ values: ["home", "settings"],
377
+ },
378
+ });
379
+ ```
380
+
381
+ ### B) Filter toggle
382
+
383
+ ```ts
384
+ const tables = defineDispatchTables<{ newOnly: boolean }>({
385
+ toggle: {
386
+ events: ["filter.new"],
387
+ fields: ["newOnly"],
388
+ },
389
+ });
390
+ ```
391
+
392
+ ### C) Cart increment/decrement
393
+
394
+ ```ts
395
+ const tables = defineDispatchTables<{ cartCount: number }>({
396
+ increment: {
397
+ events: ["cart.add", "cart.remove"],
398
+ fields: ["cartCount", "cartCount"],
399
+ by: [1, -1],
400
+ },
401
+ });
402
+ ```
403
+
404
+ ### D) Product inspect via prefix/suffix
405
+
406
+ ```ts
407
+ const PRODUCT_IDS = ["p1", "p2", "p3"] as const;
408
+
409
+ const tables = defineDispatchTables<{ selectedProduct: string | null }>({
410
+ setSuffix: {
411
+ prefixes: ["product.inspect."],
412
+ fields: ["selectedProduct"],
413
+ allowedSuffixes: [PRODUCT_IDS],
414
+ },
415
+ });
416
+ ```
417
+
418
+ ### E) React userland usage (no custom hook)
419
+
420
+ ```ts
421
+ const [state, setState] = useState(initialState);
422
+
423
+ function onEvent(event: string) {
424
+ setState((s) => dispatchEvent(s, event, tables));
425
+ }
426
+ ```
427
+
428
+ ### F) Vue userland usage (no Pinia required)
429
+
430
+ ```ts
431
+ const state = ref(initialState);
432
+
433
+ function onEvent(event: string) {
434
+ state.value = dispatchEvent(state.value, event, tables);
435
+ }
436
+ ```
437
+
438
+ ### G) Integrating route state with Machina layout rows
439
+
440
+ ```ts
441
+ const next = dispatchEvent(state, event, tables);
442
+ const rows = rowsByRoute[next.route];
443
+ const layout = resolveLayoutRows(rows, rootRect);
444
+ ```
445
+
446
+ ## 15. D1 implementation plan
447
+
448
+ 1. Add `src/dispatch/types.ts`.
449
+ 2. Add `src/dispatch/errors.ts` with `MachinaDispatchError` and codes.
450
+ 3. Add `src/dispatch/dispatchEvent.ts`.
451
+ 4. Add `src/dispatch/helpers.ts` (for `resolveEventValue`, `matchEventPrefix`) if included publicly.
452
+ 5. Add `src/dispatch/index.ts`.
453
+ 6. Add package subpath export `./dispatch` and build entry `dispatch/index`.
454
+ 7. Add tests for dispatch semantics and error behavior.
455
+ 8. Add docs/README section linking `machinalayout/dispatch`.
456
+ 9. Run build/test/pack smoke checks.
457
+
458
+ ## 16. D1 test plan
459
+
460
+ Planned coverage:
461
+
462
+ - set dispatch,
463
+ - toggle dispatch,
464
+ - increment dispatch,
465
+ - increment default `by`,
466
+ - increment row-specific `by`,
467
+ - setSuffix with allowed suffix,
468
+ - setSuffix with disallowed suffix returns same object,
469
+ - incrementSuffix behavior,
470
+ - fixed group order first-match behavior,
471
+ - first row wins within group,
472
+ - no match returns same object reference,
473
+ - matched identical value returns same object reference,
474
+ - matched changed value returns shallow copy,
475
+ - tables not mutated,
476
+ - table length mismatch error,
477
+ - invalid toggle field type error,
478
+ - invalid increment field type error,
479
+ - non-finite `by` error,
480
+ - missing runtime field error,
481
+ - route assignment scenario,
482
+ - React-style usage as plain function test,
483
+ - Vue-style usage as plain function test.
484
+
485
+ ## 17. Risks and mitigations
486
+
487
+ - Risk: scope creep into state framework.
488
+ - Mitigation: enforce non-goals and pure dispatch-only API.
489
+ - Risk: confusion about routing ownership.
490
+ - Mitigation: explicitly document “routing is state assignment”; no history ownership.
491
+ - Risk: runtime type mismatch despite TS keys.
492
+ - Mitigation: strict runtime checks + explicit dispatch error codes.
493
+ - Risk: table authoring mistakes (length mismatch).
494
+ - Mitigation: deterministic validation and early `InvalidDispatchTable` errors.
495
+ - Risk: API bloat at package root.
496
+ - Mitigation: ship as `machinalayout/dispatch` subpath first.
@@ -0,0 +1,143 @@
1
+ # MachinaDispatch Runtime Guide (D1)
2
+
3
+ ## Purpose
4
+
5
+ MachinaDispatch is a tiny, pure table-driven event dispatcher for single-field state transitions:
6
+
7
+ `state + event + dispatch tables -> next state`
8
+
9
+ Import from the subpath:
10
+
11
+ ```ts
12
+ import { defineDispatchTables, dispatchEvent } from "machinalayout/dispatch";
13
+ ```
14
+
15
+ ## Thesis
16
+
17
+ MachinaDispatch is not a router, store, middleware layer, or async framework. It only maps event strings to deterministic state updates through columnar tables.
18
+
19
+ ## Dispatch tables
20
+
21
+ ```ts
22
+ type AppState = { route: "home" | "settings"; cartCount: number; newOnly: boolean; selectedProduct: string };
23
+
24
+ const tables = defineDispatchTables<AppState>({
25
+ set: {
26
+ events: ["nav.home", "nav.settings"],
27
+ fields: ["route", "route"],
28
+ values: ["home", "settings"],
29
+ },
30
+ toggle: {
31
+ events: ["filter.new"],
32
+ fields: ["newOnly"],
33
+ },
34
+ increment: {
35
+ events: ["cart.add"],
36
+ fields: ["cartCount"],
37
+ by: [1],
38
+ },
39
+ setSuffix: {
40
+ prefixes: ["product.inspect."],
41
+ fields: ["selectedProduct"],
42
+ allowedSuffixes: [["p1", "p2"]],
43
+ },
44
+ });
45
+ ```
46
+
47
+ ## Operation semantics
48
+
49
+ Matching order is fixed: `set -> toggle -> increment -> setSuffix -> incrementSuffix`.
50
+
51
+ - First matching group wins.
52
+ - Within each group, first matching row wins.
53
+ - No match returns the same state object.
54
+
55
+ ## Error model
56
+
57
+ Errors throw `MachinaDispatchError` with stable codes:
58
+
59
+ - `InvalidDispatchTable`
60
+ - `InvalidDispatchField`
61
+ - `InvalidDispatchValue`
62
+ - `InvalidDispatchEvent`
63
+
64
+ ## Immutability
65
+
66
+ - Never mutates input state or tables.
67
+ - Identity-equal updates return the original state reference.
68
+ - Changed updates return a shallow copy with one changed field.
69
+
70
+ ## Routing as state assignment
71
+
72
+ ```ts
73
+ const next = dispatchEvent({ route: "home" }, "nav.settings", tables);
74
+ ```
75
+
76
+ This is state assignment, not URL parsing/history/router trees.
77
+
78
+ ## Composition with MachinaLayout
79
+
80
+ ```ts
81
+ const nextState = dispatchEvent(state, event, tables);
82
+ const rows = rowsByRoute[nextState.route];
83
+ const layout = resolveLayoutRows(rows, rootRect);
84
+ ```
85
+
86
+ Dispatch and layout remain decoupled.
87
+
88
+
89
+ ## Smallest useful example
90
+
91
+ ```ts
92
+ type CounterState = {
93
+ count: number;
94
+ };
95
+
96
+ const DISPATCH = defineDispatchTables<CounterState>({
97
+ increment: {
98
+ events: ["counter.increment"],
99
+ fields: ["count"],
100
+ by: [1],
101
+ },
102
+ });
103
+
104
+ const next = dispatchEvent({ count: 0 }, "counter.increment", DISPATCH);
105
+ // => { count: 1 }
106
+ ```
107
+
108
+ React usage needs only framework state:
109
+
110
+ ```ts
111
+ const [state, setState] = useState<CounterState>({ count: 0 });
112
+ const send = (event: string) => {
113
+ setState((s) => dispatchEvent(s, event, DISPATCH));
114
+ };
115
+ ```
116
+
117
+ Vue usage is equally small:
118
+
119
+ ```ts
120
+ const state = ref<CounterState>({ count: 0 });
121
+ const send = (event: string) => {
122
+ state.value = dispatchEvent(state.value, event, DISPATCH);
123
+ };
124
+ ```
125
+
126
+ No MachinaDispatch hook, provider, router, or store runtime is required.
127
+
128
+ ## When to use MachinaDispatch
129
+
130
+ Use MachinaDispatch when an event can be expressed as a simple field transition:
131
+
132
+ - `field = value`
133
+ - `field = !field`
134
+ - `field += n`
135
+ - `field = event suffix`
136
+
137
+ If behavior needs async effects, timers, retries, guards, hierarchical states, trace/replay, or orchestration, keep that in real app logic (or Dominatus/userland code) and keep MachinaDispatch as the tiny deterministic table layer.
138
+
139
+ If Dominatus would be overkill, you probably need a table, not a state manager.
140
+
141
+ ## Non-goals
142
+
143
+ No hooks, composables, browser history, URL parsing, router trees, middleware, subscriptions, async actions/loaders, or global state runtime.
@@ -0,0 +1,40 @@
1
+ # Named Layers (M6a)
2
+
3
+ Named layers add semantic paint grouping on top of existing node `z`.
4
+
5
+ ## What layers do
6
+ - `layer` is optional node metadata.
7
+ - Layers affect paint order only.
8
+ - Geometry, placement, and coordinate spaces are unchanged.
9
+
10
+ ## Layer and z bounds
11
+ - Layer registry z values are conceptually bounded to integer `-5..5`.
12
+ - Node `z` remains integer `-5..5`.
13
+
14
+ ## Paint order
15
+ Siblings are painted by:
16
+ 1. layer z ascending
17
+ 2. node z ascending
18
+ 3. original sibling order ascending
19
+
20
+ React CSS representation uses `zIndex = layerZ * 100 + nodeZ`.
21
+
22
+ ## React adapter props
23
+ - `layers?: Record<string, { z: number }>`
24
+ - `defaultLayer?: string` (default: `"base"`)
25
+
26
+ If `layers` is omitted, behavior is equivalent to:
27
+
28
+ ```ts
29
+ { base: { z: 0 } }
30
+ ```
31
+
32
+ ## Fallback behavior
33
+ - Unknown node layer name: keeps declared name for metadata, layer z falls back to `0`.
34
+ - Invalid registry z (non-finite, non-integer, out of range): falls back to `0`.
35
+
36
+ ## Important M6a limitations
37
+ - Not portals.
38
+ - No DOM reparenting.
39
+ - No root overlay container.
40
+ - No clipping escape. Overflow clipping from ancestors can still apply.