jui-tools-mcp-server 2.12.0 → 2.12.2
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.
|
@@ -1,6 +1,32 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_comment": "Unified attribute definitions for JsonUI (Swift/Kotlin/React). Binding format: @{propertyName}. When `type` includes \"binding\", an optional `binding_direction` field declares how the MCP should label the binding: \"two-way\" means the component writes back to the binding (e.g. TextField.text); omit the field (or set \"read-only\") for values that only flow view ← model. Optional extended fields per attribute: `aliases` (array of alternate names treated as synonyms for this canonical attribute; validator expands these at load time), `deprecated` (boolean; validator emits a warning when the attribute is used), `deprecation_note` (string appended to the warning to explain the replacement or limitation).",
|
|
3
3
|
"common": {
|
|
4
|
+
"glass": {
|
|
5
|
+
"type": ["boolean", "object"],
|
|
6
|
+
"platform": "swift",
|
|
7
|
+
"mode": ["uikit", "swiftui"],
|
|
8
|
+
"description": "Liquid Glass. true for the default treatment, or an object {style: regular|clear|identity, tint: color, interactive: bool, shape: capsule|rect|circle|rounded(N)}. Declared on common rather than per component because ios.md names View, Button, TextField and Label followed by 'etc' - an open list, and a per-component declaration would make the set of components the acceptance population, so every reading of 'etc' becomes a gap. mode carries BOTH uikit and swiftui because the attribute has two implementations, .glassEffect() on SwiftUI and UIGlassEffect on UIKit; this is the first declaration in the file to pair those two, though five declarations already use an array for mode and both readers accept one (kjui Array(attr_def['mode']), jui isinstance(raw, list)). Leaving mode off would not have meant 'both' - an absent mode means NO restriction at all (kjui attribute_validator_core.rb mode_compatible? returns true when the key is missing), which would let the attribute read as available in modes it has no implementation for.",
|
|
9
|
+
"properties": {
|
|
10
|
+
"style": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"enum": ["regular", "clear", "identity"],
|
|
13
|
+
"description": "Glass appearance. SwiftUI has all three (SwiftUICore.Glass.regular / .clear / .identity). UIKit has only two: UIGlassEffectStyle is Regular and Clear, so `identity` is SwiftUI-only and the UIKit path applies no effect for it rather than resolving a member that does not exist."
|
|
14
|
+
},
|
|
15
|
+
"tint": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"description": "Tint colour applied to the glass. SwiftUI Glass.tint(_ color: Color?), UIKit UIGlassEffect.tintColor."
|
|
18
|
+
},
|
|
19
|
+
"interactive": {
|
|
20
|
+
"type": "boolean",
|
|
21
|
+
"description": "Whether the glass reacts to touch. SwiftUI Glass.interactive(_:), UIKit UIGlassEffect.interactive. An explicit false is a value the author wrote and is distinct from the key being absent - the SwiftUI Glass type does not preserve that distinction (measured: Glass.regular == Glass.regular.interactive(false)), so the implementation records the author's choice before calling the SDK."
|
|
22
|
+
},
|
|
23
|
+
"shape": {
|
|
24
|
+
"type": "string",
|
|
25
|
+
"enum": ["capsule", "circle", "rect"],
|
|
26
|
+
"description": "Shape the glass is clipped to. The enum is the complete set of FIXED spellings; `rounded(N)` is additionally accepted and cannot be an enum member because N varies, so a validator checks the enum first and the `rounded(` prefix second. SwiftUI takes a shape argument (.glassEffect(_:in:)); UIKit does not - UIGlassEffect is a UIVisualEffect subclass with no shape parameter, so there the shape comes from the host view. Transcribed from the implementation that resolves them (SJUIGlass.swift knownShapeSpellings), not from this file's own prose - the two disagreed once already (`rectangle` was accepted by the implementation and named by nobody)."
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
},
|
|
4
30
|
"_comment": "Common attributes shared across all components",
|
|
5
31
|
"$jui": {
|
|
6
32
|
"type": "object",
|
|
@@ -1669,9 +1695,13 @@
|
|
|
1669
1695
|
"phone",
|
|
1670
1696
|
"url",
|
|
1671
1697
|
"password",
|
|
1672
|
-
"decimal"
|
|
1698
|
+
"decimal",
|
|
1699
|
+
"signedDecimal",
|
|
1700
|
+
"date",
|
|
1701
|
+
"time",
|
|
1702
|
+
"datetime"
|
|
1673
1703
|
],
|
|
1674
|
-
"description": "Input type (includes 'allphabet' typo for backward compatibility)"
|
|
1704
|
+
"description": "Input type (includes 'allphabet' typo for backward compatibility). Not every face can express every value, and the degradations are declared here so that an implementer does not rediscover them one at a time. Android, codegen: all four of the values added in 1.8.8x land natively (Compose 1.12 KeyboardType.Date / Time / DateTime / DecimalSigned - note the order: Signed is a suffix in Compose, so the member is DecimalSigned, modifier last, while the value declared here is signedDecimal, modifier first. Each reads as a typo of the other. The plan for this work named the member SignedDecimal, which does not exist, and this description repeated it until 2026-09-14. Both spellings therefore appear in this text on purpose, here and in the four vendored tables that copy it, so grepping for SignedDecimal finds hits that are the correction rather than the defect - the hit count is not a measure of whether this was fixed). Android, dynamic: the runtime renderer has no case for any of the four - DynamicTextFieldComponent and DynamicTextViewComponent both fall to `else -> KeyboardType.Text` - so they degrade to plain text there. The two Android faces differ, and 'Android supports it' is true only of the generated one. The floor is codegen's alone: the generated Kotlin NAMES the member, so Compose below 1.12 would be a COMPILE error there rather than a degradation - ui-text 1.11 has ten KeyboardType members and none of these four, and 1.12.0 is the first with Date, Time, DateTime and DecimalSigned. Dynamic needs nothing new, because it never names them: it falls to KeyboardType.Text, which 1.11 already has. As of 1.8.80 the Compose emitter carries that floor, and it is DECLARED rather than detected: a project that declares nothing keeps the real members, and a project that sets compose_version below 1.12.0 in kjui.config.json gets these four - and only these four - degraded to KeyboardType.Text, on TextView as well as TextField because both read one table. Nothing is detected, because this generator never sees the consumer's dependency graph; a project on an older Compose that declares nothing therefore still gets a build failure rather than a silent keyboard change, which is the failure a reader can act on. The comparison is Gem::Version and never String, because '1.9.0' sorts ABOVE '1.12.0' as text and that is the direction that ships the build failure. web, TextField: date, time and datetime are not inputmode values; they change the element type to date, time and datetime-local, which is the path SelectBox's datepicker already takes. web, TextView: a textarea has no type attribute at all, so those three degrade to plain text there and the declared value cannot be honoured. web, inputmode: signedDecimal has no inputmode of its own and must share decimal with the decimal value above, so on web the two are INDISTINGUISHABLE. web, type: number and decimal already collapse to type=number, and signedDecimal joins them. iOS: UIKeyboardType has no member for the date family, so they fall back to .default, and signedDecimal to .numbersAndPunctuation."
|
|
1675
1705
|
},
|
|
1676
1706
|
"inputType": {
|
|
1677
1707
|
"type": "string",
|
|
@@ -1782,7 +1812,7 @@
|
|
|
1782
1812
|
},
|
|
1783
1813
|
"applyLiquidGlass": {
|
|
1784
1814
|
"type": "boolean",
|
|
1785
|
-
"description": "Apply liquid glass effect",
|
|
1815
|
+
"description": "Apply liquid glass effect. SUPERSEDED by the common attribute 'glass' as of 2026-09-14. The fold from this spelling to glass belongs to the normalizer and IS NOT IMPLEMENTED YET, so both spellings are live: this one stays TextField-only, UIKit-only and backed by UIBlurEffect, while glass is declared on common for SwiftUI and UIKit. Nothing breaks while both exist - they do not overlap in platform, mode or component - but a layout should be written against glass.",
|
|
1786
1816
|
"platform": "swift",
|
|
1787
1817
|
"mode": "uikit"
|
|
1788
1818
|
},
|
|
@@ -1794,7 +1824,7 @@
|
|
|
1794
1824
|
},
|
|
1795
1825
|
"glassEffectStyle": {
|
|
1796
1826
|
"type": "string",
|
|
1797
|
-
"description": "Glass effect style",
|
|
1827
|
+
"description": "Glass effect style. SUPERSEDED by glass.style as of 2026-09-14. As with applyLiquidGlass, the normalizer fold is NOT IMPLEMENTED YET and both spellings are live.",
|
|
1798
1828
|
"platform": "swift",
|
|
1799
1829
|
"mode": "uikit"
|
|
1800
1830
|
},
|
|
@@ -2117,9 +2147,13 @@
|
|
|
2117
2147
|
"phone",
|
|
2118
2148
|
"url",
|
|
2119
2149
|
"password",
|
|
2120
|
-
"decimal"
|
|
2150
|
+
"decimal",
|
|
2151
|
+
"signedDecimal",
|
|
2152
|
+
"date",
|
|
2153
|
+
"time",
|
|
2154
|
+
"datetime"
|
|
2121
2155
|
],
|
|
2122
|
-
"description": "Input type (includes 'allphabet' typo for backward compatibility)"
|
|
2156
|
+
"description": "Input type (includes 'allphabet' typo for backward compatibility). Not every face can express every value, and the degradations are declared here so that an implementer does not rediscover them one at a time. Android, codegen: all four of the values added in 1.8.8x land natively (Compose 1.12 KeyboardType.Date / Time / DateTime / DecimalSigned - note the order: Signed is a suffix in Compose, so the member is DecimalSigned, modifier last, while the value declared here is signedDecimal, modifier first. Each reads as a typo of the other. The plan for this work named the member SignedDecimal, which does not exist, and this description repeated it until 2026-09-14. Both spellings therefore appear in this text on purpose, here and in the four vendored tables that copy it, so grepping for SignedDecimal finds hits that are the correction rather than the defect - the hit count is not a measure of whether this was fixed). Android, dynamic: the runtime renderer has no case for any of the four - DynamicTextFieldComponent and DynamicTextViewComponent both fall to `else -> KeyboardType.Text` - so they degrade to plain text there. The two Android faces differ, and 'Android supports it' is true only of the generated one. The floor is codegen's alone: the generated Kotlin NAMES the member, so Compose below 1.12 would be a COMPILE error there rather than a degradation - ui-text 1.11 has ten KeyboardType members and none of these four, and 1.12.0 is the first with Date, Time, DateTime and DecimalSigned. Dynamic needs nothing new, because it never names them: it falls to KeyboardType.Text, which 1.11 already has. As of 1.8.80 the Compose emitter carries that floor, and it is DECLARED rather than detected: a project that declares nothing keeps the real members, and a project that sets compose_version below 1.12.0 in kjui.config.json gets these four - and only these four - degraded to KeyboardType.Text, on TextView as well as TextField because both read one table. Nothing is detected, because this generator never sees the consumer's dependency graph; a project on an older Compose that declares nothing therefore still gets a build failure rather than a silent keyboard change, which is the failure a reader can act on. The comparison is Gem::Version and never String, because '1.9.0' sorts ABOVE '1.12.0' as text and that is the direction that ships the build failure. web, TextField: date, time and datetime are not inputmode values; they change the element type to date, time and datetime-local, which is the path SelectBox's datepicker already takes. web, TextView: a textarea has no type attribute at all, so those three degrade to plain text there and the declared value cannot be honoured. web, inputmode: signedDecimal has no inputmode of its own and must share decimal with the decimal value above, so on web the two are INDISTINGUISHABLE. web, type: number and decimal already collapse to type=number, and signedDecimal joins them. iOS: UIKeyboardType has no member for the date family, so they fall back to .default, and signedDecimal to .numbersAndPunctuation."
|
|
2123
2157
|
},
|
|
2124
2158
|
"returnKeyType": {
|
|
2125
2159
|
"type": "string",
|
|
@@ -156,9 +156,11 @@
|
|
|
156
156
|
"observable": {}
|
|
157
157
|
},
|
|
158
158
|
"boundSize": {
|
|
159
|
+
"readBy": "The three emitters carry unitModel in their output strings: sjui_tools/lib/swiftui/views/frame_helper.rb (points), kjui_tools/lib/compose/helpers/modifier_builder.rb process_dimension (.dp), rjui_tools/lib/react/converters/base_converter.rb (px template literal); pinned by sjui_tools/spec/swiftui/views/frame_helper_spec.rb, kjui_tools/spec/compose/helpers/modifier_builder_spec.rb and rjui_tools/spec/react/converters/base_converter_spec.rb. `observable` is {} — the contract measures nothing for this topic. Declared 2026-09-10.",
|
|
159
160
|
"unitModel": "a bound size attribute carries a runtime number in layout units: dp on android, points on ios, px on web",
|
|
160
161
|
"ruling": "2026-08-04 orchestrator adjudication (36, consumer field report): all six size attributes (width, height, minWidth, maxWidth, minHeight, maxHeight) declare \"binding\" in attribute_definitions, so a bound value MUST be honoured on every platform. kjui resolves it to `.height(data.v.dp)` (process_dimension) and sjui to `.frame(height: data.v)` (frame_helper is_binding?); rjui dropped it silently because the value reached the Tailwind arbitrary-value mapper, which returns '' for any string that is not a CSS unit — the declaration produced no class and no inline style, and jui build stayed warning-free. rjui now routes a bound dimension to the inline style as a px template literal, and treats it as an EXPLICIT size for the shrink and explicit-over-bounds rules above. No activeness observable: a bound fixture renders whatever the host viewmodel supplies, so fixture-vs-control cannot separate honouring from ignoring.",
|
|
161
|
-
"observable": {}
|
|
162
|
+
"observable": {},
|
|
163
|
+
"observableNote": "Empty by design, not by omission: a bound size renders whatever the host view-model supplies, so a fixture-vs-control comparison cannot separate honouring the binding from ignoring it (see ruling). The emitted strings stand in — the specs named in readBy pin points / .dp / px per face. Declared 2026-09-10."
|
|
162
164
|
},
|
|
163
165
|
"distribution": {
|
|
164
166
|
"sizeValues": [
|
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
"unresolved": "dropKey"
|
|
29
29
|
},
|
|
30
30
|
"nameRef": {
|
|
31
|
-
"
|
|
31
|
+
"readBy": "Hand-mirrored per face, measured 2026-09-10 and pinned per face: sjui_tools/lib/swiftui/views/base_view_converter.rb (onClick = binding, onclick = bare selector name) pinned by sjui_tools/spec/swiftui/views/handler_name_spelling_spec.rb; kjui_tools/lib/compose/helpers/modifier_builder.rb (camelCase events require the binding form) pinned by kjui_tools/spec/compose/components/handler_name_spelling_spec.rb; rjui_tools/lib/react/converters/base_converter.rb (onClick = binding only, onclick = bare only) pinned by rjui_tools/spec/react/converters/handler_name_spelling_spec.rb. 0 vectors: this context is code generation, not runtime resolution. Known gap, declared here and not a rule: a bare camelCase handler compiles to a no-op with no build-time report (an inline ERROR comment on kjui and rjui, nothing on sjui). A validator rule would need scoping to the declared event attributes first — consumer layouts also use camelCase keys as custom-component props (measured 2026-09-10).",
|
|
32
|
+
"description": "Event / handler NAME references. The camelCase event attributes (onClick, onAppear, onLongPress, …) take the binding form only: '@{name}' names the handler, and a bare string is NOT a handler on any face (measured 2026-09-10: kjui and rjui emit an inline ERROR comment and no call; sjui emits an empty action). The lowercase `onclick` is the UIKit selector legacy and takes the bare name only. Name extraction, not value resolution: path, default and negation are not applicable. Until 2026-09-10 this entry said the two spellings were equivalent; no generator ever implemented that, and the sentence was corrected to what they do.",
|
|
32
33
|
"interpolation": false,
|
|
33
34
|
"features": [],
|
|
34
35
|
"unresolved": "platformEventContract"
|
|
@@ -44,9 +45,12 @@
|
|
|
44
45
|
"order": ["flatKey", "pathTraversal"],
|
|
45
46
|
"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
47
|
"arrayIndex": {
|
|
48
|
+
"vectors": ["text_bracket_index", "text_bracket_index_scalar", "text_bracket_out_of_range", "text_bracket_on_non_array", "value_number_bracket_index", "invalid_twoway_bracket"],
|
|
49
|
+
"readBy": "Executed by the vendored binding_vectors.json in SwiftJsonUI and KotlinJsonUI test suites (ids in `vectors`; held to exist by jui_tools/tests/test_platform_semantics_is_read_by_the_resolver.py). Hand-mirrored in Ruby, no loader (no Ruby tool reads this file by design): kjui_tools/lib/core/binding_validator_core.rb (bracket-index grammar; sjui_tools/lib/core/binding_validator_core.rb and rjui_tools/lib/core/binding_validator_core.rb carry the same text), pinned by kjui_tools/spec/compose/helpers/binding_expression_spec.rb (items[0], items[10].label). Declared 2026-09-10; the link is prose, so a canon edit does not turn that spec red.",
|
|
47
50
|
"syntax": "name[index] with a non-negative integer literal",
|
|
48
51
|
"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."
|
|
52
|
+
"nonCanonical": "Dot-numeric segments (items.0.title) are a frozen UIKit-runtime legacy; not canonical, not to be added elsewhere.",
|
|
53
|
+
"nonCanonicalEnforcement": "None, deliberately. No validator rule rejects a dot-numeric segment (measured 2026-09-10: the validators emit five rule ids and none covers it), and none will be added — rejecting would break frozen UIKit layouts that still carry the form. The sentence above binds authors and generators; it is not a gate."
|
|
50
54
|
},
|
|
51
55
|
"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
56
|
},
|
|
@@ -55,6 +59,8 @@
|
|
|
55
59
|
"contexts": ["text", "value"],
|
|
56
60
|
"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
61
|
"defaultLiteral": {
|
|
62
|
+
"vectors": ["text_default_double_quotes", "text_default_single_quotes", "text_default_number", "text_default_bool", "text_default_null_is_unresolved", "text_default_empty_quotes", "value_bool_default_typed", "value_number_default_typed", "value_string_default_quotes"],
|
|
63
|
+
"readBy": "Executed by the vendored binding_vectors.json in SwiftJsonUI and KotlinJsonUI test suites (ids in `vectors`). Hand-mirrored in Ruby: kjui_tools/lib/compose/helpers/binding_expression.rb (double- or single-quoted string, true/false, number), pinned by kjui_tools/spec/compose/helpers/binding_expression_spec.rb 'parses a single-quoted string default (canonical-new spelling)'. Declared 2026-09-10; the link is prose.",
|
|
58
64
|
"string": "Quoted with double OR single quotes: \"x\" or 'x' (single quotes are canonical-new; historical KJUI accepted only double quotes).",
|
|
59
65
|
"boolean": "true | false",
|
|
60
66
|
"number": "Integer or decimal literal",
|
|
@@ -96,12 +102,18 @@
|
|
|
96
102
|
"number": "JSON-faithful: integral numbers render with no decimal point ('5', not '5.0'); non-integral render with their decimal digits.",
|
|
97
103
|
"bool": "'true' / 'false'",
|
|
98
104
|
"null": "unresolved (empty string)",
|
|
105
|
+
"objectOrArrayVectors": ["text_object_value_renders_empty", "text_array_value_renders_empty"],
|
|
106
|
+
"objectOrArrayVectorsNote": "Both halves have a vector since 2026-09-10: the array case (text_array_value_renders_empty) was added that day and executed by the vendored copy on KotlinJsonUI (allRuntimeVectorCasesPass, local run); SwiftJsonUI runs it on its next vendoring of binding_vectors.json.",
|
|
107
|
+
"objectOrArrayReadBy": "Hand-mirrored in Ruby: rjui_tools/lib/react/converters/base_converter.rb #convert_text_binding (text sinks emit a stringifying template literal; unresolved renders \"\"), pinned by rjui_tools/spec/react/converters/base_converter_spec.rb 'treats a null default as unresolved (empty string)'. Declared 2026-09-10; the link is prose.",
|
|
99
108
|
"objectOrArray": "unresolved (empty string) — never a platform debug dump"
|
|
100
109
|
},
|
|
110
|
+
"embedParamsTypesVectors": ["params_literal_types_preserved", "params_bound_types_preserved", "params_unresolved_leaf_dropped", "params_nested_unresolved_leaf_dropped"],
|
|
111
|
+
"embedParamsTypesReadBy": "Executed by the vendored binding_vectors.json in SwiftJsonUI and KotlinJsonUI (ids in embedParamsTypesVectors); in this repository the validators only exempt Embed params from attribute typing (kjui_tools/lib/core/binding_validator_core.rb embed_params_attr?, mirrored in sjui_tools and rjui_tools). Declared 2026-09-10.",
|
|
101
112
|
"embedParamsTypes": "Resolved leaves keep native JSON types (bool stays bool, 5 stays integer 5). Unresolved leaves are dropped (see unresolved.embedParams).",
|
|
102
113
|
"collectionCellScope": {
|
|
103
114
|
"guaranteed": "The item object's own fields, flat, plus the reserved key 'index' (0-based).",
|
|
104
115
|
"parentData": "NOT guaranteed. Depending on parent-screen keys inside a cell is non-portable (validator warning).",
|
|
116
|
+
"itemPrefixReadBy": "Hand-mirrored in Ruby: kjui_tools/lib/compose/generators/cell_generator.rb (scaffolds flat item fields) and kjui_tools/lib/core/binding_validator_core.rb (mirrored in sjui_tools and rjui_tools), pinned by kjui_tools/spec/compose/generators/cell_generator_spec.rb 'scaffolds flat item-field bindings without the item. prefix'. Declared 2026-09-10; the link is prose.",
|
|
105
117
|
"itemPrefix": "'@{item.xxx}' is NOT canonical; scaffolds must emit flat field names."
|
|
106
118
|
},
|
|
107
119
|
"validatorRules": [
|
|
@@ -10,8 +10,10 @@
|
|
|
10
10
|
],
|
|
11
11
|
"nodeDirective": {
|
|
12
12
|
"attribute": "platform",
|
|
13
|
+
"consumedAtReadBy": "jui_tools/jui_cli/core/platform_resolver.py PlatformResolver.resolve_tree consumes the directive (the key is dropped from the output on every platform); pinned by jui_tools/tests/test_platform_semantics_is_read_by_the_resolver.py test_a_canon_token_keeps_the_node_and_a_foreign_one_drops_it (asserts the resolved node carries no `platform`). Declared 2026-09-10.",
|
|
13
14
|
"consumedAt": "distribution (jui build). The key never appears in distributed per-platform output; platform-specific emit is produced from it.",
|
|
14
15
|
"objectForm": {
|
|
16
|
+
"readBy": "jui_tools/jui_cli/core/platform_resolver.py PlatformResolver._is_override_map (`detection`: every key one of VALID_PLATFORMS) and resolve (merge); pinned by jui_tools/tests/test_platform_semantics_is_read_by_the_resolver.py",
|
|
15
17
|
"summary": "Per-platform attribute overrides, merged into the node for the matching platform only. Override wins over the base value; merge is per top-level key (the override value replaces the base value for that key wholesale — it is not deep-merged into it).",
|
|
16
18
|
"example": {
|
|
17
19
|
"prompt": "Change fontColor only on iOS",
|
|
@@ -43,6 +45,7 @@
|
|
|
43
45
|
"nesting": "Override values are themselves resolved recursively, so an override may contain child nodes with their own 'platform' directives."
|
|
44
46
|
},
|
|
45
47
|
"stringForm": {
|
|
48
|
+
"readBy": "jui_tools/jui_cli/core/platform_resolver.py PLATFORM_LANG_MAP (the `tokens` table, held equal by jui_tools/tests/test_platform_semantics_is_read_by_the_resolver.py) and PlatformResolver.resolve (the comma-split filter)",
|
|
46
49
|
"summary": "Node filter: the node (and its whole subtree) is DROPPED on platforms that do not match. Comma-separated tokens allowed.",
|
|
47
50
|
"example": {
|
|
48
51
|
"prompt": "Render this node on Android only",
|
|
@@ -56,15 +59,17 @@
|
|
|
56
59
|
}
|
|
57
60
|
},
|
|
58
61
|
"layoutRootPlatforms": {
|
|
62
|
+
"readBy": "jui_tools/jui_cli/commands/build_cmd.py ROOT_PLATFORMS_KEY (read at the layout root, stripped from the output); held equal to `attribute` by jui_tools/tests/test_platform_semantics_is_read_by_the_resolver.py",
|
|
59
63
|
"attribute": "platforms",
|
|
60
64
|
"summary": "Layout-root array whitelisting the distribution targets of the ENTIRE file (e.g. [\"ios\", \"android\"]). Distinct from the node-level 'platform' directive: 'platforms' picks which platforms get the file at all; 'platform' shapes a single node per platform."
|
|
61
65
|
},
|
|
62
66
|
"vsResponsive": {
|
|
67
|
+
"readBy": "jui_tools/jui_cli/core/platform_resolver.py PlatformResolver.resolve_tree leaves `responsive` untouched on every platform (the runtime resolves it); pinned by jui_tools/tests/test_platform_semantics_is_read_by_the_resolver.py test_responsive_is_left_for_the_runtime. Declared 2026-09-10.",
|
|
63
68
|
"summary": "'responsive' is resolved at RUNTIME by each platform's framework (window size class / orientation); 'platform' is resolved at BUILD time and is invisible at runtime. Use 'platform' for per-OS differences, 'responsive' for per-form-factor differences. The resolver leaves 'responsive' untouched."
|
|
64
69
|
},
|
|
65
70
|
"authoringGuidance": {
|
|
66
71
|
"spec": "Screen specs stay platform-agnostic; when a node intentionally differs per platform, note the difference in the spec text of the section that owns the node (do not fork the spec per platform). Large screens split into sub-specs list them in the parent's 'subSpecs' array — the sub-spec that owns the node carries the note.",
|
|
67
72
|
"precedence": "Per-node 'platform' override > inline base attribute > 'style'-provided value. Distribution bakes the override into the node as a plain inline attribute; style merge (applied later, at codegen or dynamic runtime) always yields to inline attributes.",
|
|
68
|
-
"
|
|
73
|
+
"dynamicFaceNote": "Distribution has already resolved 'platform' by the time a layout reaches a device, so the dynamic face never sees the key either — declaring it costs nothing at runtime on any face."
|
|
69
74
|
}
|
|
70
75
|
}
|
|
@@ -159,6 +159,7 @@
|
|
|
159
159
|
}
|
|
160
160
|
},
|
|
161
161
|
"buildGating": {
|
|
162
|
+
"readBy": "web: rjui_tools/lib/cli/commands/build_command.rb emits the generated screenMarker helper (returns {} when NODE_ENV is production) and rjui_tools/lib/react/react_generator.rb spreads it on the root; rjui_tools/spec/react/react_generator_screen_marker_spec.rb pins the emitted TEXT only. ⚠️ Until 2026-09-10 the emitted helper carried `typeof process !== 'undefined' && process?.env?.NODE_ENV === 'production'`, which no bundler folds (measured: Next 16 Turbopack rendered the marker in production; esbuild kept it under define). Fixed the same day: the emit is the literal form, pinned by rjui_tools/spec/cli/commands/screen_marker_helper_spec.rb (text; tsc with and without a global process; esbuild define folds production and keeps development; the previous form runs through the same arm as the control). ios and android: implemented in the SwiftJsonUI and KotlinJsonUI libraries (outside this repository — DEBUG-only modifier / FLAG_DEBUGGABLE), not pinned from here. Declared 2026-09-10.",
|
|
162
163
|
"rule": "The marker is test scaffolding and must not reach a shipped app. Every platform emits it in development builds only.",
|
|
163
164
|
"platforms": {
|
|
164
165
|
"ios": "#if DEBUG inside the library modifier — the same gate ViewSwitcher uses for Dynamic mode. SwiftJsonUI is distributed as source (SPM / CocoaPods), so this follows the CONSUMER's build configuration.",
|
|
@@ -275,11 +276,13 @@
|
|
|
275
276
|
"rationale": "Distinct from defaultTimeout (5000ms): real cross-screen assertions in production suites already use 15000-20000ms after a cold start."
|
|
276
277
|
},
|
|
277
278
|
"optOut": {
|
|
279
|
+
"readBy": "Outside this repository: the three test drivers read the config key (jsonui-test-runner drivers/android and drivers/web JsonUITestRunner, jsonui-test-runner-ios TestRunner; all default true). Nothing in jsonui-cli reads this entry; it documents the drivers. Declared 2026-09-10.",
|
|
278
280
|
"config": "TestRunnerConfig.verifyScreenTransitions",
|
|
279
281
|
"default": true,
|
|
280
282
|
"note": "ON by default from driver ios 1.9.0 / android 1.8.0 / web 1.8.0. An app whose generated code predates screen markers fails every screen change with marker-absent — that is the intended signal that it needs `jui build` and a current library pin, not a reason to soften the check. A project mid-migration opts out by setting this to false."
|
|
281
283
|
},
|
|
282
284
|
"failureHandling": {
|
|
285
|
+
"readBy": "Outside this repository: the drivers choose the class string and throw (ios and web found by the spelling `diagnosis`; android not located by that spelling on 2026-09-10 — unmeasured, not absent). Nothing in jsonui-cli reads this entry. Declared 2026-09-10.",
|
|
283
286
|
"severity": "failure",
|
|
284
287
|
"note": "Every class below fails the step through the ordinary assertion path. There is no warning tier and no per-class severity: a class is a prefix on the thrown failure message that names the likely CAUSE, so the reader knows what to fix — not how seriously to take it. This is what the three drivers implement (ScreenMarker.diagnosis on ios/android, AssertionExecutor.screenDiagnosis on web): they choose a string, then throw.",
|
|
285
288
|
"rationale": "A class was briefly considered for demotion to a warning (marker-absent, since its cause is the build rather than the app). Rejected: a suite that reports success while verifying nothing is worse than one that fails loudly on a stale build."
|
|
@@ -329,6 +332,7 @@
|
|
|
329
332
|
"A `back` declaration carries no target: it is drawn as a return edge (dotted) to each screen that has a forward edge into the declaring screen. Derived from the specs, never from a test.",
|
|
330
333
|
"An `external` destination is drawn as a terminal node; `none` draws nothing (a positive declaration); `unknown` and an unmapped `route` draw nothing and are treated as ABSENT — counted and listed, not errors by themselves (ruled 2026-09-10: 解決できない宣言は「無い」扱い)."
|
|
331
334
|
],
|
|
335
|
+
"idSpaceCollisions": "The id space (layout screen ids ∪ spec stems ∪ app-owned ids) is collapsed onto the classifier's normalized key BEFORE classification. Two raw ids that normalize alike (a layout forgot_password and a spec file forgotpassword) are one screen: the winner is chosen by provenance — layout > app-owned > spec file name, ties by sort — every loser is redirected to it, and the pair is reported as WARNING [doc-diagram] naming both ids and their sources. Measured 2026-09-10 on one face: left to a set, the winner followed PYTHONHASHSEED (3 of 6 seeds each way), the same input drew 31 or 32 nodes, and the round trip split across two nodes with no warning. The classifier itself is deterministic (sorted, first wins) but cannot see provenance, so the collapse lives in the caller.",
|
|
332
336
|
"flowTestCheck": {
|
|
333
337
|
"rule": "Every FORWARD transition a flow test performs (consecutive distinct screens after resolve/canonicalize/collapse) must be an edge the specs declare: a forward edge, or the return edge derived from a `back` declaration (X returns to each screen that pushes it — a flow that leaves a sheet by tapping save and lands on its opener performs that return as a forward step). One that is neither is an ERROR.",
|
|
334
338
|
"ruledOn": "2026-09-10 — 'flowdiagram は spec だけ見て書く。flow test で遷移が見つかって、それが spec にない時はエラーを吐く'",
|
|
@@ -342,7 +346,7 @@
|
|
|
342
346
|
"severity": "error. `jsonui-doc generate html` writes every page (the diagram page lists the errors), prints one `ERROR [doc-diagram]:` line per transition, and exits 1. Not covered by --allow-partial (that accepts missing pages; this is a wrong spec). `generate mermaid` exits 1 likewise.",
|
|
343
347
|
"reported": "per owner (app), with the flow test's name and file, from, to, and the reason. A count without the list is not a report.",
|
|
344
348
|
"noSpecDirectory": "A flow group with no spec directory anywhere gets ONE `WARNING [doc-diagram]` naming how many flow test files went unchecked, and no diagram. Absence of the directory is a configuration state, not a spec omission.",
|
|
345
|
-
"readBy": "document_tools/jsonui_doc_cli/test_doc/mermaid/generator.py build_diagram; the exit code in jsonui_doc_cli/cli.py cmd_generate_html"
|
|
349
|
+
"readBy": "document_tools/jsonui_doc_cli/test_doc/mermaid/generator.py build_diagram; the exit code in document_tools/jsonui_doc_cli/cli.py cmd_generate_html"
|
|
346
350
|
},
|
|
347
351
|
"specTransitions": {
|
|
348
352
|
"source": "<screen spec>.transitions[].destination",
|
|
@@ -423,7 +427,7 @@
|
|
|
423
427
|
"ambiguity": "When one file name is claimed by several tests covering DIFFERENT screens, nothing is resolved: picking one would silently label the node with the wrong screen."
|
|
424
428
|
},
|
|
425
429
|
"groups": {
|
|
426
|
-
"source": "A node's group comes from the screen tests covering it (metadata.group).
|
|
430
|
+
"source": "A node's group comes, in this precedence, from: the screen tests covering it (metadata.group); the screen spec's metadata.group (string or list) — added 2026-09-10 because the diagram is drawn from specs and a screen with a spec and a layout but no test had nowhere to declare one (one face: 22 of 31 drawn nodes undeclared); the jui.config.json app-owned declaration (appOwnedScreens.declaration.group).",
|
|
427
431
|
"ungrouped": "Nodes with no group land in a catch-all bucket, which must keep reading as 'undeclared'. A screen with no way to declare a group would otherwise live there forever and hide the ones that simply have not been declared yet."
|
|
428
432
|
}
|
|
429
433
|
},
|
package/package.json
CHANGED
|
@@ -44,7 +44,7 @@ const DATA_DIR = join(PROJECT_ROOT, "data");
|
|
|
44
44
|
// 3. `npm run fetch-definitions` and commit the data/ diff together with
|
|
45
45
|
// this pin in one commit
|
|
46
46
|
// JSONUI_CLI_RAW_BASE still overrides the whole base URL for manual testing.
|
|
47
|
-
const JSONUI_CLI_TAG = "v1.8.
|
|
47
|
+
const JSONUI_CLI_TAG = "v1.8.80";
|
|
48
48
|
const BASE_URL =
|
|
49
49
|
process.env.JSONUI_CLI_RAW_BASE ||
|
|
50
50
|
`https://raw.githubusercontent.com/Tai-Kimura/jsonui-cli/${JSONUI_CLI_TAG}`;
|