jui-tools-mcp-server 2.15.2 → 2.15.4
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/data/attribute_definitions.json +1 -1
- package/data/attribute_semantics.json +109 -1
- package/data/binding_semantics.json +2 -2
- package/dist/data/derived.js +3 -1
- package/package.json +1 -1
- package/scripts/fetch-definitions.js +1 -1
- package/src/data/derived.ts +3 -1
- package/tests/__snapshots__/derived.test.ts.snap +3 -1
|
@@ -4453,7 +4453,7 @@
|
|
|
4453
4453
|
"large",
|
|
4454
4454
|
"linear"
|
|
4455
4455
|
],
|
|
4456
|
-
"description": "Indicator style: `small` / `medium` / `large` are the spinner's size
|
|
4456
|
+
"description": "Indicator style: `small` / `medium` / `large` are the spinner's size and `linear` a bar instead of a spinner. Each size is the platform's own value, `small` and `large` as well as `medium` (2026-10-05 user ruling): the faces need not draw the same small or large spinner. A declared width / height wins over the style's size. The legacy spellings `style` (naming one of these values) and `size` (a length) are folded by the layout normalizer, with a warning: `style` into this attribute, `size` into width and height.",
|
|
4457
4457
|
"platform": [
|
|
4458
4458
|
"swift",
|
|
4459
4459
|
"kotlin"
|
|
@@ -181,7 +181,7 @@
|
|
|
181
181
|
},
|
|
182
182
|
"perValueMapping": {
|
|
183
183
|
"fill": {
|
|
184
|
-
"swiftui": ".frame(maxWidth/maxHeight: .infinity)
|
|
184
|
+
"swiftui": "grow from content: DistributionFillLayout in SwiftJsonUI (10.29.6), content size plus an equal share of the leftover, the same policy as Compose's DistributionFillRow/Column; each undeclared child still takes .frame(maxWidth/maxHeight: .infinity), which is how the layout knows it may grow. An HStack/VStack of those children split the axis equally, which is fillEqually (frame-parity common/distribution__fill, 2026-10-05: 60 / 120 / 120 against Android 60 / 97 / 143)",
|
|
185
185
|
"compose": "grow from content — NOT expressible with Modifier.weight (weight(1f) IS fillEqually, the run-4 0px collapse; weight(1f, fill=false) packs by content and is inert). Dedicated measurement pass DistributionFillRow/Column in the base KotlinJsonUI library (c7d2dfb): content size plus an equal split of the leftover, one policy shared by the kjui codegen emit and the dynamic renderer. Device-verified: all 6 value pairs pairwise distinct, explicit child size never grown, spacing gaps pinned",
|
|
186
186
|
"web": "grow on each child"
|
|
187
187
|
},
|
|
@@ -395,6 +395,114 @@
|
|
|
395
395
|
},
|
|
396
396
|
"ruling": "2026-09-26 (4f, round 5 of the Collection audit). Until jsonui-cli 1.9.0 no declaration said what an undeclared Collection spacing draws, and the paths had each picked one: 0 everywhere except the flow route on iOS — sjui codegen 8 between cells, lines and section blocks; SwiftJsonUI Dynamic 8 on the lazy flow (its section blocks the ScrollView's own stack spacing) and `x > 0 ? x : 8` on the `lazy: none` flow, so a declared 0 drew 8. Android and web drew 0. The ruling: 0, flow included, on every route and every path. Declared HERE and not as a `default` in attribute_definitions.json, on purpose: a `default` there changes the attribute's kind — the conformance fixture generator writes the declared default as the fixture's value, the inert audit lists it, and attr codegen carries it into the generated tables — and the rule is per route, which one scalar cannot say. `sectionSpacing` is lineSpacing's alias (definitions `aliases`). Toolchain comments point here.",
|
|
397
397
|
"observable": {}
|
|
398
|
+
},
|
|
399
|
+
"backgroundSafeArea": {
|
|
400
|
+
"attributes": [
|
|
401
|
+
"common.background"
|
|
402
|
+
],
|
|
403
|
+
"ruling": "2026-10-05 user ruling (relayed by the orchestrator, from the frame-parity inventory: docs/bugs/material/frame-parity-inventory-3face-2026-10-05/rulings.md). When a view with a background touches the canvas edge next to a safe-area inset (the status-bar band above it), iOS painting the background into that band is an accepted iOS idiom; Android and web stopping at the view's frame are equally correct. The view's layout frame is the same on every face; only the paint may reach the band. Measured: SwiftUI's default for a background touching a safe-area edge (iOS frame y -62, height +62 or more, 340 ids), Android and web y 0. Not a reason to move the layout box.",
|
|
404
|
+
"observable": {}
|
|
405
|
+
},
|
|
406
|
+
"borderContentBox": {
|
|
407
|
+
"attributes": [
|
|
408
|
+
"common.borderWidth",
|
|
409
|
+
"common.borderColor"
|
|
410
|
+
],
|
|
411
|
+
"ruling": "2026-10-05 user ruling (relayed by the orchestrator, from the frame-parity inventory: docs/bugs/material/frame-parity-inventory-3face-2026-10-05/rulings.md). A border does NOT push the content inward: the view's children keep the positions they have without the border, and the border is drawn over them. A border on a wrapContent Label does not add to its height. Measured deviation: web insets children by the border width (2, 2) and adds 2 x borderWidth to a Label's height; iOS and Android draw over the content. See also `border` (when a border is drawn at all).",
|
|
412
|
+
"observable": {},
|
|
413
|
+
"inputFieldException": "TextField, TextView and SelectBox are the exception, declared so it is not a silent one: an input field's border is the control's chrome, and where its text sits inside that border is the platform's idiom. Web keeps a CSS border on input fields (rjui border_draws_over_content? is false for them) so the text never runs under the frame and the outline stays free for the focus ring. Every other web border is drawn over the content with ::after (an outline only for replaced elements such as <img>, which cannot hold ::after): outline does not follow rounded corners before Safari 16.4, and an inset box-shadow is painted under a child touching the edge (triage branch triage/1916-rjui-geometry, commit 7cee4bdb; it replaced 5898c2cf on rebase). Not covered by any fixture yet: the conformance corpus has no input field that declares a border (triage, scanning the web-scoped manifest), so this exception is not measured by the gate until one is added."
|
|
414
|
+
},
|
|
415
|
+
"stackOverflow": {
|
|
416
|
+
"attributes": [
|
|
417
|
+
"View.orientation",
|
|
418
|
+
"View.spacing",
|
|
419
|
+
"common.paddings"
|
|
420
|
+
],
|
|
421
|
+
"ruling": "2026-10-05 user ruling (relayed by the orchestrator, from the frame-parity inventory: docs/bugs/material/frame-parity-inventory-3face-2026-10-05/rulings.md). When a fixed-size row's (or column's) children, with its padding and spacing, exceed its size, the children are still placed in sequence at their declared sizes and the overflow runs past the far edge; no child is moved back inside. Measured deviation: Android holds the last child at 180 in a 200 row of six 40s; iOS and web place it at 200 (208 with paddingLeft 8, 240 with spacing 8).",
|
|
422
|
+
"observable": {}
|
|
423
|
+
},
|
|
424
|
+
"distributionRemainder": {
|
|
425
|
+
"attributes": [
|
|
426
|
+
"View.distribution"
|
|
427
|
+
],
|
|
428
|
+
"ruling": "2026-10-05 user ruling (relayed by the orchestrator, from the frame-parity inventory: docs/bugs/material/frame-parity-inventory-3face-2026-10-05/rulings.md). In a `fillEqually` stack where some children declare their size on the distribution axis, those children keep their declared size (distribution.explicitChildSizeWins) and the REMAINING space is shared equally by the others: 300 wide, one child declaring 60 and two undeclared -> 60 / 120 / 120. Measured before the ruling: Android 60 / 100 / 100 (40 left empty), web 100 / 100 / 100.",
|
|
429
|
+
"observable": {}
|
|
430
|
+
},
|
|
431
|
+
"emptyLabelHeight": {
|
|
432
|
+
"attributes": [
|
|
433
|
+
"Label.text",
|
|
434
|
+
"common.height"
|
|
435
|
+
],
|
|
436
|
+
"ruling": "2026-10-05 user ruling (relayed by the orchestrator, from the frame-parity inventory: docs/bugs/material/frame-parity-inventory-3face-2026-10-05/rulings.md). A Label whose text is empty and whose height is wrapContent still has the height of one line of its font; wrapContent does not collapse it to 0. Measured before the ruling: Android 24, web 0.",
|
|
437
|
+
"observable": {}
|
|
438
|
+
},
|
|
439
|
+
"labelHint": {
|
|
440
|
+
"attributes": [
|
|
441
|
+
"Label.hint",
|
|
442
|
+
"Label.hintAttributes"
|
|
443
|
+
],
|
|
444
|
+
"ruling": "2026-10-05 user ruling (relayed by the orchestrator, from the frame-parity inventory: docs/bugs/material/frame-parity-inventory-3face-2026-10-05/rulings.md). `Label.hint` is shown when the text is empty whether or not `hintAttributes` is declared; without hintAttributes it is drawn in the platform's default subdued hint colour at the Label's font. Measured before the ruling: rjui and Android required hintAttributes to show the hint.",
|
|
445
|
+
"observable": {}
|
|
446
|
+
},
|
|
447
|
+
"lineBreakModeLines": {
|
|
448
|
+
"attributes": [
|
|
449
|
+
"Label.lineBreakMode",
|
|
450
|
+
"Label.lines"
|
|
451
|
+
],
|
|
452
|
+
"ruling": "2026-10-05 user ruling (relayed by the orchestrator, from the frame-parity inventory: docs/bugs/material/frame-parity-inventory-3face-2026-10-05/rulings.md). `lineBreakMode` (head / middle / tail / clip) chooses HOW the last allowed line is truncated; it does not mean one line. The number of lines is `lines` alone (0 or absent: unlimited), so with no `lines` the text wraps and nothing is truncated. Measured before the ruling: web drew one line (24), Android wrapped (120).",
|
|
453
|
+
"observable": {}
|
|
454
|
+
},
|
|
455
|
+
"lineHeightMultipleBase": {
|
|
456
|
+
"attributes": [
|
|
457
|
+
"Label.lineHeightMultiple"
|
|
458
|
+
],
|
|
459
|
+
"ruling": "2026-10-05 user ruling (relayed by the orchestrator, from the frame-parity inventory: docs/bugs/material/frame-parity-inventory-3face-2026-10-05/rulings.md). `lineHeightMultiple` multiplies the font's own (natural) line height, not the font size. Measured before the ruling: lineHeightMultiple 1.8 drew 127.5 on Android and 143.98 on web. The base line height L is defined by a second user ruling (2026-10-05, relayed by the orchestrator): L is the height of one line of a Label on that platform that declares no lineHeight, at the Label's fontSize (with no fontSize: iOS 20.33, Android 24, web 24, measured on the conformance hosts; with a fontSize, one line of an undeclared-lineHeight Label at that size). lineHeightMultiple makes every line m x L; lineSpacing is added between lines only. iOS draws no multiple on the first line: SwiftUI has no way to set each line's height, so the first line stays L and the lines after it are m x L apart (2026-10-05 user instruction, relayed by the orchestrator: no elaborate implementation for this). So n lines are L + (n - 1) x m x L on iOS, (m - 1) x L short of the n x m x L the other platforms draw; frame_parity_rules.json pins that amount. Expected for three lines at m = 1.8 with no fontSize: iOS 93.52 (20.33 + 2 x 36.59), Android 129.6, web 129.6. Measured before (three-line probe, pixels): iOS 88.33 (it adds (m - 1) x fontSize between lines), Android 76.5 (it multiplied a fallback fontSize 14), web 86.4 (it multiplied the font size 16).",
|
|
460
|
+
"observable": {}
|
|
461
|
+
},
|
|
462
|
+
"lineSpacingBetween": {
|
|
463
|
+
"attributes": [
|
|
464
|
+
"Label.lineSpacing"
|
|
465
|
+
],
|
|
466
|
+
"ruling": "2026-10-05 user ruling (relayed by the orchestrator, from the frame-parity inventory: docs/bugs/material/frame-parity-inventory-3face-2026-10-05/rulings.md). `lineSpacing` is added BETWEEN lines only: n lines get n - 1 spacings, and nothing is added after the last line. Measured before the ruling: lineSpacing 16 drew 150 on Android and 160 on web. The base line height L is defined by a second user ruling (2026-10-05, relayed by the orchestrator): L is the height of one line of a Label on that platform that declares no lineHeight, at the Label's fontSize (with no fontSize: iOS 20.33, Android 24, web 24, measured on the conformance hosts; with a fontSize, one line of an undeclared-lineHeight Label at that size). lineHeightMultiple makes every line m x L; lineSpacing is added between lines only. So n lines are n x L + (n - 1) x lineSpacing. Expected for three lines at 16 with no fontSize: iOS 93, Android 104, web 104. Measured before (three-line probe, pixels): iOS 93 (already so: SwiftUI's lineSpacing replaces its default 1.67 leading), Android 90 and web 96 (both added the spacing to every line).",
|
|
467
|
+
"observable": {}
|
|
468
|
+
},
|
|
469
|
+
"iconLabelIconSize": {
|
|
470
|
+
"attributes": [
|
|
471
|
+
"IconLabel.iconSize",
|
|
472
|
+
"IconLabel.icon_on",
|
|
473
|
+
"IconLabel.icon_off"
|
|
474
|
+
],
|
|
475
|
+
"ruling": "2026-10-05 user ruling (relayed by the orchestrator, from the frame-parity inventory: docs/bugs/material/frame-parity-inventory-3face-2026-10-05/rulings.md). When `iconSize` is not declared: an IconLabel with no icon reserves no space for one, and one with an icon draws it at the image's own size. Measured before the ruling: web reserved a 96 icon box with no icon declared, Android none (height 24 vs 96).",
|
|
476
|
+
"observable": {}
|
|
477
|
+
},
|
|
478
|
+
"stackDirection": {
|
|
479
|
+
"attributes": [
|
|
480
|
+
"View.direction",
|
|
481
|
+
"SafeAreaView.direction"
|
|
482
|
+
],
|
|
483
|
+
"ruling": "2026-10-05 user ruling (relayed by the orchestrator, from the frame-parity inventory: docs/bugs/material/frame-parity-inventory-3face-2026-10-05/rulings.md). `direction: bottomToTop` stacks from the BOTTOM edge: the first child is the lowest, and each next child sits above it. (The same reading applies to the other directions: the first child sits at the edge the direction starts from.) Measured before the ruling: in a 200-high column of 40s, iOS and Android put box_b at 160, web at 120; SafeAreaView bottomToTop: Android stacked at the top (0 / 40 / 80), web at the bottom (728 / 688 / 648). 2026-10-05 user ruling (bottomToTop) applied to the horizontal axis by the orchestrator: `direction: rightToLeft` stacks from the RIGHT edge: the first child is the rightmost, and each next child sits to its left (the usual meaning of right-to-left). Measured before (frame-parity inventory, View/direction__righttoleft, six 40-wide boxes in a 200-wide row): web put box_a at x 160 and box_f at -40 (from the right edge, as declared); iOS (box_a 200 ... box_f 0) and Android (box_a 180, then 160 ... 0) reverse the children and stack them from the left.",
|
|
484
|
+
"observable": {}
|
|
485
|
+
},
|
|
486
|
+
"wrapContentCap": {
|
|
487
|
+
"attributes": [
|
|
488
|
+
"common.width",
|
|
489
|
+
"common.height"
|
|
490
|
+
],
|
|
491
|
+
"ruling": "2026-10-05 user ruling (relayed by the orchestrator, from the three-face conformance run): \"Wrapcontent は Android が正しいね。親の大きさが固定や matchparent ならその大きさまで\". A wrapContent box (or one with no size declared on that axis) takes its content's size, but no more than its parent's size on that axis when the parent has one there: a number, or matchParent. A parent that scrolls along that axis (a ScrollView's content axis) has none, and neither does a wrapContent parent. Measured before the ruling: a flow Collection, lazy none, wrapContent in a 150x100 parent (Collection/flowOverflow__noneWrapInBox and its control) was 168 high on web, 100 on Android. Distinct from ruling S: a child with a FIXED size larger than its parent keeps that size and overflows; the cap applies to the wrapContent box itself only.",
|
|
492
|
+
"iosLimitation": "SwiftUI: not enforced; a wrapContent box may report its content's size past a sized parent (declared limitation, 2026-10-06 user ruling). SwiftUI offers a child its share of a stack, not its parent's size, and a vertical ScrollView frames its content at least the viewport high, so a cap read from the offer squeezed wrapContent rows inside scrolling content below their content (measured on a consumer screen, 14.3pt -> 9.2pt); it was dropped rather than built around."
|
|
493
|
+
},
|
|
494
|
+
"iconLabelTextLine": {
|
|
495
|
+
"attributes": [
|
|
496
|
+
"IconLabel.text",
|
|
497
|
+
"IconLabel.fontSize"
|
|
498
|
+
],
|
|
499
|
+
"ruling": "2026-10-05 user ruling (relayed by the orchestrator): the text line of an IconLabel is the same as one line of a Label on the same platform at the same font size (lineHeightMultipleBase's L). An IconLabel draws no taller or shorter text line than a Label would."
|
|
500
|
+
},
|
|
501
|
+
"labelLongWordBreak": {
|
|
502
|
+
"attributes": [
|
|
503
|
+
"Label.text"
|
|
504
|
+
],
|
|
505
|
+
"ruling": "2026-10-05 user ruling (relayed by the orchestrator, answered as a choice): a word longer than the Label's box breaks inside the box — only the word that does not fit is cut at the box's width and carried to the next line (CSS `overflow-wrap: anywhere`); where it breaks may differ per OS. Measured before the ruling: a 200-wide Label with \"See https://example.com/conformance for details\" (Label/linkable__* and __control/Label__text-5ed92def) wrapped the URL inside the box on iOS and Android and let it run past the box on web. A declared lineBreakMode Char / Word keeps its own rule."
|
|
398
506
|
}
|
|
399
507
|
}
|
|
400
508
|
}
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"unresolved": "platformEventContract"
|
|
36
36
|
},
|
|
37
37
|
"twoWay": {
|
|
38
|
-
"description": "Two-way bindings (TextField text, Switch isOn, ...). The
|
|
38
|
+
"description": "Two-way bindings (TextField text, Switch isOn, ...). The whole value must be one binding of a single flat identifier: no literal text around it, no second binding, and no dots, no brackets, no '??', no '!' in the expression. This keeps write-back and change-handler wiring trivially derivable on every platform.",
|
|
39
39
|
"interpolation": false,
|
|
40
40
|
"features": [],
|
|
41
41
|
"unresolved": "attributeDefault"
|
|
@@ -118,7 +118,7 @@
|
|
|
118
118
|
},
|
|
119
119
|
"validatorRules": [
|
|
120
120
|
{"id": "binding-double-default", "severity": "error", "context": "any", "rule": "More than one '??' in a single binding expression."},
|
|
121
|
-
{"id": "binding-two-way-complex", "severity": "error", "context": "twoWay", "rule": "
|
|
121
|
+
{"id": "binding-two-way-complex", "severity": "error", "context": "twoWay", "rule": "A two-way value is not one flat binding: the whole value must be a single flat identifier binding — no literal text mixed with it, no second binding, and no '.', '[', '??', or '!' in the expression."},
|
|
122
122
|
{"id": "binding-negation-context", "severity": "error", "context": "text|embedParams|nameRef|twoWay", "rule": "'!' negation used outside a boolean value context."},
|
|
123
123
|
{"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)."},
|
|
124
124
|
{"id": "binding-params-array", "severity": "error", "context": "embedParams", "rule": "Array anywhere inside params (pre-existing rule from the embed isolated+params track; restated here)."},
|
package/dist/data/derived.js
CHANGED
|
@@ -130,10 +130,11 @@ export const MODIFIER_ORDER = {
|
|
|
130
130
|
},
|
|
131
131
|
kotlin: {
|
|
132
132
|
order: [
|
|
133
|
-
"testTag",
|
|
134
133
|
"margins",
|
|
135
134
|
"weight (caller applies)",
|
|
136
135
|
"size (width/height, matchParent/wrapContent, min/max/aspectRatio)",
|
|
136
|
+
"offset",
|
|
137
|
+
"testTag (with testTagsAsResourceId): after margins and offset, so the tagged node's bounds are the drawn box",
|
|
137
138
|
"alpha/opacity",
|
|
138
139
|
"shadow/elevation",
|
|
139
140
|
"background (cornerRadius -> clip -> border -> bgColor)",
|
|
@@ -144,6 +145,7 @@ export const MODIFIER_ORDER = {
|
|
|
144
145
|
criticalRules: [
|
|
145
146
|
"Modifier.then() chains left-to-right; order matters",
|
|
146
147
|
"margins before size (outside to inside)",
|
|
148
|
+
"testTag after margins and offset (from KotlinJsonUI 2.43.5 / jsonui-cli 1.9.16): a tag outside them reports a margin-inclusive box to accessibility",
|
|
147
149
|
"cornerRadius must be applied as clip before background",
|
|
148
150
|
],
|
|
149
151
|
},
|
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.9.
|
|
47
|
+
const JSONUI_CLI_TAG = "v1.9.16";
|
|
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}`;
|
package/src/data/derived.ts
CHANGED
|
@@ -136,10 +136,11 @@ export const MODIFIER_ORDER = {
|
|
|
136
136
|
},
|
|
137
137
|
kotlin: {
|
|
138
138
|
order: [
|
|
139
|
-
"testTag",
|
|
140
139
|
"margins",
|
|
141
140
|
"weight (caller applies)",
|
|
142
141
|
"size (width/height, matchParent/wrapContent, min/max/aspectRatio)",
|
|
142
|
+
"offset",
|
|
143
|
+
"testTag (with testTagsAsResourceId): after margins and offset, so the tagged node's bounds are the drawn box",
|
|
143
144
|
"alpha/opacity",
|
|
144
145
|
"shadow/elevation",
|
|
145
146
|
"background (cornerRadius -> clip -> border -> bgColor)",
|
|
@@ -150,6 +151,7 @@ export const MODIFIER_ORDER = {
|
|
|
150
151
|
criticalRules: [
|
|
151
152
|
"Modifier.then() chains left-to-right; order matters",
|
|
152
153
|
"margins before size (outside to inside)",
|
|
154
|
+
"testTag after margins and offset (from KotlinJsonUI 2.43.5 / jsonui-cli 1.9.16): a tag outside them reports a margin-inclusive box to accessibility",
|
|
153
155
|
"cornerRadius must be applied as clip before background",
|
|
154
156
|
],
|
|
155
157
|
},
|
|
@@ -62,13 +62,15 @@ exports[`derived constants (pinned) > MODIFIER_ORDER snapshot 1`] = `
|
|
|
62
62
|
"criticalRules": [
|
|
63
63
|
"Modifier.then() chains left-to-right; order matters",
|
|
64
64
|
"margins before size (outside to inside)",
|
|
65
|
+
"testTag after margins and offset (from KotlinJsonUI 2.43.5 / jsonui-cli 1.9.16): a tag outside them reports a margin-inclusive box to accessibility",
|
|
65
66
|
"cornerRadius must be applied as clip before background",
|
|
66
67
|
],
|
|
67
68
|
"order": [
|
|
68
|
-
"testTag",
|
|
69
69
|
"margins",
|
|
70
70
|
"weight (caller applies)",
|
|
71
71
|
"size (width/height, matchParent/wrapContent, min/max/aspectRatio)",
|
|
72
|
+
"offset",
|
|
73
|
+
"testTag (with testTagsAsResourceId): after margins and offset, so the tagged node's bounds are the drawn box",
|
|
72
74
|
"alpha/opacity",
|
|
73
75
|
"shadow/elevation",
|
|
74
76
|
"background (cornerRadius -> clip -> border -> bgColor)",
|