jui-tools-mcp-server 2.5.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 (172) hide show
  1. package/.github/workflows/test.yml +35 -0
  2. package/README.md +148 -0
  3. package/data/attribute_definitions.json +4248 -0
  4. package/data/attribute_semantics.json +137 -0
  5. package/data/binding_semantics.json +115 -0
  6. package/data/component_metadata.json +769 -0
  7. package/data/coverage.json +114 -0
  8. package/data/screen_identity.json +350 -0
  9. package/dist/cli_runner.d.ts +10 -0
  10. package/dist/cli_runner.js +33 -0
  11. package/dist/config.d.ts +19 -0
  12. package/dist/config.js +67 -0
  13. package/dist/data/component_metadata.d.ts +30 -0
  14. package/dist/data/component_metadata.js +498 -0
  15. package/dist/data/derived.d.ts +198 -0
  16. package/dist/data/derived.js +293 -0
  17. package/dist/index.d.ts +2 -0
  18. package/dist/index.js +130 -0
  19. package/dist/spec_loader.d.ts +110 -0
  20. package/dist/spec_loader.js +445 -0
  21. package/dist/tools/api/list_api_models.d.ts +10 -0
  22. package/dist/tools/api/list_api_models.js +37 -0
  23. package/dist/tools/api/list_api_specs.d.ts +19 -0
  24. package/dist/tools/api/list_api_specs.js +42 -0
  25. package/dist/tools/api/preview_api_model_sync.d.ts +11 -0
  26. package/dist/tools/api/preview_api_model_sync.js +34 -0
  27. package/dist/tools/context/get_project_config.d.ts +3 -0
  28. package/dist/tools/context/get_project_config.js +20 -0
  29. package/dist/tools/context/list_component_specs.d.ts +3 -0
  30. package/dist/tools/context/list_component_specs.js +35 -0
  31. package/dist/tools/context/list_layouts.d.ts +3 -0
  32. package/dist/tools/context/list_layouts.js +118 -0
  33. package/dist/tools/context/list_screen_specs.d.ts +3 -0
  34. package/dist/tools/context/list_screen_specs.js +37 -0
  35. package/dist/tools/context/read_layout_file.d.ts +3 -0
  36. package/dist/tools/context/read_layout_file.js +25 -0
  37. package/dist/tools/context/read_spec_file.d.ts +3 -0
  38. package/dist/tools/context/read_spec_file.js +27 -0
  39. package/dist/tools/doc/doc_generate_component.d.ts +3 -0
  40. package/dist/tools/doc/doc_generate_component.js +38 -0
  41. package/dist/tools/doc/doc_generate_html.d.ts +3 -0
  42. package/dist/tools/doc/doc_generate_html.js +46 -0
  43. package/dist/tools/doc/doc_generate_spec.d.ts +3 -0
  44. package/dist/tools/doc/doc_generate_spec.js +38 -0
  45. package/dist/tools/doc/doc_init_component.d.ts +3 -0
  46. package/dist/tools/doc/doc_init_component.js +30 -0
  47. package/dist/tools/doc/doc_init_spec.d.ts +3 -0
  48. package/dist/tools/doc/doc_init_spec.js +33 -0
  49. package/dist/tools/doc/doc_rules_init.d.ts +3 -0
  50. package/dist/tools/doc/doc_rules_init.js +25 -0
  51. package/dist/tools/doc/doc_rules_show.d.ts +3 -0
  52. package/dist/tools/doc/doc_rules_show.js +21 -0
  53. package/dist/tools/doc/doc_validate_component.d.ts +3 -0
  54. package/dist/tools/doc/doc_validate_component.js +39 -0
  55. package/dist/tools/doc/doc_validate_spec.d.ts +3 -0
  56. package/dist/tools/doc/doc_validate_spec.js +40 -0
  57. package/dist/tools/jui/jui_build.d.ts +3 -0
  58. package/dist/tools/jui/jui_build.js +31 -0
  59. package/dist/tools/jui/jui_generate_converter.d.ts +3 -0
  60. package/dist/tools/jui/jui_generate_converter.js +43 -0
  61. package/dist/tools/jui/jui_generate_project.d.ts +3 -0
  62. package/dist/tools/jui/jui_generate_project.js +43 -0
  63. package/dist/tools/jui/jui_generate_screen.d.ts +3 -0
  64. package/dist/tools/jui/jui_generate_screen.js +22 -0
  65. package/dist/tools/jui/jui_init.d.ts +3 -0
  66. package/dist/tools/jui/jui_init.js +42 -0
  67. package/dist/tools/jui/jui_migrate_layouts.d.ts +3 -0
  68. package/dist/tools/jui/jui_migrate_layouts.js +25 -0
  69. package/dist/tools/jui/jui_sync_tool.d.ts +3 -0
  70. package/dist/tools/jui/jui_sync_tool.js +33 -0
  71. package/dist/tools/jui/jui_verify.d.ts +3 -0
  72. package/dist/tools/jui/jui_verify.js +43 -0
  73. package/dist/tools/spec/get_binding_rules.d.ts +3 -0
  74. package/dist/tools/spec/get_binding_rules.js +8 -0
  75. package/dist/tools/spec/get_data_source.d.ts +3 -0
  76. package/dist/tools/spec/get_data_source.js +41 -0
  77. package/dist/tools/spec/get_modifier_order.d.ts +3 -0
  78. package/dist/tools/spec/get_modifier_order.js +14 -0
  79. package/dist/tools/spec/get_platform_mapping.d.ts +3 -0
  80. package/dist/tools/spec/get_platform_mapping.js +20 -0
  81. package/dist/tools/spec/get_screen_identity.d.ts +3 -0
  82. package/dist/tools/spec/get_screen_identity.js +22 -0
  83. package/dist/tools/spec/lookup_attribute.d.ts +3 -0
  84. package/dist/tools/spec/lookup_attribute.js +14 -0
  85. package/dist/tools/spec/lookup_component.d.ts +3 -0
  86. package/dist/tools/spec/lookup_component.js +15 -0
  87. package/dist/tools/spec/search_components.d.ts +3 -0
  88. package/dist/tools/spec/search_components.js +14 -0
  89. package/dist/tools/test/test_artifacts_pull.d.ts +3 -0
  90. package/dist/tools/test/test_artifacts_pull.js +48 -0
  91. package/dist/tools/test/test_artifacts_status.d.ts +3 -0
  92. package/dist/tools/test/test_artifacts_status.js +28 -0
  93. package/dist/tools/test/test_generate_description.d.ts +3 -0
  94. package/dist/tools/test/test_generate_description.js +27 -0
  95. package/dist/tools/test/test_generate_flow.d.ts +3 -0
  96. package/dist/tools/test/test_generate_flow.js +29 -0
  97. package/dist/tools/test/test_generate_screen.d.ts +3 -0
  98. package/dist/tools/test/test_generate_screen.js +29 -0
  99. package/dist/tools/test/test_mock_generate.d.ts +3 -0
  100. package/dist/tools/test/test_mock_generate.js +64 -0
  101. package/dist/tools/test/test_report.d.ts +3 -0
  102. package/dist/tools/test/test_report.js +26 -0
  103. package/dist/tools/test/test_validate.d.ts +3 -0
  104. package/dist/tools/test/test_validate.js +43 -0
  105. package/docs/design.md +227 -0
  106. package/install.sh +90 -0
  107. package/package.json +24 -0
  108. package/scripts/fetch-definitions.js +101 -0
  109. package/scripts/fetch-manifest.js +60 -0
  110. package/src/cli_runner.ts +55 -0
  111. package/src/config.ts +88 -0
  112. package/src/data/derived.ts +305 -0
  113. package/src/index.ts +157 -0
  114. package/src/spec_loader.ts +591 -0
  115. package/src/tools/api/list_api_models.ts +44 -0
  116. package/src/tools/api/list_api_specs.ts +53 -0
  117. package/src/tools/api/preview_api_model_sync.ts +41 -0
  118. package/src/tools/context/get_project_config.ts +27 -0
  119. package/src/tools/context/list_component_specs.ts +44 -0
  120. package/src/tools/context/list_layouts.ts +131 -0
  121. package/src/tools/context/list_screen_specs.ts +46 -0
  122. package/src/tools/context/read_layout_file.ts +35 -0
  123. package/src/tools/context/read_spec_file.ts +38 -0
  124. package/src/tools/doc/doc_generate_component.ts +43 -0
  125. package/src/tools/doc/doc_generate_html.ts +40 -0
  126. package/src/tools/doc/doc_generate_spec.ts +43 -0
  127. package/src/tools/doc/doc_init_component.ts +32 -0
  128. package/src/tools/doc/doc_init_spec.ts +37 -0
  129. package/src/tools/doc/doc_rules_init.ts +29 -0
  130. package/src/tools/doc/doc_rules_show.ts +27 -0
  131. package/src/tools/doc/doc_validate_component.ts +47 -0
  132. package/src/tools/doc/doc_validate_spec.ts +48 -0
  133. package/src/tools/jui/jui_build.ts +31 -0
  134. package/src/tools/jui/jui_generate_converter.ts +41 -0
  135. package/src/tools/jui/jui_generate_project.ts +37 -0
  136. package/src/tools/jui/jui_generate_screen.ts +28 -0
  137. package/src/tools/jui/jui_init.ts +38 -0
  138. package/src/tools/jui/jui_migrate_layouts.ts +29 -0
  139. package/src/tools/jui/jui_sync_tool.ts +33 -0
  140. package/src/tools/jui/jui_verify.ts +43 -0
  141. package/src/tools/spec/get_binding_rules.ts +16 -0
  142. package/src/tools/spec/get_data_source.ts +53 -0
  143. package/src/tools/spec/get_modifier_order.ts +22 -0
  144. package/src/tools/spec/get_platform_mapping.ts +28 -0
  145. package/src/tools/spec/get_screen_identity.ts +31 -0
  146. package/src/tools/spec/lookup_attribute.ts +22 -0
  147. package/src/tools/spec/lookup_component.ts +23 -0
  148. package/src/tools/spec/search_components.ts +22 -0
  149. package/src/tools/test/test_artifacts_pull.ts +47 -0
  150. package/src/tools/test/test_artifacts_status.ts +34 -0
  151. package/src/tools/test/test_generate_description.ts +33 -0
  152. package/src/tools/test/test_generate_flow.ts +33 -0
  153. package/src/tools/test/test_generate_screen.ts +33 -0
  154. package/src/tools/test/test_mock_generate.ts +58 -0
  155. package/src/tools/test/test_report.ts +32 -0
  156. package/src/tools/test/test_validate.ts +43 -0
  157. package/tests/__snapshots__/derived.test.ts.snap +279 -0
  158. package/tests/bundled_snapshot.test.ts +155 -0
  159. package/tests/cli_runner.test.ts +120 -0
  160. package/tests/config.test.ts +212 -0
  161. package/tests/derived.test.ts +114 -0
  162. package/tests/helpers.ts +272 -0
  163. package/tests/setup.ts +27 -0
  164. package/tests/snapshot_completeness.test.ts +152 -0
  165. package/tests/spec_loader.test.ts +488 -0
  166. package/tests/tools_cli.test.ts +687 -0
  167. package/tests/tools_context.test.ts +274 -0
  168. package/tests/tools_spec.test.ts +225 -0
  169. package/tests/tools_test.test.ts +284 -0
  170. package/tsconfig.json +15 -0
  171. package/uninstall.sh +46 -0
  172. package/vitest.config.ts +15 -0
@@ -0,0 +1,769 @@
1
+ {
2
+ "_description": "Per-component presentation metadata consumed by jsonui-mcp-server. Augments attribute_definitions.json (which defines the raw attribute schema, including binding_direction per attribute) with human descriptions, aliases, platform availability, platform-specific implementation notes, and agent-facing rules. Edit this file to change what `lookup_component` / `search_components` / `lookup_attribute` return from the MCP.",
3
+ "_schema": {
4
+ "description": "string",
5
+ "aliases": "string[]",
6
+ "platforms": {
7
+ "swift_generated": "boolean",
8
+ "swift_dynamic": "boolean",
9
+ "kotlin_generated": "boolean",
10
+ "kotlin_dynamic": "boolean",
11
+ "react": "boolean"
12
+ },
13
+ "platformSpecific": "Record<platform, Record<key, note>>",
14
+ "rules": "string[] — agent-facing prose rules"
15
+ },
16
+ "Label": {
17
+ "description": "Text display component",
18
+ "aliases": [
19
+ "Text"
20
+ ],
21
+ "platforms": {
22
+ "swift_generated": true,
23
+ "swift_dynamic": true,
24
+ "kotlin_generated": true,
25
+ "kotlin_dynamic": true,
26
+ "react": true
27
+ },
28
+ "platformSpecific": {
29
+ "swift": {
30
+ "view": "Text",
31
+ "partialAttributes": "Supports styled text ranges via partialAttributes array",
32
+ "alignment": ".leading alignment added to frame for textAlign to work"
33
+ },
34
+ "kotlin": {
35
+ "composable": "Text",
36
+ "partialAttributes": "AnnotatedString with SpanStyle for styled ranges"
37
+ },
38
+ "react": {
39
+ "element": "<span>",
40
+ "lineClamping": "lines:1 -> truncate, lines:2+ -> line-clamp-N"
41
+ }
42
+ },
43
+ "rules": [
44
+ "text attribute supports interpolation with @{binding} syntax",
45
+ "partialAttributes allows styling ranges within text",
46
+ "linkable enables auto-detection of URLs",
47
+ "Frame alignment must be .leading for textAlign to work correctly (Swift)"
48
+ ]
49
+ },
50
+ "TextField": {
51
+ "description": "Text input field",
52
+ "aliases": [
53
+ "EditText",
54
+ "Input"
55
+ ],
56
+ "platforms": {
57
+ "swift_generated": true,
58
+ "swift_dynamic": true,
59
+ "kotlin_generated": true,
60
+ "kotlin_dynamic": true,
61
+ "react": true
62
+ },
63
+ "platformSpecific": {
64
+ "swift": {
65
+ "view": "TextField / SecureField (when input='password')",
66
+ "focusManagement": "@FocusState variable generated, nextFocusId for chain",
67
+ "caretColor": "tintColor or caretAttributes.color"
68
+ },
69
+ "kotlin": {
70
+ "composable": "OutlinedTextField / BasicTextField",
71
+ "focusManagement": "FocusManager.requestFocus(nextFocusId)",
72
+ "inputType": "KeyboardOptions based on contentType/input"
73
+ },
74
+ "react": {
75
+ "element": "<input type=\"...\"/>",
76
+ "autoHandler": "text binding auto-generates onPropChange handler",
77
+ "inputMapping": "email->email, password->password, number->number, phone->tel"
78
+ }
79
+ },
80
+ "rules": [
81
+ "input='password' switches to SecureField in SwiftUI",
82
+ "fieldId + nextFocusId creates focus chain across fields",
83
+ "text binding MUST be two-way ($data. in Swift)",
84
+ "React auto-generates onXxxChange from text binding"
85
+ ]
86
+ },
87
+ "Button": {
88
+ "description": "Tappable button component",
89
+ "aliases": [],
90
+ "platforms": {
91
+ "swift_generated": true,
92
+ "swift_dynamic": true,
93
+ "kotlin_generated": true,
94
+ "kotlin_dynamic": true,
95
+ "react": true
96
+ },
97
+ "platformSpecific": {
98
+ "swift": {
99
+ "view": "StateAwareButtonView (not plain Button+Text)",
100
+ "asyncSupport": "isAsync flag for async operations with loading state"
101
+ },
102
+ "kotlin": {
103
+ "composable": "Button with Text content",
104
+ "asyncSupport": "isAsync with CoroutineScope(Dispatchers.Main)"
105
+ },
106
+ "react": {
107
+ "element": "<button> or <Link><button> (Next.js navigation)",
108
+ "stateStyles": "hover/active/disabled via Tailwind prefixes"
109
+ }
110
+ },
111
+ "rules": [
112
+ "Uses StateAwareButtonView in Swift (not plain Text+Button)",
113
+ "Width/height are passed to view for background fill",
114
+ "onclick (lowercase) = selector format, onClick (camelCase) = binding format",
115
+ "Supports async operations with loading state and spinner"
116
+ ]
117
+ },
118
+ "Image": {
119
+ "description": "Local image component",
120
+ "aliases": [],
121
+ "platforms": {
122
+ "swift_generated": true,
123
+ "swift_dynamic": true,
124
+ "kotlin_generated": true,
125
+ "kotlin_dynamic": true,
126
+ "react": true
127
+ },
128
+ "platformSpecific": {
129
+ "swift": {
130
+ "view": "Image (with .resizable().aspectRatio())",
131
+ "priority": "srcName > src > defaultImage",
132
+ "circleImage": "type='CircleImage' adds .clipShape(Circle())"
133
+ },
134
+ "kotlin": {
135
+ "composable": "Image with painterResource",
136
+ "contentScale": "ContentScale.Fit/Crop/FillBounds"
137
+ },
138
+ "react": {
139
+ "element": "<img/>",
140
+ "objectFit": "aspectfit->contain, aspectfill->cover, scaletofill->fill"
141
+ }
142
+ },
143
+ "rules": [
144
+ ".resizable() must come first, then .aspectRatio (Swift)",
145
+ "CircleImage type adds circular clipping",
146
+ "contentMode maps differently per platform"
147
+ ]
148
+ },
149
+ "NetworkImage": {
150
+ "description": "Remote/network image component",
151
+ "aliases": [],
152
+ "platforms": {
153
+ "swift_generated": true,
154
+ "swift_dynamic": true,
155
+ "kotlin_generated": true,
156
+ "kotlin_dynamic": true,
157
+ "react": true
158
+ },
159
+ "platformSpecific": {
160
+ "swift": {
161
+ "view": "AsyncImage or custom NetworkImageView",
162
+ "defaultContentMode": ".center for matchParent"
163
+ },
164
+ "kotlin": {
165
+ "composable": "AsyncImage (Coil)"
166
+ },
167
+ "react": {
168
+ "element": "<img/> with src binding"
169
+ }
170
+ },
171
+ "rules": [
172
+ "Default contentMode is .center for matchParent (Swift)",
173
+ "Supports placeholder/error images via defaultImage"
174
+ ]
175
+ },
176
+ "View": {
177
+ "description": "Container view (HStack/VStack/ZStack)",
178
+ "aliases": [],
179
+ "platforms": {
180
+ "swift_generated": true,
181
+ "swift_dynamic": true,
182
+ "kotlin_generated": true,
183
+ "kotlin_dynamic": true,
184
+ "react": true
185
+ },
186
+ "platformSpecific": {
187
+ "swift": {
188
+ "view": "HStack/VStack/ZStack based on orientation",
189
+ "weightedChildren": "Special body splitting for weighted layouts",
190
+ "relativePositioning": "ZStack with alignment for relative layouts"
191
+ },
192
+ "kotlin": {
193
+ "composable": "Row/Column/Box based on orientation",
194
+ "relativePositioning": "ConstraintLayout if children have alignXyzOfView"
195
+ },
196
+ "react": {
197
+ "element": "<div> with flex classes",
198
+ "orientation": "horizontal->flex-row, vertical->flex-col, none->relative"
199
+ }
200
+ },
201
+ "rules": [
202
+ "orientation determines layout: horizontal->HStack/Row, vertical->VStack/Column, none->ZStack/Box",
203
+ "Children with alignXyzOfView trigger relative/constraint layout",
204
+ "bottomToTop/rightToLeft reverses child order",
205
+ "distribution: fillEqually/fill/equalSpacing affects child arrangement"
206
+ ]
207
+ },
208
+ "ScrollView": {
209
+ "description": "Scrollable container",
210
+ "aliases": [],
211
+ "platforms": {
212
+ "swift_generated": true,
213
+ "swift_dynamic": true,
214
+ "kotlin_generated": true,
215
+ "kotlin_dynamic": true,
216
+ "react": true
217
+ },
218
+ "platformSpecific": {
219
+ "swift": {
220
+ "view": "ScrollView(.horizontal/.vertical)"
221
+ },
222
+ "kotlin": {
223
+ "composable": "LazyColumn/LazyRow or verticalScroll/horizontalScroll"
224
+ },
225
+ "react": {
226
+ "element": "<div> with overflow-auto/scroll"
227
+ }
228
+ },
229
+ "rules": [
230
+ "orientation determines scroll direction",
231
+ "showsIndicator controls scrollbar visibility"
232
+ ]
233
+ },
234
+ "Collection": {
235
+ "description": "List/grid collection with data binding",
236
+ "aliases": [],
237
+ "platforms": {
238
+ "swift_generated": true,
239
+ "swift_dynamic": true,
240
+ "kotlin_generated": true,
241
+ "kotlin_dynamic": true,
242
+ "react": true
243
+ },
244
+ "platformSpecific": {
245
+ "swift": {
246
+ "view": "LazyVStack/LazyHStack for performance",
247
+ "sections": "Section-based with headers/footers",
248
+ "flowLayout": "Flow layout support"
249
+ },
250
+ "kotlin": {
251
+ "composable": "LazyColumn/LazyRow/LazyVerticalGrid",
252
+ "sections": "Section-based with column count per section"
253
+ },
254
+ "react": {
255
+ "element": "map() with component references",
256
+ "cellFormat": "sections[].cell or cellClasses for component routing"
257
+ }
258
+ },
259
+ "rules": [
260
+ "items binding connects to data source array",
261
+ "Sections support different cell types per section",
262
+ "cellIdProperty extracts unique keys from item data",
263
+ "scrollTo binding enables programmatic scrolling",
264
+ "lazy (default true) controls virtualization: true uses LazyVStack/LazyColumn/LazyVerticalGrid with internal scroll; false renders eagerly in a plain VStack/Column with NO scroll container (use when nested in an already-scrollable parent). Sticky headers and paging require lazy: true."
265
+ ]
266
+ },
267
+ "Switch": {
268
+ "description": "Toggle switch (iOS style)",
269
+ "aliases": [],
270
+ "platforms": {
271
+ "swift_generated": true,
272
+ "swift_dynamic": true,
273
+ "kotlin_generated": true,
274
+ "kotlin_dynamic": true,
275
+ "react": true
276
+ },
277
+ "platformSpecific": {
278
+ "swift": {
279
+ "view": "Toggle with switch style"
280
+ },
281
+ "kotlin": {
282
+ "composable": "Switch"
283
+ },
284
+ "react": {
285
+ "element": "Hidden checkbox + styled span (iOS-style toggle)"
286
+ }
287
+ },
288
+ "rules": [
289
+ "State binding priority: isOn > value > checked > bind",
290
+ "Two-way binding updates via $data. (Swift) or updateData (Kotlin)",
291
+ "onValueChange/onToggle for state change callback"
292
+ ]
293
+ },
294
+ "Toggle": {
295
+ "description": "Toggle control (checkbox style)",
296
+ "aliases": [],
297
+ "platforms": {
298
+ "swift_generated": true,
299
+ "swift_dynamic": true,
300
+ "kotlin_generated": true,
301
+ "kotlin_dynamic": true,
302
+ "react": true
303
+ },
304
+ "platformSpecific": {
305
+ "swift": {
306
+ "view": "Toggle"
307
+ },
308
+ "kotlin": {
309
+ "composable": "Checkbox or Switch"
310
+ },
311
+ "react": {
312
+ "element": "<input type=\"checkbox\">"
313
+ }
314
+ },
315
+ "rules": [
316
+ "Similar to Switch but may render differently per platform",
317
+ "State binding priority: isOn > value > checked > bind"
318
+ ]
319
+ },
320
+ "CheckBox": {
321
+ "description": "Checkbox input",
322
+ "aliases": [
323
+ "Check",
324
+ "Checkbox"
325
+ ],
326
+ "platforms": {
327
+ "swift_generated": true,
328
+ "swift_dynamic": true,
329
+ "kotlin_generated": true,
330
+ "kotlin_dynamic": true,
331
+ "react": true
332
+ },
333
+ "platformSpecific": {
334
+ "swift": {
335
+ "view": "Toggle with checkbox style"
336
+ },
337
+ "kotlin": {
338
+ "composable": "Checkbox or IconToggleButton (custom icons)"
339
+ },
340
+ "react": {
341
+ "element": "<input type=\"checkbox\">"
342
+ }
343
+ },
344
+ "rules": [
345
+ "Kotlin supports custom icon/selectedIcon via IconToggleButton",
346
+ "checked is primary binding attribute"
347
+ ]
348
+ },
349
+ "Radio": {
350
+ "description": "Radio button group",
351
+ "aliases": [],
352
+ "platforms": {
353
+ "swift_generated": true,
354
+ "swift_dynamic": true,
355
+ "kotlin_generated": true,
356
+ "kotlin_dynamic": true,
357
+ "react": true
358
+ },
359
+ "platformSpecific": {
360
+ "swift": {
361
+ "view": "Custom radio button implementation"
362
+ },
363
+ "kotlin": {
364
+ "composable": "RadioButton in Row/Column"
365
+ },
366
+ "react": {
367
+ "element": "<input type=\"radio\">"
368
+ }
369
+ },
370
+ "rules": [
371
+ "selectedIndex tracks currently selected option"
372
+ ]
373
+ },
374
+ "SelectBox": {
375
+ "description": "Dropdown/picker selection",
376
+ "aliases": [],
377
+ "platforms": {
378
+ "swift_generated": true,
379
+ "swift_dynamic": true,
380
+ "kotlin_generated": true,
381
+ "kotlin_dynamic": true,
382
+ "react": true
383
+ },
384
+ "platformSpecific": {
385
+ "swift": {
386
+ "view": "Picker"
387
+ },
388
+ "kotlin": {
389
+ "composable": "ExposedDropdownMenuBox"
390
+ },
391
+ "react": {
392
+ "element": "<select>"
393
+ }
394
+ },
395
+ "rules": [
396
+ "selectedIndex tracks currently selected option"
397
+ ]
398
+ },
399
+ "Segment": {
400
+ "description": "Segmented control",
401
+ "aliases": [],
402
+ "platforms": {
403
+ "swift_generated": true,
404
+ "swift_dynamic": true,
405
+ "kotlin_generated": true,
406
+ "kotlin_dynamic": true,
407
+ "react": true
408
+ },
409
+ "platformSpecific": {
410
+ "swift": {
411
+ "view": "Picker with segmented style"
412
+ },
413
+ "kotlin": {
414
+ "composable": "TabRow or custom segment"
415
+ },
416
+ "react": {
417
+ "element": "Button group with useState for selectedIndex"
418
+ }
419
+ },
420
+ "rules": [
421
+ "Without binding, React auto-generates useState for selectedIndex",
422
+ "selectedIndex tracks active segment"
423
+ ]
424
+ },
425
+ "Slider": {
426
+ "description": "Slider/range input",
427
+ "aliases": [],
428
+ "platforms": {
429
+ "swift_generated": true,
430
+ "swift_dynamic": true,
431
+ "kotlin_generated": true,
432
+ "kotlin_dynamic": true,
433
+ "react": true
434
+ },
435
+ "platformSpecific": {
436
+ "swift": {
437
+ "view": "Slider"
438
+ },
439
+ "kotlin": {
440
+ "composable": "Slider with step calculation"
441
+ },
442
+ "react": {
443
+ "element": "<input type=\"range\">"
444
+ }
445
+ },
446
+ "rules": [
447
+ "value/bind is two-way binding",
448
+ "minimumValue/maximumValue define range (default 0-100)",
449
+ "step defines discrete steps"
450
+ ]
451
+ },
452
+ "Progress": {
453
+ "description": "Progress bar/indicator",
454
+ "aliases": [],
455
+ "platforms": {
456
+ "swift_generated": true,
457
+ "swift_dynamic": true,
458
+ "kotlin_generated": true,
459
+ "kotlin_dynamic": true,
460
+ "react": true
461
+ },
462
+ "platformSpecific": {
463
+ "swift": {
464
+ "view": "ProgressView"
465
+ },
466
+ "kotlin": {
467
+ "composable": "LinearProgressIndicator / CircularProgressIndicator"
468
+ },
469
+ "react": {
470
+ "element": "<progress> or <div> with width percentage"
471
+ }
472
+ },
473
+ "rules": [
474
+ "value represents progress (0.0 to 1.0)"
475
+ ]
476
+ },
477
+ "Indicator": {
478
+ "description": "Activity indicator / loading spinner",
479
+ "aliases": [],
480
+ "platforms": {
481
+ "swift_generated": true,
482
+ "swift_dynamic": true,
483
+ "kotlin_generated": true,
484
+ "kotlin_dynamic": true,
485
+ "react": true
486
+ },
487
+ "platformSpecific": {
488
+ "swift": {
489
+ "view": "ProgressView (indeterminate)"
490
+ },
491
+ "kotlin": {
492
+ "composable": "CircularProgressIndicator"
493
+ },
494
+ "react": {
495
+ "element": "Animated spinner div"
496
+ }
497
+ },
498
+ "rules": []
499
+ },
500
+ "CircleView": {
501
+ "description": "Circular shape view",
502
+ "aliases": [],
503
+ "platforms": {
504
+ "swift_generated": false,
505
+ "swift_dynamic": false,
506
+ "kotlin_generated": false,
507
+ "kotlin_dynamic": true,
508
+ "react": true
509
+ },
510
+ "platformSpecific": {
511
+ "kotlin": {
512
+ "composable": "Box with CircleShape clip (dynamic runtime only)"
513
+ },
514
+ "react": {
515
+ "element": "div with rounded-full clip, centered content"
516
+ }
517
+ },
518
+ "rules": [
519
+ "Only the Kotlin dynamic runtime and React dispatch CircleView — SwiftUI codegen/dynamic and Compose codegen render a fallback instead",
520
+ "For cross-platform layouts use View with cornerRadius"
521
+ ]
522
+ },
523
+ "GradientView": {
524
+ "description": "Gradient background view",
525
+ "aliases": [],
526
+ "platforms": {
527
+ "swift_generated": true,
528
+ "swift_dynamic": true,
529
+ "kotlin_generated": true,
530
+ "kotlin_dynamic": true,
531
+ "react": true
532
+ },
533
+ "platformSpecific": {
534
+ "swift": {
535
+ "view": "LinearGradient / RadialGradient"
536
+ },
537
+ "kotlin": {
538
+ "composable": "Brush.linearGradient / radialGradient"
539
+ },
540
+ "react": {
541
+ "element": "div with a CSS linear/radial-gradient background"
542
+ }
543
+ },
544
+ "rules": [
545
+ "colors array defines gradient stops"
546
+ ]
547
+ },
548
+ "Blur": {
549
+ "description": "Blur effect view",
550
+ "aliases": [],
551
+ "platforms": {
552
+ "swift_generated": true,
553
+ "swift_dynamic": true,
554
+ "kotlin_generated": true,
555
+ "kotlin_dynamic": true,
556
+ "react": true
557
+ },
558
+ "platformSpecific": {
559
+ "swift": {
560
+ "view": ".blur() modifier or UIVisualEffectView"
561
+ },
562
+ "kotlin": {
563
+ "composable": "Box with Modifier.blur() (dynamic runtime only)"
564
+ },
565
+ "react": {
566
+ "element": "div with backdrop-filter blur"
567
+ }
568
+ },
569
+ "rules": [
570
+ "Kotlin Compose codegen dispatches only the legacy BlurView spelling; canonical Blur reaches the dynamic runtime only"
571
+ ]
572
+ },
573
+ "IconLabel": {
574
+ "description": "Icon with label combination",
575
+ "aliases": [],
576
+ "platforms": {
577
+ "swift_generated": true,
578
+ "swift_dynamic": true,
579
+ "kotlin_generated": true,
580
+ "kotlin_dynamic": true,
581
+ "react": true
582
+ },
583
+ "platformSpecific": {
584
+ "swift": {
585
+ "view": "Label with systemImage"
586
+ },
587
+ "kotlin": {
588
+ "composable": "Row with Icon + Text"
589
+ },
590
+ "react": {
591
+ "element": "flex div with img + span; iconPosition maps to the flex direction"
592
+ }
593
+ },
594
+ "rules": [
595
+ "icon_on / icon_off / selected drive the two-state icon; iconPosition places the icon Left/Right/Top/Bottom"
596
+ ]
597
+ },
598
+ "Web": {
599
+ "description": "Web view / embedded browser",
600
+ "aliases": [],
601
+ "platforms": {
602
+ "swift_generated": true,
603
+ "swift_dynamic": true,
604
+ "kotlin_generated": true,
605
+ "kotlin_dynamic": true,
606
+ "react": true
607
+ },
608
+ "platformSpecific": {
609
+ "swift": {
610
+ "view": "WKWebView wrapper"
611
+ },
612
+ "kotlin": {
613
+ "composable": "AndroidView with WebView"
614
+ },
615
+ "react": {
616
+ "element": "sandboxed iframe (allow-scripts allow-same-origin allow-forms)"
617
+ }
618
+ },
619
+ "rules": [
620
+ "url attribute specifies the web page to load"
621
+ ]
622
+ },
623
+ "SafeAreaView": {
624
+ "description": "Safe area respecting container",
625
+ "aliases": [],
626
+ "platforms": {
627
+ "swift_generated": true,
628
+ "swift_dynamic": true,
629
+ "kotlin_generated": true,
630
+ "kotlin_dynamic": true,
631
+ "react": false
632
+ },
633
+ "platformSpecific": {
634
+ "swift": {
635
+ "view": "Respects safeAreaInsets"
636
+ },
637
+ "kotlin": {
638
+ "composable": "Respects WindowInsets"
639
+ }
640
+ },
641
+ "rules": [
642
+ "Ensures content doesn't overlap with system UI"
643
+ ]
644
+ },
645
+ "TabView": {
646
+ "description": "Tab-based navigation container",
647
+ "aliases": [],
648
+ "platforms": {
649
+ "swift_generated": true,
650
+ "swift_dynamic": true,
651
+ "kotlin_generated": true,
652
+ "kotlin_dynamic": true,
653
+ "react": true
654
+ },
655
+ "platformSpecific": {
656
+ "swift": {
657
+ "view": "TabView with tag-based selection"
658
+ },
659
+ "kotlin": {
660
+ "composable": "Scaffold with BottomNavigation"
661
+ },
662
+ "react": {
663
+ "element": "Tab buttons + conditional content rendering"
664
+ }
665
+ },
666
+ "rules": [
667
+ "Each tab child uses tag for identification"
668
+ ]
669
+ },
670
+ "TextView": {
671
+ "description": "Multi-line text input",
672
+ "aliases": [],
673
+ "platforms": {
674
+ "swift_generated": true,
675
+ "swift_dynamic": true,
676
+ "kotlin_generated": true,
677
+ "kotlin_dynamic": true,
678
+ "react": true
679
+ },
680
+ "platformSpecific": {
681
+ "swift": {
682
+ "view": "TextEditor"
683
+ },
684
+ "kotlin": {
685
+ "composable": "OutlinedTextField with multiline"
686
+ },
687
+ "react": {
688
+ "element": "<textarea>"
689
+ }
690
+ },
691
+ "rules": [
692
+ "text binding is two-way",
693
+ "Multi-line input (unlike TextField which is single-line by default)"
694
+ ]
695
+ },
696
+ "EditText": {
697
+ "description": "Text input (Android naming convention)",
698
+ "aliases": [],
699
+ "platforms": {
700
+ "swift_generated": true,
701
+ "swift_dynamic": true,
702
+ "kotlin_generated": false,
703
+ "kotlin_dynamic": true,
704
+ "react": true
705
+ },
706
+ "platformSpecific": {
707
+ "kotlin": {
708
+ "composable": "OutlinedTextField"
709
+ }
710
+ },
711
+ "rules": [
712
+ "Alias spelling of TextField (canonical: TextField)",
713
+ "The Compose codegen dispatches only the canonical TextField spelling — write TextField for kotlin_generated targets"
714
+ ]
715
+ },
716
+ "Input": {
717
+ "description": "Generic input (React naming convention)",
718
+ "aliases": [],
719
+ "platforms": {
720
+ "swift_generated": true,
721
+ "swift_dynamic": true,
722
+ "kotlin_generated": false,
723
+ "kotlin_dynamic": true,
724
+ "react": true
725
+ },
726
+ "platformSpecific": {
727
+ "react": {
728
+ "element": "<input>"
729
+ }
730
+ },
731
+ "rules": [
732
+ "Alias spelling of TextField (canonical: TextField)",
733
+ "The Compose codegen dispatches only the canonical TextField spelling — write TextField for kotlin_generated targets"
734
+ ]
735
+ },
736
+ "Embed": {
737
+ "description": "Embeds another screen as a region of this layout. The embedded screen owns its own ViewModel — independent from the parent VM.",
738
+ "aliases": [],
739
+ "platforms": {
740
+ "swift_generated": true,
741
+ "swift_dynamic": true,
742
+ "kotlin_generated": true,
743
+ "kotlin_dynamic": true,
744
+ "react": true
745
+ },
746
+ "platformSpecific": {
747
+ "swift": {
748
+ "view": "EmbedContainer wrapping the embedded screen's generated View (static) or DynamicView(jsonName:data:) (dynamic). @StateObject identity gives natural VM isolation."
749
+ },
750
+ "kotlin": {
751
+ "composable": "EmbedContainer with per-slot ViewModelStoreOwner remembered against the layout id — required so that the same screen embedded twice gets two VM instances."
752
+ },
753
+ "react": {
754
+ "element": "<EmbedContainer> rendering the embedded screen component. Hook closure scoping gives natural VM isolation."
755
+ }
756
+ },
757
+ "rules": [
758
+ "id is required on the Layout JSON Embed node and must be unique across the parent layout. On Android it doubles as the ViewModelStoreOwner key (remember(id)) — without it, multi-embed of the same screen would share a VM.",
759
+ "screen value is the layout JSON filename (snake_case, no extension), e.g. 'order_detail'. Codegen converts to the View class name; dynamic mode loads the layout JSON as-is.",
760
+ "The embedded screen requires no spec changes — the parent declares the embed in structure.embeds[]; the embedded VM stays as-is. Optional applyInitParams(_:) on the embedded VM consumes params.",
761
+ "params keys are camelCase at every level; values form a tree — intermediate nodes are literal objects only, leaves are scalar literals or @{varName} bindings (bindings are leaf-only; arrays unsupported). Bindings reactively propagate.",
762
+ "events keys follow on[A-Z]...; values must reference parent VM methods or eventHandlers. Embedded VMs emit via the lib-provided emit(name, payload) helper.",
763
+ "navigationMode 'delegate' (default): parent NavController/Router is shared; pop/dismiss/navigateBack are bounded at the embed and do not close it. 'isolated': the embed owns a private nav stack — push stays inside, pop stops at the embed stack root, the embed never closes itself. Requires SwiftJsonUI >= 10.5.0 / KotlinJsonUI >= 2.12.0 / EmbedContainer.tsx template v2.",
764
+ "Inside an isolated embed, present-type transitions (sheet/modal/dialog/dismiss) in the embedded screen's spec are a build error — iOS sheets present on the parent window, so visual containment cannot be guaranteed cross-platform.",
765
+ "Escape hatch from an isolated embed (full-screen transition, logout, session expiry): pass a parent-VM callback via params as a leaf binding (e.g. \"onExitRequested\": \"@{handleChildExit}\"). The child calls it; the parent executes the actual navigation. The child never navigates its host.",
766
+ "Isolated stack lifetime equals the embed container's identity: it survives Android configuration changes but resets when the embed leaves the view tree. Deep links address the host router only; OS back gestures / browser back are platform-delegated (Android: deepest non-empty isolated stack wins; web: browser back drives host history only)."
767
+ ]
768
+ }
769
+ }