@mmapp/react-compiler 0.1.0-alpha.1 → 0.1.0-alpha.3
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.
- package/ATOM-PIPELINE.md +144 -0
- package/README.md +88 -40
- package/dist/babel/index.js +113 -6
- package/dist/babel/index.mjs +2 -2
- package/dist/chunk-3USIFFE4.mjs +2190 -0
- package/dist/chunk-45YMGEVT.mjs +186 -0
- package/dist/chunk-4FN2AISW.mjs +148 -0
- package/dist/chunk-4OPI5L7G.mjs +2593 -0
- package/dist/chunk-4RYTKOOJ.mjs +186 -0
- package/dist/chunk-5RKTOVR5.mjs +244 -0
- package/dist/chunk-5YDMOO4X.mjs +214 -0
- package/dist/chunk-64ZWEMLJ.mjs +148 -0
- package/dist/chunk-6XP4KSWQ.mjs +2190 -0
- package/dist/chunk-72QWL54I.mjs +175 -0
- package/dist/chunk-7B4TRI7C.mjs +4835 -0
- package/dist/chunk-7ZKGHTNB.mjs +4952 -0
- package/dist/chunk-CIESM3BP.mjs +33 -0
- package/dist/chunk-DE3ZGQAC.mjs +148 -0
- package/dist/chunk-DMCY3BBG.mjs +1933 -0
- package/dist/chunk-DPIK3PJS.mjs +244 -0
- package/dist/chunk-E5IVH4RE.mjs +186 -0
- package/dist/chunk-E6FZNUR5.mjs +4953 -0
- package/dist/chunk-EJRBDQDP.mjs +2607 -0
- package/dist/chunk-ELO4TXJL.mjs +186 -0
- package/dist/chunk-FKRO52XH.mjs +3446 -0
- package/dist/chunk-FL4YAKU6.mjs +4941 -0
- package/dist/chunk-FYT47UBU.mjs +5076 -0
- package/dist/chunk-GCLGPOJZ.mjs +148 -0
- package/dist/chunk-GXB4JOP7.mjs +5072 -0
- package/dist/chunk-HFXOUMTD.mjs +175 -0
- package/dist/chunk-HWIZ47US.mjs +214 -0
- package/dist/chunk-IB7MNPQL.mjs +4953 -0
- package/dist/chunk-ICSIHQCG.mjs +148 -0
- package/dist/chunk-JLA5VNQ3.mjs +186 -0
- package/dist/chunk-JQLWFCTM.mjs +214 -0
- package/dist/chunk-KFJJCQAL.mjs +148 -0
- package/dist/chunk-KJUIIEQE.mjs +186 -0
- package/dist/chunk-KNWTHRVQ.mjs +175 -0
- package/dist/chunk-KSG4XSZF.mjs +175 -0
- package/dist/chunk-LF5N6DOU.mjs +175 -0
- package/dist/chunk-LJQCM2IM.mjs +214 -0
- package/dist/chunk-NW6555WJ.mjs +186 -0
- package/dist/chunk-OMZE6VLQ.mjs +214 -0
- package/dist/chunk-P4BR7WVO.mjs +2190 -0
- package/dist/chunk-QQHVYH2X.mjs +244 -0
- package/dist/chunk-S5QLWLLT.mjs +186 -0
- package/dist/chunk-SCWGT2FY.mjs +2190 -0
- package/dist/chunk-SMKJUSB3.mjs +2190 -0
- package/dist/chunk-VCAY2KGM.mjs +175 -0
- package/dist/chunk-WECAV6QB.mjs +148 -0
- package/dist/chunk-WMKBXUCE.mjs +3228 -0
- package/dist/chunk-XAJ5BKKL.mjs +4947 -0
- package/dist/chunk-XG2X7AEA.mjs +175 -0
- package/dist/chunk-XG7Z23NQ.mjs +148 -0
- package/dist/chunk-XWZAOCQ7.mjs +2607 -0
- package/dist/chunk-Y6MA7ULW.mjs +148 -0
- package/dist/chunk-YMS7Q7LG.mjs +214 -0
- package/dist/chunk-ZA37XTGA.mjs +175 -0
- package/dist/cli/index.js +1616 -366
- package/dist/cli/index.mjs +8 -8
- package/dist/codemod/cli.mjs +1 -1
- package/dist/codemod/index.mjs +1 -1
- package/dist/dev-server-RmGHIntF.d.mts +113 -0
- package/dist/dev-server-RmGHIntF.d.ts +113 -0
- package/dist/dev-server.d.mts +1 -1
- package/dist/dev-server.d.ts +1 -1
- package/dist/dev-server.js +982 -53
- package/dist/dev-server.mjs +5 -5
- package/dist/envelope.js +113 -6
- package/dist/envelope.mjs +3 -3
- package/dist/index.d.mts +5 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +992 -63
- package/dist/index.mjs +8 -8
- package/{src/cli/init.ts → dist/init-7JQMAAXS.mjs} +70 -95
- package/dist/init-EHO4VQ22.mjs +369 -0
- package/dist/init-UC3FWPIW.mjs +367 -0
- package/dist/init-UNSMVKIK.mjs +366 -0
- package/dist/init-UNV5XIDE.mjs +367 -0
- package/dist/project-compiler-2P4N4DR7.mjs +10 -0
- package/dist/project-compiler-D2LCC27O.mjs +10 -0
- package/dist/project-compiler-EJ3GANJE.mjs +10 -0
- package/dist/project-compiler-LOQKVRZJ.mjs +10 -0
- package/dist/project-compiler-RQ6OQKRM.mjs +10 -0
- package/dist/project-compiler-VWNNCHGO.mjs +10 -0
- package/dist/project-compiler-XVAAU4C5.mjs +10 -0
- package/dist/project-compiler-YES5FGMD.mjs +10 -0
- package/dist/project-compiler-ZKMQDLGU.mjs +10 -0
- package/dist/project-decompiler-FLXCEJHS.mjs +7 -0
- package/dist/project-decompiler-VLPR22QF.mjs +7 -0
- package/dist/pull-FUS5QYZS.mjs +109 -0
- package/dist/pull-LD5ENLGY.mjs +109 -0
- package/dist/testing/index.js +113 -6
- package/dist/testing/index.mjs +2 -2
- package/dist/vite/index.js +113 -6
- package/dist/vite/index.mjs +3 -3
- package/examples/uber-app/app/admin/fleet.tsx +19 -19
- package/package.json +4 -3
- package/compile-blueprint-chat.mjs +0 -99
- package/compile-blueprint-glass-console.mjs +0 -98
- package/compile-chat-defs.mjs +0 -92
- package/examples/uber-app/tests/payment.test.tsx +0 -129
- package/examples/uber-app/tests/ride-flow.test.tsx +0 -123
- package/package.json.backup +0 -86
- package/scripts/decompile.ts +0 -226
- package/scripts/seed-auth.ts +0 -267
- package/scripts/seed-uber.ts +0 -248
- package/scripts/validate-uber.ts +0 -119
- package/seed-blueprint-chat.mjs +0 -444
- package/seed-blueprint-glass-console.mjs +0 -445
- package/seed-compiled.mjs +0 -318
- package/src/RoundTripValidator.ts +0 -400
- package/src/__tests__/atom-rendering-coverage.test.ts +0 -680
- package/src/__tests__/auth-module-compilation.test.ts +0 -247
- package/src/__tests__/auth-template-compilation.test.ts +0 -589
- package/src/__tests__/change-extractor.test.ts +0 -142
- package/src/__tests__/cli-pull.test.ts +0 -73
- package/src/__tests__/cli-test.test.ts +0 -72
- package/src/__tests__/component-extractor.test.ts +0 -331
- package/src/__tests__/context-extractor.test.ts +0 -145
- package/src/__tests__/decompiler.test.ts +0 -718
- package/src/__tests__/define-blueprint.test.ts +0 -133
- package/src/__tests__/definition-validator.test.ts +0 -519
- package/src/__tests__/during-extractor.test.ts +0 -152
- package/src/__tests__/effect-extractor.test.ts +0 -107
- package/src/__tests__/event-emission.test.ts +0 -127
- package/src/__tests__/examples.test.ts +0 -236
- package/src/__tests__/full-blueprint-coverage.test.ts +0 -1221
- package/src/__tests__/golden-suite.test.ts +0 -403
- package/src/__tests__/grammar-island-extractor.test.ts +0 -289
- package/src/__tests__/instance-key.test.ts +0 -82
- package/src/__tests__/ir-migration.test.ts +0 -255
- package/src/__tests__/lock-file.test.ts +0 -117
- package/src/__tests__/model-extractor.test.ts +0 -195
- package/src/__tests__/model-field-acl.test.ts +0 -237
- package/src/__tests__/model-hooks.test.ts +0 -130
- package/src/__tests__/model-ref-resolution.test.ts +0 -268
- package/src/__tests__/model-roundtrip.test.ts +0 -502
- package/src/__tests__/model-runtime.test.ts +0 -112
- package/src/__tests__/model-transitions.test.ts +0 -183
- package/src/__tests__/nrt-action-trace.test.ts +0 -391
- package/src/__tests__/pipeline-hardening.test.ts +0 -413
- package/src/__tests__/project-compiler.test.ts +0 -546
- package/src/__tests__/project-decompiler.test.ts +0 -343
- package/src/__tests__/query-compilation.test.ts +0 -145
- package/src/__tests__/round-trip/PLAN.md +0 -158
- package/src/__tests__/round-trip/README.md +0 -52
- package/src/__tests__/round-trip/RESULTS.md +0 -86
- package/src/__tests__/round-trip/fixtures/data-heavy/main.workflow.tsx +0 -55
- package/src/__tests__/round-trip/fixtures/data-heavy/mm.config.ts +0 -11
- package/src/__tests__/round-trip/fixtures/data-heavy/models/contact.ts +0 -54
- package/src/__tests__/round-trip/fixtures/full-workflow/main.workflow.tsx +0 -79
- package/src/__tests__/round-trip/fixtures/full-workflow/mm.config.ts +0 -12
- package/src/__tests__/round-trip/fixtures/full-workflow/models/order.ts +0 -50
- package/src/__tests__/round-trip/fixtures/simple-crud/main.workflow.tsx +0 -25
- package/src/__tests__/round-trip/fixtures/simple-crud/mm.config.ts +0 -11
- package/src/__tests__/round-trip/fixtures/simple-crud/models/task.ts +0 -32
- package/src/__tests__/round-trip/fixtures/view-heavy/main.workflow.tsx +0 -79
- package/src/__tests__/round-trip/fixtures/view-heavy/mm.config.ts +0 -10
- package/src/__tests__/round-trip/round-trip.test.ts +0 -2598
- package/src/__tests__/round-trip-ir.test.ts +0 -300
- package/src/__tests__/round-trip.test.ts +0 -1212
- package/src/__tests__/route-merging.test.ts +0 -372
- package/src/__tests__/router-composition.test.ts +0 -489
- package/src/__tests__/router-extractor.test.ts +0 -176
- package/src/__tests__/server-action-extractor.test.ts +0 -128
- package/src/__tests__/smart-type-inference.test.ts +0 -365
- package/src/__tests__/source-envelope.test.ts +0 -284
- package/src/__tests__/source-fidelity.test.ts +0 -516
- package/src/__tests__/state-extractor.test.ts +0 -115
- package/src/__tests__/strict-mode.test.ts +0 -227
- package/src/__tests__/transition-effect-extractor.test.ts +0 -119
- package/src/__tests__/transition-extractor.test.ts +0 -68
- package/src/__tests__/ts-to-expression.test.ts +0 -462
- package/src/__tests__/type-generator.test.ts +0 -201
- package/src/__tests__/uber-validation.test.ts +0 -502
- package/src/action-compiler.ts +0 -361
- package/src/babel/emitters/experience-transform.ts +0 -199
- package/src/babel/emitters/ir-to-tsx-emitter.ts +0 -110
- package/src/babel/emitters/pure-form-emitter.ts +0 -1023
- package/src/babel/emitters/runtime-glue-emitter.ts +0 -39
- package/src/babel/extractors/change-extractor.ts +0 -199
- package/src/babel/extractors/component-extractor.ts +0 -907
- package/src/babel/extractors/computed-extractor.ts +0 -262
- package/src/babel/extractors/context-extractor.ts +0 -277
- package/src/babel/extractors/during-extractor.ts +0 -295
- package/src/babel/extractors/effect-extractor.ts +0 -340
- package/src/babel/extractors/event-extractor.ts +0 -235
- package/src/babel/extractors/grammar-island-extractor.ts +0 -302
- package/src/babel/extractors/model-extractor.ts +0 -1018
- package/src/babel/extractors/router-extractor.ts +0 -303
- package/src/babel/extractors/server-action-extractor.ts +0 -173
- package/src/babel/extractors/server-action-hook-extractor.ts +0 -72
- package/src/babel/extractors/server-state-extractor.ts +0 -88
- package/src/babel/extractors/state-extractor.ts +0 -214
- package/src/babel/extractors/transition-effect-extractor.ts +0 -176
- package/src/babel/extractors/transition-extractor.ts +0 -143
- package/src/babel/index.ts +0 -24
- package/src/babel/transpilers/ts-to-expression.ts +0 -674
- package/src/babel/visitor.ts +0 -807
- package/src/cli/auth.ts +0 -255
- package/src/cli/build.ts +0 -288
- package/src/cli/deploy.ts +0 -206
- package/src/cli/index.ts +0 -328
- package/src/cli/installer.ts +0 -261
- package/src/cli/lock-file.ts +0 -94
- package/src/cli/mmrc.ts +0 -22
- package/src/cli/pull.ts +0 -172
- package/src/cli/registry-client.ts +0 -175
- package/src/cli/test.ts +0 -397
- package/src/cli/type-generator.ts +0 -243
- package/src/codemod/__tests__/forward.test.ts +0 -239
- package/src/codemod/__tests__/reverse.test.ts +0 -145
- package/src/codemod/__tests__/round-trip.test.ts +0 -137
- package/src/codemod/annotation.ts +0 -97
- package/src/codemod/classify.ts +0 -197
- package/src/codemod/cli.ts +0 -207
- package/src/codemod/control-flow.ts +0 -409
- package/src/codemod/forward.ts +0 -244
- package/src/codemod/import-manager.ts +0 -171
- package/src/codemod/index.ts +0 -120
- package/src/codemod/reverse.ts +0 -197
- package/src/codemod/rules.ts +0 -174
- package/src/codemod/state-transform.ts +0 -126
- package/src/decompiler/ast-builder.ts +0 -538
- package/src/decompiler/config-generator.ts +0 -151
- package/src/decompiler/index.ts +0 -315
- package/src/decompiler/project-decompiler.ts +0 -1776
- package/src/decompiler/project.ts +0 -862
- package/src/decompiler/split-strategy.ts +0 -140
- package/src/decompiler/state-emitter.ts +0 -1053
- package/src/decompiler/sx-emitter.ts +0 -318
- package/src/decompiler/workspace-hydrator.ts +0 -189
- package/src/dev-server.ts +0 -238
- package/src/envelope/fs-tree.ts +0 -217
- package/src/envelope/source-envelope.ts +0 -264
- package/src/envelope.ts +0 -315
- package/src/incremental-compiler.ts +0 -401
- package/src/index.ts +0 -99
- package/src/model-compiler.ts +0 -277
- package/src/project-compiler.ts +0 -1629
- package/src/route-extractor.ts +0 -333
- package/src/testing/index.ts +0 -32
- package/src/testing/snapshot.ts +0 -252
- package/src/testing/test-utils.ts +0 -226
- package/src/types.ts +0 -68
- package/src/vite/index.ts +0 -288
- package/test-compile.mjs +0 -142
- package/tsconfig.json +0 -25
- package/tsup.config.ts +0 -23
- package/vitest.config.ts +0 -9
package/ATOM-PIPELINE.md
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# Atom Pipeline — How Components Flow From Authoring to Runtime
|
|
2
|
+
|
|
3
|
+
## The Two Atom Packages Are NOT Duplicates
|
|
4
|
+
|
|
5
|
+
There are two places where atom components live. They serve different purposes and are **not** supposed to be identical.
|
|
6
|
+
|
|
7
|
+
### `packages/react/src/components/atoms/` — Authoring Atoms (14 components)
|
|
8
|
+
|
|
9
|
+
These are **specifications**, not implementations. They exist so that `.workflow.tsx` authors get TypeScript completion when writing views:
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
import { Stack, Text, Button, TextInput } from '@mindmatrix/react';
|
|
13
|
+
|
|
14
|
+
export default function MyPage() {
|
|
15
|
+
return (
|
|
16
|
+
<Stack gap={16}>
|
|
17
|
+
<Text variant="h3" value="Hello" />
|
|
18
|
+
<TextInput placeholder="Type here..." onChange={setName} />
|
|
19
|
+
<Button variant="primary" onPress={submit}>Save</Button>
|
|
20
|
+
</Stack>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The Babel compiler (`react-compiler`) extracts this JSX into IR nodes. These components **never render in production**. They only need enough implementation to satisfy TypeScript and render a basic preview in tests.
|
|
26
|
+
|
|
27
|
+
**Full list:** Stack, Row, Column, Text, Heading, Button, TextInput, Each, Show, Slot, Badge, Card, Divider
|
|
28
|
+
|
|
29
|
+
### `packages/frontend/src/experiences/atoms/` — Runtime Atoms (50+ components)
|
|
30
|
+
|
|
31
|
+
These are **real React components** that render in the browser. They implement the full visual and interactive behavior: Radix UI primitives, Tailwind styling, event handling, accessibility, config prop resolution, Style IR support.
|
|
32
|
+
|
|
33
|
+
The `ComponentTreeRenderer` maps IR node `component` strings to these atoms via the component registry (`component-registry.ts`). When the experience tree has `{ component: 'TextInput', config: { placeholder: '...' } }`, the registry resolves it to the `TextInput` atom from this directory.
|
|
34
|
+
|
|
35
|
+
**These include everything the authoring atoms have, plus:** Select, Icon, Image, Modal, Tabs, Accordion, Router, Route, NavLink, RoleGuard, AnimatedBox, Canvas3D, DataGrid, Chart, MetricCard, ServerGrid, all media atoms (Video, Audio, Map), interactive atoms (Slider, Toggle, Rating, Counter), data display (Tag, Timeline, Pricing), layout (Breadcrumb, Stepper, Drawer), and 17 sub-experience components.
|
|
36
|
+
|
|
37
|
+
## Why They Differ
|
|
38
|
+
|
|
39
|
+
The authoring atoms are intentionally minimal because:
|
|
40
|
+
|
|
41
|
+
1. **Different players implement different subsets.** A web player has 90+ renderers. An iOS player might implement 30. A console/CLI player might implement 10. The authoring atoms define the universal vocabulary — what authors can write. Each player decides what it can render, falling back gracefully for unknown components.
|
|
42
|
+
|
|
43
|
+
2. **The IR is the contract, not the React components.** The compiled IR node `{ component: 'TextInput', config: { placeholder, maxLength, multiline } }` is the spec. Any player that can render a `TextInput` with those config keys is compliant. The React authoring atoms are just one convenient way to produce that IR.
|
|
44
|
+
|
|
45
|
+
3. **Player-specific atoms shouldn't pollute the authoring SDK.** The web player has `Canvas3D`, `DataGrid`, `GlassConsoleRunner`. These are web-specific. They don't belong in the authoring package that iOS developers also import.
|
|
46
|
+
|
|
47
|
+
## The Full Pipeline
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
Author writes .workflow.tsx
|
|
51
|
+
│
|
|
52
|
+
▼
|
|
53
|
+
@mindmatrix/react atoms (Stack, Text, Button, ...)
|
|
54
|
+
│ TypeScript types + basic rendering for preview
|
|
55
|
+
│
|
|
56
|
+
▼
|
|
57
|
+
react-compiler Babel plugin
|
|
58
|
+
│ component-extractor.ts extracts JSX → IRExperienceNode
|
|
59
|
+
│ Each JSX element becomes { component, config, bindings, children }
|
|
60
|
+
│
|
|
61
|
+
▼
|
|
62
|
+
IRExperienceNode tree (stored in database)
|
|
63
|
+
│ component: "TextInput"
|
|
64
|
+
│ config: { placeholder: "Search...", maxLength: 100 }
|
|
65
|
+
│ bindings: { value: "$local.query", onChange: "$action.setLocal('query', $value)" }
|
|
66
|
+
│
|
|
67
|
+
▼
|
|
68
|
+
ComponentTreeRenderer (packages/frontend/src/experiences/)
|
|
69
|
+
│ Walks the IR tree, resolves each node.component via component-registry
|
|
70
|
+
│ Applies binding-resolver for dynamic values
|
|
71
|
+
│ Passes config as props, style from Style IR
|
|
72
|
+
│
|
|
73
|
+
▼
|
|
74
|
+
Frontend atoms render in the browser
|
|
75
|
+
TextInput, SelectAtom, ButtonAtom, etc.
|
|
76
|
+
Full Radix UI + Tailwind + accessibility
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## The Decompiler (Reverse Direction)
|
|
80
|
+
|
|
81
|
+
The decompiler converts IR back to `.workflow.tsx` source:
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
IRExperienceNode tree
|
|
85
|
+
│
|
|
86
|
+
▼
|
|
87
|
+
ast-builder.ts builds Babel JSX AST
|
|
88
|
+
│ Uses REACT_ATOMS set (32 components) to decide imports
|
|
89
|
+
│ Maps IR tokens ($local.x, $action.y) back to variable names
|
|
90
|
+
│
|
|
91
|
+
▼
|
|
92
|
+
@babel/generator produces source code
|
|
93
|
+
│
|
|
94
|
+
▼
|
|
95
|
+
.workflow.tsx with imports from @mindmatrix/react
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The `REACT_ATOMS` set in `ast-builder.ts` determines which components get `import { X } from '@mindmatrix/react'` in the decompiled output. Components not in this set are emitted as bare identifiers (assumed to be local imports or custom components).
|
|
99
|
+
|
|
100
|
+
## Style IR and the `style` Prop
|
|
101
|
+
|
|
102
|
+
The compiled experience tree may attach Style IR to nodes as inline `style` objects. These flow through to the atom's wrapper div. This means **atoms receive border, borderRadius, background, etc. from the compiled view** — not just from their own CSS.
|
|
103
|
+
|
|
104
|
+
If an atom should always be borderless (like TextInput and Select inside Cards), it must explicitly override the style prop:
|
|
105
|
+
|
|
106
|
+
```tsx
|
|
107
|
+
<div style={{ ...style, border: 'none', borderRadius: 0, background: 'transparent' }}>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
This is by design — Style IR gives authors control over visual appearance, but atoms can override properties that conflict with their intended rendering.
|
|
111
|
+
|
|
112
|
+
## Coverage Validation
|
|
113
|
+
|
|
114
|
+
`atom-rendering-coverage.test.ts` validates the full pipeline:
|
|
115
|
+
|
|
116
|
+
1. Compiles all blueprint packages and examples to IR
|
|
117
|
+
2. Collects every unique `component` string from IR trees
|
|
118
|
+
3. Checks each against `KNOWN_RENDERERS` (90+ registered) and `STRUCTURAL_COMPONENTS` (HTML elements)
|
|
119
|
+
4. Fails if any IR component has no renderer
|
|
120
|
+
|
|
121
|
+
This catches: new authoring atoms missing a frontend renderer, typos in component names, and regression when atoms are renamed.
|
|
122
|
+
|
|
123
|
+
## Key Files
|
|
124
|
+
|
|
125
|
+
| File | Purpose |
|
|
126
|
+
|------|---------|
|
|
127
|
+
| `packages/react/src/components/atoms/index.tsx` | Authoring atoms (14) — the author's vocabulary |
|
|
128
|
+
| `packages/frontend/src/experiences/atoms/` | Runtime atoms (50+) — what actually renders |
|
|
129
|
+
| `packages/frontend/src/experiences/ComponentTreeRenderer.tsx` | Walks IR tree, resolves components, applies bindings |
|
|
130
|
+
| `packages/frontend/src/experiences/component-registry.ts` | Maps component name strings → lazy-loaded React components |
|
|
131
|
+
| `packages/frontend/src/experiences/binding-resolver.ts` | Resolves $local, $instance, $action, $fn expressions |
|
|
132
|
+
| `packages/react-compiler/src/babel/extractors/component-extractor.ts` | Extracts JSX → IR nodes during compilation |
|
|
133
|
+
| `packages/react-compiler/src/decompiler/ast-builder.ts` | Builds JSX AST from IR nodes (REACT_ATOMS set) |
|
|
134
|
+
| `packages/react-compiler/src/__tests__/atom-rendering-coverage.test.ts` | Validates no gaps in the pipeline |
|
|
135
|
+
|
|
136
|
+
## Rules of Thumb
|
|
137
|
+
|
|
138
|
+
- **Adding a new authoring atom?** Add it to `packages/react/src/components/atoms/`, export from the package, and add a matching renderer in `packages/frontend/src/experiences/atoms/`. Register it in `component-registry.ts`. The coverage test will catch if you forget the renderer.
|
|
139
|
+
|
|
140
|
+
- **Adding a web-only atom?** Add it only to `packages/frontend/src/experiences/atoms/` and register in `component-registry.ts`. Don't add it to the authoring package — it's platform-specific.
|
|
141
|
+
|
|
142
|
+
- **Adding a new player (iOS, CLI, etc.)?** Implement renderers for whichever atoms make sense for that platform. The IR is the contract. Unknown components should render a fallback, not crash.
|
|
143
|
+
|
|
144
|
+
- **Atom props drifting?** The coverage test catches missing atoms but not missing props. If you add a new config key to an authoring atom, make sure the corresponding frontend atom handles it. TypeScript won't catch this — it crosses package boundaries through IR serialization.
|
package/README.md
CHANGED
|
@@ -1,21 +1,47 @@
|
|
|
1
|
-
# @
|
|
1
|
+
# @mmapp/react-compiler
|
|
2
2
|
|
|
3
|
-
Babel plugin
|
|
3
|
+
Babel plugin, Vite integration, and CLI for compiling React workflow files into Pure Form IR (Intermediate Representation).
|
|
4
|
+
|
|
5
|
+
Published on npm as `@mmapp/react-compiler` (internal workspace name: `@mindmatrix/react-compiler`).
|
|
4
6
|
|
|
5
7
|
## Installation
|
|
6
8
|
|
|
7
9
|
```bash
|
|
8
|
-
|
|
10
|
+
npm install -g @mmapp/react-compiler # for mmrc CLI
|
|
11
|
+
npm install -D @mmapp/react-compiler # for Vite/Babel plugin
|
|
9
12
|
```
|
|
10
13
|
|
|
11
|
-
##
|
|
14
|
+
## CLI — `mmrc`
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# Scaffold a new blueprint
|
|
18
|
+
mmrc init my-app
|
|
19
|
+
|
|
20
|
+
# Build workflows to IR
|
|
21
|
+
mmrc build --src src/workflows --out dist/workflows
|
|
22
|
+
|
|
23
|
+
# Deploy to MindMatrix server
|
|
24
|
+
mmrc deploy --api-url https://dev.mindmatrix.club --token YOUR_TOKEN
|
|
25
|
+
|
|
26
|
+
# Start dev server with hot reload
|
|
27
|
+
mmrc dev
|
|
28
|
+
|
|
29
|
+
# Run workflow tests
|
|
30
|
+
mmrc test
|
|
31
|
+
|
|
32
|
+
# Pull remote definitions to local files
|
|
33
|
+
mmrc pull --slug my-workflow
|
|
12
34
|
|
|
13
|
-
|
|
35
|
+
# Auth
|
|
36
|
+
mmrc login / mmrc logout / mmrc whoami
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Vite Plugin
|
|
14
40
|
|
|
15
41
|
```typescript
|
|
16
42
|
// vite.config.ts
|
|
17
43
|
import { defineConfig } from 'vite';
|
|
18
|
-
import { mindmatrixReact } from '@
|
|
44
|
+
import { mindmatrixReact } from '@mmapp/react-compiler/vite';
|
|
19
45
|
|
|
20
46
|
export default defineConfig({
|
|
21
47
|
plugins: [
|
|
@@ -27,41 +53,36 @@ export default defineConfig({
|
|
|
27
53
|
});
|
|
28
54
|
```
|
|
29
55
|
|
|
30
|
-
|
|
56
|
+
## Babel Plugin
|
|
31
57
|
|
|
32
58
|
```javascript
|
|
33
59
|
// babel.config.js
|
|
34
60
|
module.exports = {
|
|
35
61
|
plugins: [
|
|
36
|
-
['@
|
|
62
|
+
['@mmapp/react-compiler/babel', { mode: 'infer' }]
|
|
37
63
|
]
|
|
38
64
|
};
|
|
39
65
|
```
|
|
40
66
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
- Extract `useTransition` → IR transition definitions
|
|
56
|
-
- Extract `useOnEvent` → IR event subscriptions
|
|
57
|
-
- Extract JSX components → IR experience nodes
|
|
58
|
-
- Emit Pure Form JSON matching `IRWorkflowDefinition` schema
|
|
67
|
+
## What It Compiles
|
|
68
|
+
|
|
69
|
+
| Source Pattern | IR Output |
|
|
70
|
+
|---------------|-----------|
|
|
71
|
+
| `useState(initial)` | Field definition |
|
|
72
|
+
| `useOnEnter(state, fn)` | State on_enter actions |
|
|
73
|
+
| `useOnExit(state, fn)` | State on_exit actions |
|
|
74
|
+
| `useTransition(name)` | Transition definition |
|
|
75
|
+
| `useOnEvent(pattern, fn)` | Event subscription |
|
|
76
|
+
| JSX components | Experience tree nodes |
|
|
77
|
+
| `defineModel({...})` | Model IR (fields, states, transitions) |
|
|
78
|
+
| `defineBlueprint({...})` | Blueprint manifest |
|
|
79
|
+
| `useServerAction(name)` | Server action binding |
|
|
80
|
+
| Router/Route composition | Route definitions |
|
|
59
81
|
|
|
60
82
|
## Example
|
|
61
83
|
|
|
62
84
|
```tsx
|
|
63
|
-
|
|
64
|
-
import { useState, useOnEnter, useTransition } from '@mindmatrix/react';
|
|
85
|
+
import { useState, useOnEnter, useTransition } from '@mmapp/react';
|
|
65
86
|
|
|
66
87
|
export function ApprovalWorkflow() {
|
|
67
88
|
const [status, setStatus] = useState('pending');
|
|
@@ -83,25 +104,52 @@ Compiles to:
|
|
|
83
104
|
{
|
|
84
105
|
"slug": "approval",
|
|
85
106
|
"name": "ApprovalWorkflow",
|
|
86
|
-
"version": "0.1.0",
|
|
87
|
-
"category": "workflow",
|
|
88
107
|
"fields": [
|
|
89
108
|
{ "name": "status", "type": "text", "default_value": "pending" },
|
|
90
109
|
{ "name": "approver", "type": "text", "default_value": "" }
|
|
91
110
|
],
|
|
92
111
|
"states": [
|
|
93
|
-
{
|
|
94
|
-
"
|
|
95
|
-
"type": "REGULAR",
|
|
96
|
-
"on_enter": [
|
|
97
|
-
{ "id": "auto_1", "type": "set_field", "mode": "auto", "config": { "field": "status", "value": "done" } }
|
|
98
|
-
],
|
|
99
|
-
"during": [],
|
|
100
|
-
"on_exit": []
|
|
101
|
-
}
|
|
112
|
+
{ "name": "approved", "type": "REGULAR",
|
|
113
|
+
"on_enter": [{ "type": "set_field", "config": { "field": "status", "value": "done" } }] }
|
|
102
114
|
],
|
|
103
115
|
"transitions": [
|
|
104
|
-
{ "name": "approve", "from": [], "to": "approved"
|
|
116
|
+
{ "name": "approve", "from": [], "to": "approved" }
|
|
105
117
|
]
|
|
106
118
|
}
|
|
107
119
|
```
|
|
120
|
+
|
|
121
|
+
## npm Publishing
|
|
122
|
+
|
|
123
|
+
This package is published to npm under the `@mmapp` scope.
|
|
124
|
+
|
|
125
|
+
| npm name | workspace name | current version |
|
|
126
|
+
|----------|---------------|-----------------|
|
|
127
|
+
| `@mmapp/react-compiler` | `@mindmatrix/react-compiler` | 0.1.0-alpha.1 |
|
|
128
|
+
|
|
129
|
+
### Publishing a new version
|
|
130
|
+
|
|
131
|
+
Use the publish script from the monorepo root:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
# Publish all 3 packages (player-core, react, react-compiler)
|
|
135
|
+
./scripts/publish-npm.sh patch # 0.1.0-alpha.1 → 0.1.0-alpha.2
|
|
136
|
+
./scripts/publish-npm.sh minor # 0.1.0-alpha.1 → 0.2.0-alpha.1
|
|
137
|
+
./scripts/publish-npm.sh 0.2.0-beta.1 # explicit version
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Or publish just this package:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
cd packages/react-compiler
|
|
144
|
+
pnpm run publish:alpha # auto-increments alpha version
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Version strategy
|
|
148
|
+
|
|
149
|
+
- `0.1.0-alpha.X` — internal development (current)
|
|
150
|
+
- `0.1.0-beta.X` — user testing
|
|
151
|
+
- `1.0.0` — production launch
|
|
152
|
+
|
|
153
|
+
### Important: workspace dependency handling
|
|
154
|
+
|
|
155
|
+
When publishing to npm, `workspace:*` dependencies are automatically rewritten to `@mmapp/*` equivalents by the publish script. The internal `@mindmatrix/player-core` becomes `@mmapp/player-core` in the published tarball.
|
package/dist/babel/index.js
CHANGED
|
@@ -649,6 +649,8 @@ var TranspileContext = class {
|
|
|
649
649
|
this.localFieldMap = options.localFieldMap ?? /* @__PURE__ */ new Map();
|
|
650
650
|
this.derivedVarMap = options.derivedVarMap ?? /* @__PURE__ */ new Map();
|
|
651
651
|
this.setterToFieldMap = options.setterToFieldMap ?? /* @__PURE__ */ new Map();
|
|
652
|
+
this.parameterMap = options.parameterMap ?? /* @__PURE__ */ new Map();
|
|
653
|
+
this.allMutable = options.allMutable ?? false;
|
|
652
654
|
}
|
|
653
655
|
/**
|
|
654
656
|
* Emit an opaque JS fallback, marking the result as impure.
|
|
@@ -670,6 +672,8 @@ var TranspileContext = class {
|
|
|
670
672
|
if (snakeName) return `$local.${snakeName}`;
|
|
671
673
|
const derivedInit = this.derivedVarMap.get(node.name);
|
|
672
674
|
if (derivedInit) return `(${this.visit(derivedInit)})`;
|
|
675
|
+
const paramField = this.parameterMap.get(node.name);
|
|
676
|
+
if (paramField) return paramField;
|
|
673
677
|
if (node.name === "undefined") return "null";
|
|
674
678
|
if (node.name === "NaN") return "null";
|
|
675
679
|
if (node.name === "Infinity") return "null";
|
|
@@ -717,6 +721,9 @@ var TranspileContext = class {
|
|
|
717
721
|
if (t5.isNewExpression(node)) {
|
|
718
722
|
return this.visitNew(node);
|
|
719
723
|
}
|
|
724
|
+
if (t5.isAssignmentExpression(node) && node.operator === "=" && t5.isIdentifier(node.left)) {
|
|
725
|
+
return `${node.left.name} = ${this.visit(node.right)}`;
|
|
726
|
+
}
|
|
720
727
|
return this.opaque(fallbackGenerate(node));
|
|
721
728
|
}
|
|
722
729
|
// ---------------------------------------------------------------------------
|
|
@@ -917,6 +924,11 @@ var TranspileContext = class {
|
|
|
917
924
|
return this.visitArrayHigherOrder("some", obj, args);
|
|
918
925
|
case "every":
|
|
919
926
|
return this.visitArrayHigherOrder("every", obj, args);
|
|
927
|
+
// --- Array push: arr.push(item) → arr = push(arr, item) ---
|
|
928
|
+
case "push": {
|
|
929
|
+
const [item] = this.visitArgsList(args);
|
|
930
|
+
return `${obj} = push(${obj}, ${item})`;
|
|
931
|
+
}
|
|
920
932
|
// --- Array mutation-free methods ---
|
|
921
933
|
case "reverse":
|
|
922
934
|
return `reverse(${obj})`;
|
|
@@ -2752,6 +2764,71 @@ function modelStateTypeToIR(type) {
|
|
|
2752
2764
|
if (type === "final") return "END";
|
|
2753
2765
|
return "REGULAR";
|
|
2754
2766
|
}
|
|
2767
|
+
function parseActionArray(raw, actionCounter) {
|
|
2768
|
+
if (!Array.isArray(raw)) return [];
|
|
2769
|
+
const actions = [];
|
|
2770
|
+
for (const item of raw) {
|
|
2771
|
+
if (typeof item !== "object" || item === null) continue;
|
|
2772
|
+
const a = item;
|
|
2773
|
+
actions.push({
|
|
2774
|
+
id: a.id ? String(a.id) : `auto_${++actionCounter.value}`,
|
|
2775
|
+
type: String(a.type || a.action_type || "unknown"),
|
|
2776
|
+
mode: a.mode || "auto",
|
|
2777
|
+
config: a.config && typeof a.config === "object" ? a.config : {},
|
|
2778
|
+
...a.condition ? { condition: String(a.condition) } : {}
|
|
2779
|
+
});
|
|
2780
|
+
}
|
|
2781
|
+
return actions;
|
|
2782
|
+
}
|
|
2783
|
+
function parseDuringArray(raw, actionCounter) {
|
|
2784
|
+
if (!Array.isArray(raw)) return [];
|
|
2785
|
+
const result = [];
|
|
2786
|
+
for (const item of raw) {
|
|
2787
|
+
if (typeof item !== "object" || item === null) continue;
|
|
2788
|
+
const d = item;
|
|
2789
|
+
result.push({
|
|
2790
|
+
id: d.id ? String(d.id) : `during_${++actionCounter.value}`,
|
|
2791
|
+
type: d.type || "interval",
|
|
2792
|
+
...d.interval_ms != null ? { interval_ms: Number(d.interval_ms) } : {},
|
|
2793
|
+
...d.cron ? { cron: String(d.cron) } : {},
|
|
2794
|
+
...d.delay_ms != null ? { delay_ms: Number(d.delay_ms) } : {},
|
|
2795
|
+
...d.condition ? { condition: String(d.condition) } : {},
|
|
2796
|
+
actions: parseActionArray(d.actions, actionCounter)
|
|
2797
|
+
});
|
|
2798
|
+
}
|
|
2799
|
+
return result;
|
|
2800
|
+
}
|
|
2801
|
+
function parseOnEventArray(raw, actionCounter) {
|
|
2802
|
+
if (!Array.isArray(raw)) return [];
|
|
2803
|
+
const result = [];
|
|
2804
|
+
for (const item of raw) {
|
|
2805
|
+
if (typeof item !== "object" || item === null) continue;
|
|
2806
|
+
const e = item;
|
|
2807
|
+
const actions = [];
|
|
2808
|
+
if (Array.isArray(e.actions)) {
|
|
2809
|
+
for (const a of e.actions) {
|
|
2810
|
+
if (typeof a !== "object" || a === null) continue;
|
|
2811
|
+
const act = a;
|
|
2812
|
+
actions.push({
|
|
2813
|
+
type: act.type || "set_field",
|
|
2814
|
+
...act.field ? { field: String(act.field) } : {},
|
|
2815
|
+
...act.expression ? { expression: String(act.expression) } : {},
|
|
2816
|
+
...act.key ? { key: String(act.key) } : {},
|
|
2817
|
+
...act.message ? { message: String(act.message) } : {},
|
|
2818
|
+
...act.config && typeof act.config === "object" ? { config: act.config } : {},
|
|
2819
|
+
...act.conditions && Array.isArray(act.conditions) ? { conditions: act.conditions.map(String) } : {}
|
|
2820
|
+
});
|
|
2821
|
+
}
|
|
2822
|
+
}
|
|
2823
|
+
result.push({
|
|
2824
|
+
match: String(e.match || ""),
|
|
2825
|
+
...e.description ? { description: String(e.description) } : {},
|
|
2826
|
+
...e.conditions && Array.isArray(e.conditions) ? { conditions: e.conditions.map(String) } : {},
|
|
2827
|
+
actions
|
|
2828
|
+
});
|
|
2829
|
+
}
|
|
2830
|
+
return result;
|
|
2831
|
+
}
|
|
2755
2832
|
function extractDefineModelCall(programPath, compilerState, actionCounter) {
|
|
2756
2833
|
for (const node of programPath.node.body) {
|
|
2757
2834
|
if (!t11.isExportDefaultDeclaration(node)) continue;
|
|
@@ -2791,17 +2868,27 @@ function extractDefineModelCall(programPath, compilerState, actionCounter) {
|
|
|
2791
2868
|
const statesObj = config.states;
|
|
2792
2869
|
for (const [name, stateConfig] of Object.entries(statesObj)) {
|
|
2793
2870
|
const stateType = modelStateTypeToIR(stateConfig.type);
|
|
2871
|
+
const on_enter = parseActionArray(stateConfig.on_enter, actionCounter);
|
|
2872
|
+
const on_exit = parseActionArray(stateConfig.on_exit, actionCounter);
|
|
2873
|
+
const during = parseDuringArray(stateConfig.during, actionCounter);
|
|
2874
|
+
const on_event = parseOnEventArray(stateConfig.on_event, actionCounter);
|
|
2794
2875
|
if (!compilerState.states.has(name)) {
|
|
2795
2876
|
compilerState.states.set(name, {
|
|
2796
2877
|
name,
|
|
2797
2878
|
type: stateType,
|
|
2798
2879
|
description: stateConfig.description,
|
|
2799
|
-
on_enter
|
|
2800
|
-
during
|
|
2801
|
-
on_exit
|
|
2880
|
+
on_enter,
|
|
2881
|
+
during,
|
|
2882
|
+
on_exit,
|
|
2883
|
+
...on_event.length > 0 ? { on_event } : {}
|
|
2802
2884
|
});
|
|
2803
2885
|
} else {
|
|
2804
|
-
compilerState.states.get(name)
|
|
2886
|
+
const existing = compilerState.states.get(name);
|
|
2887
|
+
existing.type = stateType;
|
|
2888
|
+
if (on_enter.length > 0) existing.on_enter = on_enter;
|
|
2889
|
+
if (on_exit.length > 0) existing.on_exit = on_exit;
|
|
2890
|
+
if (during.length > 0) existing.during = during;
|
|
2891
|
+
if (on_event.length > 0) existing.on_event = on_event;
|
|
2805
2892
|
}
|
|
2806
2893
|
}
|
|
2807
2894
|
}
|
|
@@ -2839,13 +2926,21 @@ function extractDefineModelCall(programPath, compilerState, actionCounter) {
|
|
|
2839
2926
|
if (transConfig.guard) {
|
|
2840
2927
|
conditions.push(parseGuardExpression(String(transConfig.guard)));
|
|
2841
2928
|
}
|
|
2929
|
+
if (transConfig.conditions && Array.isArray(transConfig.conditions)) {
|
|
2930
|
+
for (const cond of transConfig.conditions) {
|
|
2931
|
+
if (typeof cond === "object" && cond !== null) {
|
|
2932
|
+
conditions.push(cond);
|
|
2933
|
+
}
|
|
2934
|
+
}
|
|
2935
|
+
}
|
|
2936
|
+
const actions = parseActionArray(transConfig.actions, actionCounter);
|
|
2842
2937
|
compilerState.transitions.push({
|
|
2843
2938
|
name,
|
|
2844
2939
|
from,
|
|
2845
2940
|
to,
|
|
2846
2941
|
description: transConfig.description,
|
|
2847
2942
|
conditions: conditions.length > 0 ? conditions : void 0,
|
|
2848
|
-
actions
|
|
2943
|
+
actions,
|
|
2849
2944
|
roles: transConfig.roles,
|
|
2850
2945
|
auto: transConfig.auto,
|
|
2851
2946
|
required_fields: transConfig.required_fields
|
|
@@ -3771,6 +3866,18 @@ function emitIR(extracted) {
|
|
|
3771
3866
|
});
|
|
3772
3867
|
stateNames.add(transition.to);
|
|
3773
3868
|
}
|
|
3869
|
+
for (const from of transition.from) {
|
|
3870
|
+
if (from && !stateNames.has(from)) {
|
|
3871
|
+
stateArray.push({
|
|
3872
|
+
name: from,
|
|
3873
|
+
type: "REGULAR",
|
|
3874
|
+
on_enter: [],
|
|
3875
|
+
during: [],
|
|
3876
|
+
on_exit: []
|
|
3877
|
+
});
|
|
3878
|
+
stateNames.add(from);
|
|
3879
|
+
}
|
|
3880
|
+
}
|
|
3774
3881
|
}
|
|
3775
3882
|
const fieldNames = new Set(fields.map((f) => f.name));
|
|
3776
3883
|
let normalizedView;
|
|
@@ -4723,7 +4830,7 @@ function createVisitor(options = {}) {
|
|
|
4723
4830
|
}
|
|
4724
4831
|
}
|
|
4725
4832
|
if (mode !== "strict") return;
|
|
4726
|
-
if (source.startsWith("@mindmatrix/") || source === "react" || source.startsWith("react/") || source.startsWith(".") || source.startsWith("/")) {
|
|
4833
|
+
if (source.startsWith("@mindmatrix/") || source.startsWith("@mmapp/") || source === "react" || source.startsWith("react/") || source.startsWith(".") || source.startsWith("/")) {
|
|
4727
4834
|
return;
|
|
4728
4835
|
}
|
|
4729
4836
|
const error = {
|