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,137 @@
1
+ {
2
+ "version": 1,
3
+ "title": "JsonUI attribute semantics — observable cross-platform contract",
4
+ "notes": "Hand-authored canonical rulings (semantic-drift track 33, established 2026-08-03). Each adjudicated attribute-semantics ruling is declared ONCE here; the 'observable' block states the cross-platform effect expectation that `jui conformance gate --cross-effect` verifies mechanically against every platform's control-diff verdicts (expectation values: 'uniformly-inert' = the fixture must render identically to its control on every in-scope platform; 'uniformly-active' = it must differ on every in-scope platform). Toolchain code comments must POINT here instead of restating the rule — comment canon rots (the border ruling reversed direction twice within 48 hours while living as comments in three toolchains). Canonical choices are design decisions, not majority votes of existing implementations. Add one entry per ruling as the cross-effect adjudication queue is consumed.",
5
+ "semantics": {
6
+ "border": {
7
+ "widthAlone": "no-draw",
8
+ "styleAlone": "no-draw",
9
+ "ruling": "A border is drawn only when BOTH borderWidth and borderColor are declared; borderWidth alone renders nothing on every platform — there is no default border color (2026-08-03 user ruling; supersedes the transient gray-default direction of d2c8628, unified across toolchains in 3e87b96). borderStyle alone likewise renders nothing: it styles a border the width+color pair requests, it never summons one (2026-08-03 user ruling #2). NOTE: TextField.borderStyle is a DIFFERENT attribute (UIKit text-field chrome: roundedRect/line/bezel/none) and is outside this entry.",
10
+ "observable": {
11
+ "common/borderWidth__static": "uniformly-inert",
12
+ "common/borderStyle__solid": "uniformly-inert",
13
+ "common/borderStyle__dashed": "uniformly-inert",
14
+ "common/borderStyle__dotted": "uniformly-inert"
15
+ }
16
+ },
17
+ "image": {
18
+ "defaultContentMode": "fit",
19
+ "fill": "stretch",
20
+ "ruling": "2026-08-03 user rulings: (a) the default contentMode is fit on every platform — web's implicit cover default was drift (fixed in rjui, 22d96b4); (b) fill is the stretch (scaleToFill synonym) — AspectFill is the crop. All three platforms now implement the full mapping including the positional modes (unscaled + aligned + cropped; sjui/kjui/SwiftJsonUI/KotlinJsonUI, 33 wave). Observables: fit-valued fixtures equal the default rendering (uniformly inert); fill/scaleToFill differ from it everywhere (uniformly active). Verified on the 33 round-3 measurement.",
21
+ "observable": {
22
+ "Image/contentMode__fit": "uniformly-inert",
23
+ "Image/contentMode__aspectfit": "uniformly-inert",
24
+ "NetworkImage/contentMode__fit": "uniformly-inert",
25
+ "NetworkImage/contentMode__aspectfit": "uniformly-inert",
26
+ "Image/contentMode__fill": "uniformly-active",
27
+ "Image/contentMode__scaletofill": "uniformly-active"
28
+ }
29
+ },
30
+ "textView": {
31
+ "flexibleHeight": "declared height is the growth floor",
32
+ "ruling": "2026-08-03 user ruling (31 handoff): with flexible enabled, the declared height is the floor the view GROWS from — it never shrinks below it to follow smaller content. android's behavior is canonical; ios (both paths) shrank with content and must adopt the floor formula min = minHeight ?? height ?? default (landed in the 33 wave: SwiftJsonUI dynamic + sjui codegen, floor formula min = minHeight ?? height). web measures 'has effect' — verify it matches floor semantics before recording it anywhere. No activeness observable: at fixture content size a correct floor renders identically to the control (that is WHY the android control_diff entry was removed in e78af64); the mechanical check belongs to the geometry probe (systematic-detection optimization track).",
33
+ "observable": {}
34
+ },
35
+ "size": {
36
+ "maxBoundsClampFill": "matchParent is clamped by maxWidth/maxHeight",
37
+ "ruling": "2026-08-03 user ruling (31 handoff): matchParent + maxWidth/maxHeight resolves to min(parent extent, bound) — the bound clamps the fill. Generated code is canonical (kjui emits the constraint before the fill since 847fb56); KJUI dynamic ordered fillMaxWidth before widthIn, ignoring the bound entirely — reordered in the 33 wave (bounds precede a fill, stay after an explicit size). The composite clamp fixtures below are the observable: a clamping platform renders visibly narrower/shorter than its filled control (active); ignoring the bound renders identically (inert). Companion precedence (2026-08-03 campaign adjudication): an EXPLICIT numeric dimension pins the constraints and its bound stays inert (kjui codegen 847fb56, ios 35efc2b, rjui skips max-* under an explicit size); bounds in turn clamp a FILL. Order: explicit > bounds > fill.",
38
+ "observable": {
39
+ "common/maxWidth__fill_clamp": "uniformly-active",
40
+ "common/maxHeight__fill_clamp": "uniformly-active"
41
+ },
42
+ "explicitOverBounds": "an explicit numeric dimension wins over its min/max bound"
43
+ },
44
+ "networkImage": {
45
+ "noSrc": "defaultImage",
46
+ "ruling": "2026-08-03 user ruling: with no src, NetworkImage displays defaultImage; while loading it displays the first of hint/placeholder/loadingImage; on error it displays errorImage, falling back to defaultImage. Implemented on all platforms in the 33 wave (KotlinJsonUI defaultImage read + Coil fallback chain; ios placeholder no longer collapses over defaultImage; rjui defaultImage prop + template state machine). State-image fixtures use the DISTINCT conformance_sample_alt asset, so a hijacked no-src state renders visibly — the uniformly-inert observables verify the chain end-to-end.",
47
+ "observable": {
48
+ "NetworkImage/placeholder__static": "uniformly-inert",
49
+ "NetworkImage/hint__static": "uniformly-inert",
50
+ "NetworkImage/errorImage__static": "uniformly-inert",
51
+ "NetworkImage/loadingImage__static": "uniformly-inert"
52
+ }
53
+ },
54
+ "margins": {
55
+ "offsetModel": "ZStack/overlay child margins are full-margin offsets; an explicit center declaration disables that axis margin; margins arrays and start/endMargin apply as padding",
56
+ "ruling": "2026-08-03 campaign adjudication (31/33 rounds, measured bbox evidence in the 33 geometry diagnosis table): an overlay (ZStack) child offsets by the FULL margin on each declared edge; declaring centerInParent/centerHorizontal/centerVertical disables the margin on the centered axis; the margins array and the RTL start/endMargin spellings resolve as padding-equivalent insets. Divergent rows are ledgered per-fixture with their measured deltas; the mechanical closure is the geometry probe (systematic-detection optimization track, de-scoped from the completion gate). No activeness observable: position deltas are invisible to fixture-vs-control activeness when every platform moves.",
57
+ "observable": {}
58
+ },
59
+ "gravityDefaults": {
60
+ "defaultGravity": "top|start",
61
+ "ruling": "2026-08-03 campaign adjudication: the default content gravity is top|start on every container (CollectionStack pins Top; SafeAreaView spacer layouts anchor start). A platform centering by default is the deviant. No activeness observable — default-vs-default renders identically; the geometry probe closes it mechanically.",
62
+ "observable": {}
63
+ },
64
+ "shadow": {
65
+ "stringPipe": "five-field strict",
66
+ "ruling": "2026-08-03 campaign adjudication (31 wave, unified in 3e87b96/ca65fb6): the shadow string pipe format is exactly five fields (color|offsetX|offsetY|blur|opacity); any other count renders NOTHING on every platform — no partial parsing, no platform default. The conformance fixtures ship the valid five-field pipe; a malformed-count probe belongs to the validator, not the render layer.",
67
+ "observable": {}
68
+ },
69
+ "gradient": {
70
+ "defaultDirection": "vertical",
71
+ "ruling": "2026-08-03 campaign adjudication (31 gradient family): the default gradient direction is vertical (top to bottom) on every platform; an explicit \"vertical\" therefore equals the default rendering.",
72
+ "observable": {
73
+ "GradientView/gradientDirection__vertical": "uniformly-inert"
74
+ }
75
+ },
76
+ "skinIdioms": {
77
+ "demarcation": "control chrome may differ per platform; the DIFFERENCE ITSELF is contract-pinned",
78
+ "ruling": "2026-08-03 campaign adjudication: platform-owned control chrome is a legitimate difference, but only in its adjudicated SHAPE — the observable pins each accepted divergence as an expected-difference pattern, so both disappearing and mutating fail the gate. Members: Blur effectStyle (android material variants differ visibly, ios materials render alike on flat surfaces per the 31 saturation analysis, web CSS blur is single-variant), TextField.borderStyle (UIKit field chrome — each platform draws its own; web renders none for the UIKit-only bezel variants), TabView.showLabels (ios hides tab labels, android/web keep the labeled bar). The tabview glyph-source family is the codegen-side counterpart, pinned permanently in codegen_parity.json (2026-08-03 user ruling).",
79
+ "observable": {
80
+ "Blur/effectStyle__extralight": {
81
+ "ios": "inert",
82
+ "android": "active",
83
+ "web": "inert"
84
+ },
85
+ "Blur/effectStyle__light": {
86
+ "ios": "inert",
87
+ "android": "active",
88
+ "web": "inert"
89
+ },
90
+ "TextField/borderStyle__bezel": {
91
+ "ios": "inert",
92
+ "android": "active",
93
+ "web": "inert"
94
+ },
95
+ "TextField/borderStyle__bezel_2": {
96
+ "ios": "inert",
97
+ "android": "active",
98
+ "web": "inert"
99
+ },
100
+ "TextField/borderStyle__line": {
101
+ "ios": "inert",
102
+ "android": "active",
103
+ "web": "active"
104
+ },
105
+ "TextField/borderStyle__line_2": {
106
+ "ios": "inert",
107
+ "android": "active",
108
+ "web": "active"
109
+ },
110
+ "TextField/borderStyle__roundedrect": {
111
+ "ios": "active",
112
+ "android": "active",
113
+ "web": "inert"
114
+ },
115
+ "TextField/borderStyle__roundedrect_2": {
116
+ "ios": "active",
117
+ "android": "active",
118
+ "web": "inert"
119
+ },
120
+ "TabView/showLabels__true": {
121
+ "ios": "active",
122
+ "android": "inert",
123
+ "web": "inert"
124
+ }
125
+ }
126
+ },
127
+ "progressValue": {
128
+ "canonical": "progress",
129
+ "legacy": "value",
130
+ "undeclaredDefault": "0 (empty bar) on a determinate ProgressView; kjui renders indeterminate chrome when nothing is declared",
131
+ "ruling": "2026-08-04 campaign adjudication (closing the last unreviewed cross-effect row): `progress` is the canonical determinate value attribute (declared 0..1 in attribute_definitions); `value` is the undeclared legacy runtime spelling every reader keeps as fallback, canonical-first. Undeclared progress must NOT default to a nonzero fill — ios (dynamic + sjui codegen) defaulted to 0.5, which made the control half-full and the progress:0.5 fixture measured inert; android (both paths) read only the legacy `value` and ignored the canonical attribute entirely. Fixed canonical-first on every path (SwiftJsonUI ProgressConverter, sjui progress_converter.rb, KotlinJsonUI DynamicProgressComponent, kjui progress_component.rb, rjui progress_converter.rb). The observable pins the repair: a half-full bar must differ from its undeclared control on every platform.",
132
+ "observable": {
133
+ "Progress/progress__static": "uniformly-active"
134
+ }
135
+ }
136
+ }
137
+ }
@@ -0,0 +1,115 @@
1
+ {
2
+ "version": 1,
3
+ "title": "JsonUI binding resolution semantics (canonical)",
4
+ "notes": "Hand-authored canonical source (renderer SSoT track 15, declared 2026-07-24). This file IS the SSoT for how '@{...}' expressions resolve; runtimes and code generators conform to it, not the other way around. Companion test vectors: shared/core/binding_vectors.json. Canonical choices are design decisions, not majority votes of existing implementations.",
5
+ "syntax": {
6
+ "expression": "@{ inner }",
7
+ "inner": "path [ '??' default ] | '!' path (boolean value context only)",
8
+ "path": "identifier segments joined by '.', with optional bracket index: profile.name, items[0].title",
9
+ "whitespace": "Whitespace around 'inner', around '??', and around the default literal is insignificant."
10
+ },
11
+ "contexts": {
12
+ "text": {
13
+ "description": "Mixed-text interpolation. A string attribute may contain any number of '@{...}' occurrences with surrounding literal text; each occurrence is replaced by the stringified resolved value.",
14
+ "interpolation": true,
15
+ "features": ["path", "default"],
16
+ "unresolved": "emptyString"
17
+ },
18
+ "value": {
19
+ "description": "Whole-value binding for typed attributes (number / bool / color / enum / src / visibility / ...). The whole attribute value must be exactly one '@{...}' expression; no surrounding text.",
20
+ "interpolation": false,
21
+ "features": ["path", "default", "negationIfBool"],
22
+ "unresolved": "attributeDefault"
23
+ },
24
+ "embedParams": {
25
+ "description": "Embed 'params' leaf values. Leaves are scalar literals or one whole '@{...}' expression resolved against the PARENT screen's data. Intermediate nodes are literal objects; arrays are unsupported (validator error, established by the embed isolated+params track).",
26
+ "interpolation": false,
27
+ "features": ["path"],
28
+ "unresolved": "dropKey"
29
+ },
30
+ "nameRef": {
31
+ "description": "Event / handler name references (onclick etc.). '@{name}' and the bare name are equivalent spellings of a handler NAME; this is name extraction, not value resolution. Path, default, and negation are not applicable.",
32
+ "interpolation": false,
33
+ "features": [],
34
+ "unresolved": "platformEventContract"
35
+ },
36
+ "twoWay": {
37
+ "description": "Two-way bindings (TextField text, Switch isOn, ...). The expression must be a single flat identifier: no dots, no brackets, no '??', no '!'. This keeps write-back and change-handler wiring trivially derivable on every platform.",
38
+ "interpolation": false,
39
+ "features": [],
40
+ "unresolved": "attributeDefault"
41
+ }
42
+ },
43
+ "pathResolution": {
44
+ "order": ["flatKey", "pathTraversal"],
45
+ "rule": "Look up the raw inner string as a flat key in the data map first; if absent, split on '.' and traverse. A data map that literally contains the key 'a.b' therefore shadows the nested path a.b (determinism guarantee).",
46
+ "arrayIndex": {
47
+ "syntax": "name[index] with a non-negative integer literal",
48
+ "rule": "Canonical array access is bracket syntax (items[0].title). Out-of-range or non-array => the path is unresolved (never a crash).",
49
+ "nonCanonical": "Dot-numeric segments (items.0.title) are a frozen UIKit-runtime legacy; not canonical, not to be added elsewhere."
50
+ },
51
+ "intermediateMiss": "A missing or non-object intermediate node makes the whole path unresolved; it must never crash or throw (web: generated user data paths use optional chaining)."
52
+ },
53
+ "defaultOperator": {
54
+ "spelling": "@{path ?? default}",
55
+ "contexts": ["text", "value"],
56
+ "arity": "Exactly one '??' per expression. More than one is a validator error; runtimes split on the FIRST '??' only (the remainder is part of the default literal parse and fails closed to unresolved).",
57
+ "defaultLiteral": {
58
+ "string": "Quoted with double OR single quotes: \"x\" or 'x' (single quotes are canonical-new; historical KJUI accepted only double quotes).",
59
+ "boolean": "true | false",
60
+ "number": "Integer or decimal literal",
61
+ "null": "null => treated as unresolved (falls through to the context's unresolved behavior)"
62
+ },
63
+ "rule": "If the path resolves (including to false, 0, or empty string), the resolved value wins. Only an UNRESOLVED path yields the default. In 'value' context the default is typed (true stays a bool, 42 stays a number)."
64
+ },
65
+ "unresolved": {
66
+ "text": "Empty string per occurrence (plus a debug-level log listing available keys).",
67
+ "value": "The attribute's default value applies (component-defined). Never a crash, never a rendered error.",
68
+ "embedParams": "The key is OMITTED from the resolved params object so the embedded screen's own data-section defaultValue applies.",
69
+ "twoWay": "The attribute's default; write-back targets the declared identifier regardless."
70
+ },
71
+ "fallbackPrecedence": [
72
+ "1. data-section defaultValue (merged into the data map before any binding resolves)",
73
+ "2. inline '??' default (only reached when the key is absent after step 1)",
74
+ "3. context unresolved behavior (emptyString / attributeDefault / dropKey)"
75
+ ],
76
+ "negation": {
77
+ "spelling": "@{!path}",
78
+ "contexts": ["value (boolean attributes only)"],
79
+ "rule": "Resolve the path, coerce to bool (see coercion), then invert. An unresolved path stays unresolved (attribute default applies WITHOUT inversion). Any other context is a validator error; runtimes treat the token as an ordinary (unresolvable) key and fall through to the context's unresolved behavior."
80
+ },
81
+ "coercion": {
82
+ "bool": {
83
+ "accepts": ["bool", "int (non-zero => true)", "string 'true'/'1' => true, 'false'/'0' => false (case-insensitive)"],
84
+ "otherwise": "unresolved"
85
+ },
86
+ "number": {
87
+ "accepts": ["number", "numeric string"],
88
+ "otherwise": "unresolved"
89
+ },
90
+ "string": {
91
+ "accepts": ["any scalar via text stringification rules"],
92
+ "otherwise": "unresolved (objects/arrays do not stringify into value context)"
93
+ }
94
+ },
95
+ "textStringification": {
96
+ "number": "JSON-faithful: integral numbers render with no decimal point ('5', not '5.0'); non-integral render with their decimal digits.",
97
+ "bool": "'true' / 'false'",
98
+ "null": "unresolved (empty string)",
99
+ "objectOrArray": "unresolved (empty string) — never a platform debug dump"
100
+ },
101
+ "embedParamsTypes": "Resolved leaves keep native JSON types (bool stays bool, 5 stays integer 5). Unresolved leaves are dropped (see unresolved.embedParams).",
102
+ "collectionCellScope": {
103
+ "guaranteed": "The item object's own fields, flat, plus the reserved key 'index' (0-based).",
104
+ "parentData": "NOT guaranteed. Depending on parent-screen keys inside a cell is non-portable (validator warning).",
105
+ "itemPrefix": "'@{item.xxx}' is NOT canonical; scaffolds must emit flat field names."
106
+ },
107
+ "validatorRules": [
108
+ {"id": "binding-double-default", "severity": "error", "context": "any", "rule": "More than one '??' in a single binding expression."},
109
+ {"id": "binding-two-way-complex", "severity": "error", "context": "twoWay", "rule": "Two-way binding expression is not a single flat identifier (contains '.', '[', '??', or '!')."},
110
+ {"id": "binding-negation-context", "severity": "error", "context": "text|embedParams|nameRef|twoWay", "rule": "'!' negation used outside a boolean value context."},
111
+ {"id": "binding-default-in-params", "severity": "error", "context": "embedParams", "rule": "'??' default used in an embed params leaf (defaults belong to the embedded screen's data section)."},
112
+ {"id": "binding-params-array", "severity": "error", "context": "embedParams", "rule": "Array anywhere inside params (pre-existing rule from the embed isolated+params track; restated here)."},
113
+ {"id": "binding-cell-parent-scope", "severity": "warning", "context": "collectionCell", "rule": "Cell layout binds a key that is only present in the parent screen's data."}
114
+ ]
115
+ }