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.
- package/.github/workflows/test.yml +35 -0
- package/README.md +148 -0
- package/data/attribute_definitions.json +4248 -0
- package/data/attribute_semantics.json +137 -0
- package/data/binding_semantics.json +115 -0
- package/data/component_metadata.json +769 -0
- package/data/coverage.json +114 -0
- package/data/screen_identity.json +350 -0
- package/dist/cli_runner.d.ts +10 -0
- package/dist/cli_runner.js +33 -0
- package/dist/config.d.ts +19 -0
- package/dist/config.js +67 -0
- package/dist/data/component_metadata.d.ts +30 -0
- package/dist/data/component_metadata.js +498 -0
- package/dist/data/derived.d.ts +198 -0
- package/dist/data/derived.js +293 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +130 -0
- package/dist/spec_loader.d.ts +110 -0
- package/dist/spec_loader.js +445 -0
- package/dist/tools/api/list_api_models.d.ts +10 -0
- package/dist/tools/api/list_api_models.js +37 -0
- package/dist/tools/api/list_api_specs.d.ts +19 -0
- package/dist/tools/api/list_api_specs.js +42 -0
- package/dist/tools/api/preview_api_model_sync.d.ts +11 -0
- package/dist/tools/api/preview_api_model_sync.js +34 -0
- package/dist/tools/context/get_project_config.d.ts +3 -0
- package/dist/tools/context/get_project_config.js +20 -0
- package/dist/tools/context/list_component_specs.d.ts +3 -0
- package/dist/tools/context/list_component_specs.js +35 -0
- package/dist/tools/context/list_layouts.d.ts +3 -0
- package/dist/tools/context/list_layouts.js +118 -0
- package/dist/tools/context/list_screen_specs.d.ts +3 -0
- package/dist/tools/context/list_screen_specs.js +37 -0
- package/dist/tools/context/read_layout_file.d.ts +3 -0
- package/dist/tools/context/read_layout_file.js +25 -0
- package/dist/tools/context/read_spec_file.d.ts +3 -0
- package/dist/tools/context/read_spec_file.js +27 -0
- package/dist/tools/doc/doc_generate_component.d.ts +3 -0
- package/dist/tools/doc/doc_generate_component.js +38 -0
- package/dist/tools/doc/doc_generate_html.d.ts +3 -0
- package/dist/tools/doc/doc_generate_html.js +46 -0
- package/dist/tools/doc/doc_generate_spec.d.ts +3 -0
- package/dist/tools/doc/doc_generate_spec.js +38 -0
- package/dist/tools/doc/doc_init_component.d.ts +3 -0
- package/dist/tools/doc/doc_init_component.js +30 -0
- package/dist/tools/doc/doc_init_spec.d.ts +3 -0
- package/dist/tools/doc/doc_init_spec.js +33 -0
- package/dist/tools/doc/doc_rules_init.d.ts +3 -0
- package/dist/tools/doc/doc_rules_init.js +25 -0
- package/dist/tools/doc/doc_rules_show.d.ts +3 -0
- package/dist/tools/doc/doc_rules_show.js +21 -0
- package/dist/tools/doc/doc_validate_component.d.ts +3 -0
- package/dist/tools/doc/doc_validate_component.js +39 -0
- package/dist/tools/doc/doc_validate_spec.d.ts +3 -0
- package/dist/tools/doc/doc_validate_spec.js +40 -0
- package/dist/tools/jui/jui_build.d.ts +3 -0
- package/dist/tools/jui/jui_build.js +31 -0
- package/dist/tools/jui/jui_generate_converter.d.ts +3 -0
- package/dist/tools/jui/jui_generate_converter.js +43 -0
- package/dist/tools/jui/jui_generate_project.d.ts +3 -0
- package/dist/tools/jui/jui_generate_project.js +43 -0
- package/dist/tools/jui/jui_generate_screen.d.ts +3 -0
- package/dist/tools/jui/jui_generate_screen.js +22 -0
- package/dist/tools/jui/jui_init.d.ts +3 -0
- package/dist/tools/jui/jui_init.js +42 -0
- package/dist/tools/jui/jui_migrate_layouts.d.ts +3 -0
- package/dist/tools/jui/jui_migrate_layouts.js +25 -0
- package/dist/tools/jui/jui_sync_tool.d.ts +3 -0
- package/dist/tools/jui/jui_sync_tool.js +33 -0
- package/dist/tools/jui/jui_verify.d.ts +3 -0
- package/dist/tools/jui/jui_verify.js +43 -0
- package/dist/tools/spec/get_binding_rules.d.ts +3 -0
- package/dist/tools/spec/get_binding_rules.js +8 -0
- package/dist/tools/spec/get_data_source.d.ts +3 -0
- package/dist/tools/spec/get_data_source.js +41 -0
- package/dist/tools/spec/get_modifier_order.d.ts +3 -0
- package/dist/tools/spec/get_modifier_order.js +14 -0
- package/dist/tools/spec/get_platform_mapping.d.ts +3 -0
- package/dist/tools/spec/get_platform_mapping.js +20 -0
- package/dist/tools/spec/get_screen_identity.d.ts +3 -0
- package/dist/tools/spec/get_screen_identity.js +22 -0
- package/dist/tools/spec/lookup_attribute.d.ts +3 -0
- package/dist/tools/spec/lookup_attribute.js +14 -0
- package/dist/tools/spec/lookup_component.d.ts +3 -0
- package/dist/tools/spec/lookup_component.js +15 -0
- package/dist/tools/spec/search_components.d.ts +3 -0
- package/dist/tools/spec/search_components.js +14 -0
- package/dist/tools/test/test_artifacts_pull.d.ts +3 -0
- package/dist/tools/test/test_artifacts_pull.js +48 -0
- package/dist/tools/test/test_artifacts_status.d.ts +3 -0
- package/dist/tools/test/test_artifacts_status.js +28 -0
- package/dist/tools/test/test_generate_description.d.ts +3 -0
- package/dist/tools/test/test_generate_description.js +27 -0
- package/dist/tools/test/test_generate_flow.d.ts +3 -0
- package/dist/tools/test/test_generate_flow.js +29 -0
- package/dist/tools/test/test_generate_screen.d.ts +3 -0
- package/dist/tools/test/test_generate_screen.js +29 -0
- package/dist/tools/test/test_mock_generate.d.ts +3 -0
- package/dist/tools/test/test_mock_generate.js +64 -0
- package/dist/tools/test/test_report.d.ts +3 -0
- package/dist/tools/test/test_report.js +26 -0
- package/dist/tools/test/test_validate.d.ts +3 -0
- package/dist/tools/test/test_validate.js +43 -0
- package/docs/design.md +227 -0
- package/install.sh +90 -0
- package/package.json +24 -0
- package/scripts/fetch-definitions.js +101 -0
- package/scripts/fetch-manifest.js +60 -0
- package/src/cli_runner.ts +55 -0
- package/src/config.ts +88 -0
- package/src/data/derived.ts +305 -0
- package/src/index.ts +157 -0
- package/src/spec_loader.ts +591 -0
- package/src/tools/api/list_api_models.ts +44 -0
- package/src/tools/api/list_api_specs.ts +53 -0
- package/src/tools/api/preview_api_model_sync.ts +41 -0
- package/src/tools/context/get_project_config.ts +27 -0
- package/src/tools/context/list_component_specs.ts +44 -0
- package/src/tools/context/list_layouts.ts +131 -0
- package/src/tools/context/list_screen_specs.ts +46 -0
- package/src/tools/context/read_layout_file.ts +35 -0
- package/src/tools/context/read_spec_file.ts +38 -0
- package/src/tools/doc/doc_generate_component.ts +43 -0
- package/src/tools/doc/doc_generate_html.ts +40 -0
- package/src/tools/doc/doc_generate_spec.ts +43 -0
- package/src/tools/doc/doc_init_component.ts +32 -0
- package/src/tools/doc/doc_init_spec.ts +37 -0
- package/src/tools/doc/doc_rules_init.ts +29 -0
- package/src/tools/doc/doc_rules_show.ts +27 -0
- package/src/tools/doc/doc_validate_component.ts +47 -0
- package/src/tools/doc/doc_validate_spec.ts +48 -0
- package/src/tools/jui/jui_build.ts +31 -0
- package/src/tools/jui/jui_generate_converter.ts +41 -0
- package/src/tools/jui/jui_generate_project.ts +37 -0
- package/src/tools/jui/jui_generate_screen.ts +28 -0
- package/src/tools/jui/jui_init.ts +38 -0
- package/src/tools/jui/jui_migrate_layouts.ts +29 -0
- package/src/tools/jui/jui_sync_tool.ts +33 -0
- package/src/tools/jui/jui_verify.ts +43 -0
- package/src/tools/spec/get_binding_rules.ts +16 -0
- package/src/tools/spec/get_data_source.ts +53 -0
- package/src/tools/spec/get_modifier_order.ts +22 -0
- package/src/tools/spec/get_platform_mapping.ts +28 -0
- package/src/tools/spec/get_screen_identity.ts +31 -0
- package/src/tools/spec/lookup_attribute.ts +22 -0
- package/src/tools/spec/lookup_component.ts +23 -0
- package/src/tools/spec/search_components.ts +22 -0
- package/src/tools/test/test_artifacts_pull.ts +47 -0
- package/src/tools/test/test_artifacts_status.ts +34 -0
- package/src/tools/test/test_generate_description.ts +33 -0
- package/src/tools/test/test_generate_flow.ts +33 -0
- package/src/tools/test/test_generate_screen.ts +33 -0
- package/src/tools/test/test_mock_generate.ts +58 -0
- package/src/tools/test/test_report.ts +32 -0
- package/src/tools/test/test_validate.ts +43 -0
- package/tests/__snapshots__/derived.test.ts.snap +279 -0
- package/tests/bundled_snapshot.test.ts +155 -0
- package/tests/cli_runner.test.ts +120 -0
- package/tests/config.test.ts +212 -0
- package/tests/derived.test.ts +114 -0
- package/tests/helpers.ts +272 -0
- package/tests/setup.ts +27 -0
- package/tests/snapshot_completeness.test.ts +152 -0
- package/tests/spec_loader.test.ts +488 -0
- package/tests/tools_cli.test.ts +687 -0
- package/tests/tools_context.test.ts +274 -0
- package/tests/tools_spec.test.ts +225 -0
- package/tests/tools_test.test.ts +284 -0
- package/tsconfig.json +15 -0
- package/uninstall.sh +46 -0
- 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
|
+
}
|