@onekeyfe/react-native-native-list 3.0.157-alpha.249 → 3.0.157-alpha.250

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 (130) hide show
  1. package/README.md +21 -5
  2. package/android/src/main/java/com/onekey/nativelist/NativeListAccessoryStack.kt +711 -0
  3. package/android/src/main/java/com/onekey/nativelist/NativeListActionRenderer.kt +169 -0
  4. package/android/src/main/java/com/onekey/nativelist/NativeListActivityRenderer.kt +243 -0
  5. package/android/src/main/java/com/onekey/nativelist/NativeListAdapter.kt +20 -6
  6. package/android/src/main/java/com/onekey/nativelist/NativeListDataRowRenderer.kt +487 -0
  7. package/android/src/main/java/com/onekey/nativelist/NativeListIdentityRenderer.kt +820 -0
  8. package/android/src/main/java/com/onekey/nativelist/NativeListImageSlot.kt +147 -0
  9. package/android/src/main/java/com/onekey/nativelist/NativeListLeadingVisual.kt +453 -0
  10. package/android/src/main/java/com/onekey/nativelist/NativeListMarketRenderer.kt +997 -0
  11. package/android/src/main/java/com/onekey/nativelist/NativeListMediaTileRenderer.kt +234 -0
  12. package/android/src/main/java/com/onekey/nativelist/NativeListMessageRenderer.kt +232 -0
  13. package/android/src/main/java/com/onekey/nativelist/NativeListMetricCardRenderer.kt +551 -0
  14. package/android/src/main/java/com/onekey/nativelist/NativeListModels.kt +24 -0
  15. package/android/src/main/java/com/onekey/nativelist/NativeListPrimitives.kt +475 -0
  16. package/android/src/main/java/com/onekey/nativelist/NativeListRailRenderer.kt +143 -0
  17. package/android/src/main/java/com/onekey/nativelist/NativeListRendererRegistry.kt +42 -0
  18. package/android/src/main/java/com/onekey/nativelist/NativeListRendererRowView.kt +415 -0
  19. package/android/src/main/java/com/onekey/nativelist/NativeListResolvedText.kt +163 -0
  20. package/android/src/main/java/com/onekey/nativelist/NativeListRowHost.kt +70 -0
  21. package/android/src/main/java/com/onekey/nativelist/NativeListSectionHeaderRenderer.kt +748 -0
  22. package/android/src/main/java/com/onekey/nativelist/NativeListSystemRenderer.kt +464 -0
  23. package/android/src/main/java/com/onekey/nativelist/NativeListTextView.kt +25 -0
  24. package/android/src/main/java/com/onekey/nativelist/NativeListView.kt +174 -104
  25. package/android/src/main/java/com/onekey/nativelist/NativeListWalletGroupRenderer.kt +407 -0
  26. package/android/src/test/java/com/margelo/nitro/nativelist/NativeListSelectorBackgroundColorTest.kt +34 -0
  27. package/docs/DESIGN.md +518 -9
  28. package/docs/MIGRATION_BASELINE.md +117 -0
  29. package/docs/ROW_TEMPLATES.md +359 -0
  30. package/docs/SPEC.md +358 -0
  31. package/docs/STYLE_SPEC.md +978 -0
  32. package/docs/images/list-capabilities.svg +39 -0
  33. package/docs/images/row-action.svg +17 -0
  34. package/docs/images/row-activity.svg +24 -0
  35. package/docs/images/row-data.svg +24 -0
  36. package/docs/images/row-identity.svg +25 -0
  37. package/docs/images/row-market.svg +23 -0
  38. package/docs/images/row-media-tile.svg +26 -0
  39. package/docs/images/row-message.svg +20 -0
  40. package/docs/images/row-metric-card.svg +33 -0
  41. package/docs/images/row-rail.svg +18 -0
  42. package/docs/images/row-section-header.svg +19 -0
  43. package/docs/images/row-style-regions.svg +30 -0
  44. package/docs/images/row-system.svg +30 -0
  45. package/docs/images/row-wallet-group.svg +26 -0
  46. package/ios/NativeListAccessoryStack.swift +680 -0
  47. package/ios/NativeListActionRenderer.swift +108 -0
  48. package/ios/NativeListActivityRenderer.swift +179 -0
  49. package/ios/NativeListDataRowRenderer.swift +444 -0
  50. package/ios/NativeListIdentityRenderer.swift +685 -0
  51. package/ios/NativeListImageSlot.swift +140 -0
  52. package/ios/NativeListLeadingVisual.swift +372 -0
  53. package/ios/NativeListMarketRenderer.swift +698 -0
  54. package/ios/NativeListMediaTileRenderer.swift +184 -0
  55. package/ios/NativeListMessageRenderer.swift +168 -0
  56. package/ios/NativeListMetricCardRenderer.swift +420 -0
  57. package/ios/NativeListModels.swift +32 -0
  58. package/ios/NativeListRailRenderer.swift +152 -0
  59. package/ios/NativeListRendererCell.swift +311 -0
  60. package/ios/NativeListRendererRegistry.swift +50 -0
  61. package/ios/NativeListResolvedText.swift +122 -0
  62. package/ios/NativeListRowHost.swift +63 -0
  63. package/ios/NativeListSectionHeaderRenderer.swift +708 -0
  64. package/ios/NativeListSystemRenderer.swift +568 -0
  65. package/ios/NativeListTextLabel.swift +42 -0
  66. package/ios/NativeListTextStyles.swift +236 -0
  67. package/ios/NativeListWalletGroupRenderer.swift +255 -0
  68. package/ios/RNCNativeListView.swift +111 -180
  69. package/lib/module/models.js +54 -0
  70. package/lib/module/validation.js +324 -26
  71. package/lib/module/web/NativeListWebEngine.js +148 -936
  72. package/lib/module/web/templates/ActionRowRenderer.js +97 -0
  73. package/lib/module/web/templates/ActivityRowRenderer.js +95 -0
  74. package/lib/module/web/templates/DataRowRenderer.js +145 -0
  75. package/lib/module/web/templates/IdentityRowRenderer.js +289 -0
  76. package/lib/module/web/templates/MarketRowRenderer.js +310 -0
  77. package/lib/module/web/templates/MediaTileRowRenderer.js +132 -0
  78. package/lib/module/web/templates/MessageRowRenderer.js +165 -0
  79. package/lib/module/web/templates/MetricCardRowRenderer.js +172 -0
  80. package/lib/module/web/templates/RailRowRenderer.js +98 -0
  81. package/lib/module/web/templates/RowContainerStyle.js +100 -0
  82. package/lib/module/web/templates/RowElements.js +93 -0
  83. package/lib/module/web/templates/RowRendererRegistry.js +70 -0
  84. package/lib/module/web/templates/RowText.js +121 -0
  85. package/lib/module/web/templates/RowVisual.js +43 -0
  86. package/lib/module/web/templates/SectionHeaderRowRenderer.js +146 -0
  87. package/lib/module/web/templates/SystemRowRenderer.js +161 -0
  88. package/lib/module/web/templates/WalletGroupRowRenderer.js +96 -0
  89. package/lib/typescript/src/models.d.ts +166 -13
  90. package/lib/typescript/src/web/NativeListWebEngine.d.ts +18 -16
  91. package/lib/typescript/src/web/templates/ActionRowRenderer.d.ts +15 -0
  92. package/lib/typescript/src/web/templates/ActivityRowRenderer.d.ts +15 -0
  93. package/lib/typescript/src/web/templates/DataRowRenderer.d.ts +15 -0
  94. package/lib/typescript/src/web/templates/IdentityRowRenderer.d.ts +16 -0
  95. package/lib/typescript/src/web/templates/MarketRowRenderer.d.ts +30 -0
  96. package/lib/typescript/src/web/templates/MediaTileRowRenderer.d.ts +15 -0
  97. package/lib/typescript/src/web/templates/MessageRowRenderer.d.ts +44 -0
  98. package/lib/typescript/src/web/templates/MetricCardRowRenderer.d.ts +15 -0
  99. package/lib/typescript/src/web/templates/RailRowRenderer.d.ts +28 -0
  100. package/lib/typescript/src/web/templates/RowContainerStyle.d.ts +9 -0
  101. package/lib/typescript/src/web/templates/RowElements.d.ts +38 -0
  102. package/lib/typescript/src/web/templates/RowRendererRegistry.d.ts +83 -0
  103. package/lib/typescript/src/web/templates/RowText.d.ts +9 -0
  104. package/lib/typescript/src/web/templates/RowVisual.d.ts +20 -0
  105. package/lib/typescript/src/web/templates/SectionHeaderRowRenderer.d.ts +15 -0
  106. package/lib/typescript/src/web/templates/SystemRowRenderer.d.ts +16 -0
  107. package/lib/typescript/src/web/templates/WalletGroupRowRenderer.d.ts +20 -0
  108. package/package.json +3 -3
  109. package/src/models.ts +281 -19
  110. package/src/validation.ts +501 -46
  111. package/src/web/NativeListWebEngine.ts +361 -1932
  112. package/src/web/templates/ActionRowRenderer.ts +123 -0
  113. package/src/web/templates/ActivityRowRenderer.ts +159 -0
  114. package/src/web/templates/DataRowRenderer.ts +212 -0
  115. package/src/web/templates/IdentityRowRenderer.ts +527 -0
  116. package/src/web/templates/MarketRowRenderer.ts +467 -0
  117. package/src/web/templates/MediaTileRowRenderer.ts +182 -0
  118. package/src/web/templates/MessageRowRenderer.ts +306 -0
  119. package/src/web/templates/MetricCardRowRenderer.ts +337 -0
  120. package/src/web/templates/RailRowRenderer.ts +136 -0
  121. package/src/web/templates/RowContainerStyle.ts +131 -0
  122. package/src/web/templates/RowElements.ts +160 -0
  123. package/src/web/templates/RowRendererRegistry.ts +111 -0
  124. package/src/web/templates/RowText.ts +181 -0
  125. package/src/web/templates/RowVisual.ts +115 -0
  126. package/src/web/templates/SectionHeaderRowRenderer.ts +243 -0
  127. package/src/web/templates/SystemRowRenderer.ts +282 -0
  128. package/src/web/templates/WalletGroupRowRenderer.ts +131 -0
  129. package/android/src/main/java/com/onekey/nativelist/NativeListRowView.kt +0 -4538
  130. package/ios/NativeListCell.swift +0 -4300
@@ -0,0 +1,978 @@
1
+ # NativeList Style Spec
2
+
3
+ Status: **shared style contract implemented on Web, iOS and Android through the
4
+ registered template renderers**. Consolidated on 2026-09-22 from PRs #107, #109,
5
+ #110, #111 and #112, on main `feac35eb6`; the renderer migration and its dated
6
+ per-stage runtime acceptance are recorded in [SPEC.md](SPEC.md) and
7
+ [DESIGN.md](DESIGN.md). The full §9 matrix (pixel parity, localized/RTL text and
8
+ font scaling on every template) has not been executed as one pass.
9
+ Use symbol names as source anchors. Tables of legacy defaults below are an
10
+ inventory, not evidence of three-platform visual acceptance.
11
+
12
+ This document is the shared design vocabulary for `@onekeyfe/react-native-native-list`.
13
+ iOS, Android, and Web each own a separate renderer, and nothing in the build forces
14
+ them to agree. This spec is therefore **authoritative by review, not by codegen**.
15
+ It records what a value is called, what it should be, and what each platform
16
+ currently does — so a divergence is visible in review instead of on a device.
17
+
18
+ See [DESIGN.md](DESIGN.md) for the architecture and ownership model.
19
+ Start with the [illustrated template catalog](ROW_TEMPLATES.md) for each template's
20
+ purpose, fields, variants, fixed structure and allowed local styling.
21
+
22
+ ## 1. Why this exists
23
+
24
+ The current `RowModel` union declares **12** row types, including `walletGroup`
25
+ and the structural `sectionHeader`. Presentation variants are not additional row
26
+ types. Every
27
+ template hard-codes its own typography and geometry in three places, and the three
28
+ vocabularies have drifted:
29
+
30
+ - The package's theme keys and the application's design tokens are **two different
31
+ names for the same colors**. Every consumer writes a mapping table by hand.
32
+ - The application has a named type scale (`$bodyMd`, `$headingSm`, …). The list has
33
+ ten-plus bare font sizes and references the scale nowhere. "One step smaller" has
34
+ no shared meaning.
35
+ - The same concept resolves to different numbers per platform (see §6).
36
+
37
+ This branch extends the original Market style API to the other row types. A
38
+ template defines structure; a local style parameter customizes a named part of
39
+ that structure. Header/footer placement, sections, scrolling and the indexed bar
40
+ belong to the list container, independently of the content template.
41
+
42
+ ### 1.1 Ownership contract
43
+
44
+ ![List container owns common capabilities; templates own only their internal slots](images/list-capabilities.svg)
45
+
46
+ | Owner | Responsibility | Must not control |
47
+ | --- | --- | --- |
48
+ | List container | Viewport, header/footer placement, sections, scroll position, refresh/load more, indexed bar and list chrome | The business meaning of a content row |
49
+ | Layout | Orientation, row allocation, grid spans/table columns, content padding and item spacing | Typography of a particular row field |
50
+ | Row template + variant | Slot order, hierarchy, required/optional fields, compression and bounded line counts | Whether the list has a header, footer, section index or scroll API |
51
+ | `row.style` | Allowlisted typography, colors, local metrics and explicit container height | Arbitrary children, flex direction, positioning, reparenting or list-level geometry |
52
+
53
+ **Normative requirement:** replacing an `identity` row with a `market` or
54
+ `dataRow` in the same valid container must not remove that container's common
55
+ capabilities. Compatibility conditions may depend on layout/orientation (for
56
+ example the indexed bar requires vertical sections), not a particular business
57
+ row template. This requirement does not claim every current renderer satisfies it.
58
+ Current API limits and implementation gaps are explicit in §5 and §6.5.
59
+
60
+ ### 1.2 Row template and row style are different contracts
61
+
62
+ - **Row template** (`row.type` plus its declared `variant`/`presentation`) defines
63
+ the content model and structure: slot order, hierarchy, optional parts, and
64
+ interactions. `identity`, `market` and `dataRow` are templates. Changing a
65
+ template is a model change, not a style change.
66
+ - **Row style** (`row.style`) customizes existing semantic fields and local
67
+ metrics within that template. For example, `identity.style.title.fontSize`
68
+ changes title typography; `image.width` changes the primary visual slot.
69
+ A style cannot add a field, reorder slots or change a column's weight.
70
+ - **List capabilities** belong to the container. Header/footer placement,
71
+ sections, scrolling, indexed bar, selection and list chrome do not belong to
72
+ either a content template or its row style.
73
+
74
+ The configurable property names, valid values, semantic targets and precedence
75
+ are the same on Web, iOS and Android. A template exposes only parameters for parts
76
+ it owns. Unsupported keys are rejected by the shared validator before
77
+ serialization; optional or variant-specific parts that are absent are not created
78
+ by styling them. See the complete applicability matrix in §4.1.
79
+
80
+ The 12 templates each have a definition and schematic in ROW_TEMPLATES.md.
81
+ Overflow fitting remains the integrating developer's responsibility: choose
82
+ fitting text/image/padding values and row heights. `style.container.height` can
83
+ define a new row height; omission preserves the model/template measurement rules.
84
+ Existing measured templates account for explicit text/image/padding metrics.
85
+ Automatic overflow fitting, combination-fit rejection and automatic font shrinking
86
+ remain outside this contract.
87
+ The five source PRs are closed; implementation continues in the consolidated PR.
88
+
89
+ ## 2. How this spec is enforced
90
+
91
+ The three renderers cannot constrain each other, but they all read the same
92
+ `snapshotJson`. That boundary is the single enforcement point.
93
+
94
+ | Layer | Enforced by | Covers |
95
+ | --- | --- | --- |
96
+ | Token names and values | `src/validation.ts` resolves tokens to numbers before serialization | §3 |
97
+ | Which keys a template accepts | `validateSnapshot` rejects keys not declared for that row type | §4 |
98
+ | Numeric bounds | `validateSnapshot` throws on out-of-range values | §3, §4 |
99
+ | Per-platform default values | **This document + PR review** | §4, §5 |
100
+ | Known divergences | **This document + PR review** | §6 |
101
+
102
+ A style token is resolved on the JavaScript side. `{ token: '$bodyLg' }` becomes
103
+ `{ fontSize: 16, lineHeight: 24, fontWeight: 'regular' }` before it crosses the
104
+ bridge — and an explicit `fontSize` alongside the token wins. Native renderers
105
+ never learn the token vocabulary and therefore cannot drift from it. Raw numeric
106
+ overrides stay available for pixel-parity work. Resolution is idempotent, and a
107
+ snapshot with nothing to resolve is returned unchanged, so the common path keeps
108
+ object identity.
109
+
110
+ ### Status
111
+
112
+ | Layer | State |
113
+ | --- | --- |
114
+ | Per-template key validation, token resolution, patch support | Shared TypeScript/runtime contract; identical for all three renderers |
115
+ | Text roles and local box/image parameters | Implemented for the applicable template slots in §4.1; absent optional slots remain absent |
116
+ | `dataRow` column styles | Independent primary/secondary text on all platforms, in linear and table layouts |
117
+ | Sticky header styles | Android uses the same row view/binder as normal headers; iOS and Web also reuse their row renderers |
118
+ | `listStyle.separator` / `groupCornerRadius` | Initial/update propagation and actual-group scope aligned |
119
+ | Row heights and overflow | `style.container.height` overrides `row.height`; omission retains template measurement, including existing style-dependent sizing; caller owns content fitting |
120
+ | Legacy baseline dimensions | Inventory in §4/§6; explicit style values use logical units independently of Android's legacy list scale |
121
+ | Rendered acceptance | Per-stage renderer runtime cases are recorded in SPEC.md/DESIGN.md; source and unit checks alone do not establish the full §9 pixel/interaction matrix |
122
+
123
+ Each renderer maps its own model fields to owned views. There is no global
124
+ cross-template style-slot map or shared legacy text-view pool. §4 remains the
125
+ contract for the Swift, Kotlin and Web implementations; regression tests check
126
+ semantic isolation and style set/clear behavior.
127
+
128
+ The example application's **Native List Row Style** page has plain/styled pairs
129
+ for all 12 types, plus list chrome and an explicit-height example. Its wallet
130
+ group styles the parent member while leaving a child unstyled to expose leakage.
131
+ Migration/runtime acceptance is recorded in DESIGN.md; the checklist in §9 still
132
+ applies to future changes. The catalog
133
+ illustrations are structural diagrams, not screenshots or proof of rendering parity.
134
+
135
+ ## 3. T1 — Design tokens
136
+
137
+ ### 3.1 Color tokens
138
+
139
+ This table maps application token names to `NativeListTheme` keys by meaning.
140
+ It does not add runtime aliases: consumers still pass the actual theme keys and
141
+ resolved colors; `style.color` does not resolve application color-token names.
142
+
143
+ | Application token | `NativeListTheme` key | Role |
144
+ | --- | --- | --- |
145
+ | `text` | `primaryText` | Primary label |
146
+ | `textSubdued` | `secondaryText` | Secondary label |
147
+ | `textDisabled` | `disabledText` | Disabled / timestamp |
148
+ | `textInverse` | `inverseText` | Text on inverted ground |
149
+ | `textSuccess` | `positive` | Positive amount |
150
+ | `textCritical` | `negative` | Negative amount |
151
+ | `textCaution` | `caution` | Warning text |
152
+ | `textInfo` | `info` | Informational text, search highlight |
153
+ | `bgApp` | `background` | List ground |
154
+ | `bg` | `rowBackground` | Row ground |
155
+ | `bgActive` | `rowSelectedBackground`, `rowPressedBackground` | Selected / pressed row |
156
+ | `bgSubdued` | `subduedBackground` | Card and group ground |
157
+ | `bgStrong` | `strongBackground` | Chip / badge ground |
158
+ | `bgInverse` | `inverseBackground` | Inverted ground |
159
+ | `bgCriticalSubdued` | `criticalBackground` | Failed-state chip |
160
+ | `bgCautionSubdued` | `cautionBackground` | Caution chip |
161
+ | `borderSubdued` | `separator` | Separator, group border |
162
+ | `icon` | `icon` | Icon default |
163
+ | `iconSubdued` | `iconSubdued` | Secondary icon |
164
+ | `iconActive` | `accent` | Accent — **name mismatch, see §6** |
165
+
166
+ ### 3.2 Typography scale
167
+
168
+ Sizes below are the application's scale. The "used by" column records which list
169
+ slots already sit on that step; slots that do not are listed in §4.
170
+
171
+ | Token | Size / line height | Weight | Used by |
172
+ | --- | --- | --- | --- |
173
+ | `$headingXl` | 24 / 32 | semibold | `metricCard.value` (size `large`) |
174
+ | `$headingLg` | 20 / 28 | semibold | — |
175
+ | `$headingMd` | 18 / 24 | semibold | `sectionHeader` gallery, `metricCard.value` |
176
+ | `$headingSm` | 16 / 24 | medium | `identity.title`, `market.title`, `market.price` |
177
+ | `$headingXs` | 14 / 20 | semibold | `message.title`, `sectionHeader` default |
178
+ | `$bodyLg` | 16 / 24 | regular | `action.title`, `sectionHeader` summary |
179
+ | `$bodyMd` | 14 / 20 | regular | `identity.subtitle`, `message.body`, `market.change` |
180
+ | `$bodySm` | 12 / 16 | regular | `rail.title`, `market.subtitle`, `identity.badge` |
181
+ | `$bodyXs` | 11 / 16 | regular | `metricCard` labels, `market` badges |
182
+
183
+ Weight names map to the bundled Roobert faces: `regular`, `medium`, `semibold`,
184
+ `bold`. No other family is available; `fontFamily` is not part of the style surface.
185
+
186
+ ### 3.3 Spacing, radius, bounds
187
+
188
+ | Kind | Allowed values | Bound |
189
+ | --- | --- | --- |
190
+ | Padding / gap | finite number | `0…64` |
191
+ | `lineGap` (title to subtitle) | finite number | `0…16` |
192
+ | Font size | finite number | `8…48` |
193
+ | Line height | finite number | `8…64` |
194
+ | Corner radius | finite number | `0…80` |
195
+ | Image edge | finite number | `1…160` |
196
+
197
+ These are the validator's numeric bounds, not a guarantee that every combination
198
+ fits a row. Fractional values are accepted; native rounding can differ. See §7.1
199
+ for the stronger layout-safety contract and its current enforcement limits.
200
+
201
+ Radius steps in use: `4` (chip), `8` (rail, small visual), `10` (visual `rounded`),
202
+ `12` (row, card), `16` (media tile), `20` (wallet sidebar row on iOS only — §6).
203
+
204
+ ## 4. T2 — Template style surface
205
+
206
+ `style.X` modifies the rendering of the **model field named `X`**, regardless of
207
+ which physical view carries it. Each registered template renderer owns its views
208
+ and maps the key to the view rendering that field. The removed legacy native
209
+ views shared slots (for example legacy `metricCard` rendered its *value* through
210
+ the title label and its *title* through the subtitle label), which is why style
211
+ keys were never named after views. `market` follows the same rule with
212
+ `style.price` / `style.change`.
213
+
214
+ On Web the element rendering a field carries `data-nl-slot="<field>"`, so the
215
+ style pass resolves a slot by name rather than by CSS class — on its own,
216
+ `.ok-native-list-secondary` is the identity subtitle, the rail status, a metric
217
+ label, a data column's secondary text, and a system message. iOS and Android express
218
+ the same semantic mapping in native code. Singular style roles such as `badge`
219
+ and `value` can refer to repeated badges or values in `trailing`; their precise
220
+ meaning is defined in the [catalog](ROW_TEMPLATES.md).
221
+
222
+ ### 4.1 Shared configurable properties
223
+
224
+ Every text role listed below accepts the same `NativeListTextStyle`:
225
+ `token`, `fontSize`, `fontWeight`, `color`, `lineHeight`, `lines`, `truncate`,
226
+ `alignment`, `verticalAlignment`, `offsetY`.
227
+ Weights are `regular | medium | semibold | bold`; line counts are `1 | 2 | 3`;
228
+ `start | center | end` alignment follows layout direction on all three
229
+ platforms (`end` is the left edge in RTL). Colors must be
230
+ `#RRGGBB` or `#RRGGBBAA`, the common native/Web color format. Omitted properties
231
+ preserve the template's values. Explicit properties override token values,
232
+ presentation defaults and rich-text runs only for that property.
233
+
234
+ Every supported image role accepts `width`, `height`, `shape`, `cornerRadius`,
235
+ `contentFit`. Shape is `circle | rounded | square`; explicit corner radius wins.
236
+ Fit is `cover | contain | fill | center`. These parameters target the primary
237
+ visual, not network badges, corner decorations, message thumbnails or nested
238
+ metric images. A media tile keeps its square default when only width is set;
239
+ an explicit image height overrides that aspect. Image/text/padding metrics can
240
+ participate in an existing intrinsic/measured template, such as Market or message.
241
+ They do not override an explicit row height. Use `style.container.height` to
242
+ define a new outer height for any template.
243
+
244
+ In the matrix, **padding** means both `horizontalPadding` and `verticalPadding`.
245
+ Each cell describes the same public contract on **Web + iOS + Android**.
246
+
247
+ | Template | Semantic text roles | Local box/image properties |
248
+ | --- | --- | --- |
249
+ | `identity` | title, subtitle, tertiary, badge, value, valueSecondary | padding, leadingGap, lineGap, titleBadgeGap, trailingGap, image |
250
+ | `walletGroup` | None; members own their identity styles | padding |
251
+ | `rail` | title, badge, status | padding, leadingGap, titleBadgeGap, trailingGap, image |
252
+ | `activity` | title, description, status, primaryAmount, secondaryAmount | padding, leadingGap, lineGap, trailingGap, image |
253
+ | `message` | title, body, time | padding, leadingGap, lineGap, image |
254
+ | `dataRow` | columns, columnSecondary, index | padding, leadingGap, lineGap, titleBadgeGap, image |
255
+ | `market` | title, subtitle, price, change | padding, leadingGap, lineGap, titleBadgeGap, trailingGap, image; Market-specific properties below |
256
+ | `mediaTile` | title, subtitle, badge | padding, leadingGap, lineGap, image |
257
+ | `metricCard` | title, value, subtitle, trend | padding, leadingGap, lineGap, image |
258
+ | `sectionHeader` | title, subtitle, value | padding, lineGap, trailingGap |
259
+ | `action` | title, value | padding, leadingGap, trailingGap, image |
260
+ | `system` | title, message, actionText | padding, lineGap |
261
+
262
+ Market additionally exposes `titleBadgeLayout: inline`, `contentTrailingGap`,
263
+ `subtitleTrailingPadding`, `changeWidth`, `changeHeight`, `changeCornerRadius`, preserving
264
+ its existing template-specific style surface on every platform.
265
+
266
+ Gap meanings are structural: `leadingGap` separates the primary visual from
267
+ content; `lineGap` separates content text rows (primary/secondary per data column);
268
+ `titleBadgeGap` separates a title and its badges (vertical for walletSidebar);
269
+ `trailingGap` separates trailing values/controls (title/badge to status for rail).
270
+ `walletGroup` padding applies only to the group; it never cascades into members.
271
+ Composite metric-card `lineGap` separates heading/metric blocks and dividers; it
272
+ does not change the typography or inner label/value gap of each metric.
273
+
274
+ Variant applicability is also shared: composite metric-card `title` styles its
275
+ heading, while standard-card `value`/`subtitle`/`trend` do not style nested metrics;
276
+ `system.title` applies to warning, `actionText` to the native retry button. Web
277
+ (legacy) draws no retry button for any presentation, so `style.actionText` has
278
+ no Web target and is ignored there; iOS/Android style their button. A missing leading visual,
279
+ badge, subtitle or trailing value remains absent. New nested-metric/thumbnail
280
+ style roles require a separately declared cross-platform contract.
281
+
282
+ All numeric overrides use logical units (CSS px / iOS pt / Android dp), including
283
+ explicit typography. Legacy Android scaling may affect omitted defaults, but must
284
+ not rescale an explicit style value. Physical-pixel/font-rasterization differences
285
+ remain platform-specific. See §3.3 for bounds and §7 for fitting responsibility.
286
+
287
+ ### 4.1.1 Row container (all 12 templates)
288
+
289
+ ![Container surface, padding, semantic gaps and text regions](images/row-style-regions.svg)
290
+
291
+ `style.container` addresses the outer row surface, including `walletGroup` and
292
+ rows used as headers/footers. It never overwrites member styles; opacity naturally composites the entire
293
+ container including descendants.
294
+
295
+ | Property | Values / default when supplied | Meaning |
296
+ | --- | --- | --- |
297
+ | `height` | `0…4096` logical units | Explicit row allocation; takes precedence over `row.height` and template measurement |
298
+ | `backgroundColor` | `#RRGGBB` / `#RRGGBBAA` | Resting row background |
299
+ | `opacity` | `0…1` | Opacity of the whole row, multiplied by disabled dimming |
300
+ | `cornerRadius` | `0…80` logical units | All four outer row corners |
301
+ | `borderWidth` | `0…8` logical units | Inward border; no effect on content allocation |
302
+ | `borderColor` | `#RRGGBB` / `#RRGGBBAA` | Border color; transparent if width is supplied alone |
303
+ | `contentVerticalAlignment` | `top / center / bottom` | Align the row's content within its padded height; preserve slot order and orientation |
304
+
305
+ Omission preserves the template's existing value. Explicit container background
306
+ and opacity take precedence over legacy `row.backgroundColor` / `row.opacity`.
307
+ Existing top-level fields remain accepted; new integrations use `style.container`.
308
+ An explicit background is the resting surface (including selection); existing
309
+ pressed feedback remains visible. Radius and border remain during press/selection.
310
+ Explicit radius overrides grouped corner geometry for this row only; it does not
311
+ change `listStyle.groupCornerRadius`. `backgroundFullWidth` remains a layout/model
312
+ option and uses the resolved resting background. A border never changes padding.
313
+
314
+ `horizontalPadding` / `verticalPadding` remain on `row.style`; there is no second
315
+ padding entry under `container`. Outer margin, width, positioning, visibility,
316
+ flex direction, column weights and independent hit-area changes are not container
317
+ style properties. `height` changes the row frame and its normal row hit area; it
318
+ does not rearrange slots or resize their contents.
319
+ Shadows, gradients and per-corner/per-edge styling are outside this version.
320
+
321
+ ```ts
322
+ // Inside an identity row; the style itself can allocate a new row height.
323
+ style: {
324
+ container: {
325
+ height: 112, backgroundColor: '#EDF6FF', cornerRadius: 12,
326
+ borderWidth: 1, borderColor: '#8DB7E4', contentVerticalAlignment: 'center',
327
+ },
328
+ horizontalPadding: 16, verticalPadding: 12, leadingGap: 10, lineGap: 4,
329
+ title: { lines: 1, truncate: 'tail', alignment: 'start' },
330
+ subtitle: { lines: 3, truncate: 'clip', lineHeight: 18, offsetY: -1 },
331
+ }
332
+ ```
333
+
334
+ ### 4.1.2 Text wrapping, truncation and alignment
335
+
336
+ | Property | Shared meaning |
337
+ | --- | --- |
338
+ | `lines` | Maximum 1, 2 or 3 lines, not a forced reservation of that many lines |
339
+ | `truncate` | `tail` (ellipsis at the end of the last visible line) or `clip` (no ellipsis); default `tail` when line/truncation controls are supplied |
340
+ | `lineHeight` | Line box height inside one text field, distinct from `lineGap` between fields |
341
+ | `alignment` | Horizontal `start / center / end` inside the text region, respecting direction |
342
+ | `verticalAlignment` | `top / center / bottom` for the complete text block inside its allocated text region; no effect without spare height |
343
+ | `offsetY` | `-8…8` logical units of optical translation; does not change measurement, sibling spacing or hit areas |
344
+
345
+ Single-line text does not wrap; explicit CR/LF line breaks become spaces (CRLF is
346
+ one break). Multi-line text wraps naturally, preserves explicit breaks, and
347
+ truncates after the last permitted line. Rich runs share one line budget. Styling
348
+ one field must not alter adjacent badges, values or member rows. Middle/head
349
+ truncation is not exposed in this version; it needs a separate cross-platform
350
+ contract rather than platform-specific fallback.
351
+
352
+ Every existing declared text role accepts the same range `1…3`; each template's
353
+ omitted defaults remain documented in the inventory. Optional absent fields stay
354
+ absent. `style.title.lines` overrides legacy `titleLines`,
355
+ `style.subtitle.lines` overrides `subtitleLines`, and `style.body.lines` overrides
356
+ `message.bodyLines`. New callers should use styles for all three. Removing a
357
+ style restores the model/default value. `patch.changes.style` replaces the whole
358
+ style object; `{}` clears it, including container and text overrides.
359
+
360
+ Height precedence is `style.container.height` → `row.height` → existing
361
+ template/layout measurement. A style height is an exact logical height, not a
362
+ minimum. It applies to every template, normal rows, grid/horizontal layouts,
363
+ wallet members, sticky headers and fixed footers. Clearing the whole style or
364
+ omitting `container.height` restores the original model height or measurement;
365
+ the original `row.height` is never rewritten. Wallet-group compact reorder keeps
366
+ its temporary drag height and restores the styled height afterward. Android
367
+ uses unscaled logical dp, with `heightRounding` when supplied (nearest otherwise).
368
+
369
+ An explicit height is not automatically expanded. With neither explicit height,
370
+ styles may change an existing measured/intrinsic row height. Existing intrinsic/measured
371
+ paths must account for explicit text metrics within their declared measurement
372
+ model. Font shaping and exact wrap points can vary across platform fonts; the
373
+ property meanings and maximum line counts must agree.
374
+
375
+ `container.contentVerticalAlignment` moves the existing content arrangement as a
376
+ whole within the row; text `verticalAlignment` moves only that field's glyph block.
377
+ They must not be substituted for each other. For horizontal rows, the container
378
+ alignment aligns existing immediate content groups on the vertical axis.
379
+
380
+ ### 4.1.3 Spacing and admission rules
381
+
382
+ Spacing is always between named semantic regions, never arbitrary descendants:
383
+ `leadingGap` is visual-to-content, `titleBadgeGap` is title-to-badge, and
384
+ `trailingGap` is between the template's trailing items (rail: title/badge-to-status).
385
+ `lineGap` is between text fields; the existing composite metric variant explicitly
386
+ uses it between its heading/metric blocks. New templates must identify both
387
+ endpoints in their diagram and must not invent a different meaning for these keys.
388
+ List `layout.itemSpacing` remains the gap between rows.
389
+
390
+ Before adding a template or style property, update the catalog with its diagram,
391
+ semantic fields, defaults, applicable variants, units/ranges, precedence and
392
+ absent-field behavior. Reuse this shared text/image/container vocabulary. A new
393
+ property requires Web/iOS/Android implementations, validation for snapshot and
394
+ patch paths, and focused checks for replacement/reset, recycling, rich text,
395
+ member isolation, sticky headers and fixed footers where applicable. Source/build
396
+ checks and rendered acceptance must be reported separately. No renderer may
397
+ silently reinterpret an unsupported value.
398
+
399
+ ### 4.2 Existing defaults (inventory, not a pixel parity claim)
400
+
401
+ Legend: **=** inventoried defaults agree; **≠** legacy divergence, see §6.
402
+
403
+ ### identity
404
+
405
+ | Key | Default | iOS | Android | Web |
406
+ | --- | --- | --- | --- | --- |
407
+ | `title` | `$headingSm` | 16 medium / 24 | sp(16) medium / dp(24) | 16px / 20px / 600 **≠** |
408
+ | `subtitle` | `$bodyMd` | 14 regular / 20 | sp(14) regular / dp(20) | 14px / 20px **=** |
409
+ | `tertiary` | `$bodyMd` | 14 regular / 20 | sp(14) / dp(20) | 14px / 20px **=** |
410
+ | `subtitle` lines | model `subtitleLines` | default 1 | default **2** | default 1 **≠** |
411
+ | `badge` | `$bodySm` | 12 medium / 16, pad 8/2, r4 | sp(12) / dp(16), pad 8/2, r4 | 11px / 18px, pad 0 5, r5 **≠** |
412
+ | `value` | `$headingSm` | 16 medium | sp(16) medium / dp(24) | 14px / 20px / 500 **≠** |
413
+ | `valueSecondary` | `$bodyMd` | 14 regular | sp(14) regular / dp(20) | 14px **=** |
414
+ | box | pad 12/8, gap 12 | 12 / -12 / 8 / -8, spacing 12 | dp(12), dp(8) | `padding:8px 12px`, `gap:12px` **=** |
415
+ | `image` | 40 | 40×40 (32 for `network`) | 40 (32 for `network`) | 40px (32px for account) **=** |
416
+
417
+ ### identity · presentation `walletSidebar`
418
+
419
+ | Key | Default | iOS | Android | Web |
420
+ | --- | --- | --- | --- | --- |
421
+ | `title` | `$bodySm` | 12 regular / 16, centered | sp(12) / dp(16), centered | 12px / 16px / 400 **=** |
422
+ | `badge` | `$bodyXs` | 11 / 14, pad 6/2, r4 | sp(11) / dp(14), pad 6/2, r4 | 11px / 14px, pad 2 6, r4 **=** |
423
+ | `image` | 40 | 40×40, fallback 28 | 40, fallback sp(28) | 40px **=** |
424
+ | box | pad 4 | 4 / -4 / 4 / -4 | dp(4) × 4 | `padding:4px 8px` **≠** |
425
+ | row radius | 12 | 20 | 12 | 12 **≠** |
426
+
427
+ ### identity · presentation `accountSelector`
428
+
429
+ | Key | Default | iOS | Android | Web |
430
+ | --- | --- | --- | --- | --- |
431
+ | `title` | `$bodyLg` | 16 regular | sp(16) regular | 16px / 20px / 400 **=** |
432
+ | `subtitle` | `$bodyMd` | 14 regular / 20 | sp(14) / dp(20) | 14px / 20px / 400 **=** |
433
+ | `image` | 32 | 32×32 | 32 | 32px **=** |
434
+ | trailing icon | 38 | 38, margin −7 | dp(38), margin −dp(7) | 38px, margin −7px **=** |
435
+
436
+ ### identity · presentation `networkSelector`
437
+
438
+ | Key | Default | iOS | Android | Web |
439
+ | --- | --- | --- | --- | --- |
440
+ | `title` | `$headingSm` | 16 medium / 24 | sp(16) / dp(24) | 16px / 24px / 500 **=** |
441
+ | `image` | 32 | 32×32, fallback 19/27 semibold | 32, fallback sp(19)/dp(27) | 32px, fallback 19/27/600 **=** |
442
+ | checkbox | 20, r4, border 2 | 20×20, r4, border 2 | dp(20), r4f, stroke dp(2) | 20px, r4, border 2px **=** |
443
+
444
+ ### walletGroup
445
+
446
+ | Key | Default | iOS | Android | Web |
447
+ | --- | --- | --- | --- | --- |
448
+ | member gap | 12 | stack spacing 12 | topMargin dp(12) | `gap:12px` **=** |
449
+ | container radius | 12 | 20 | dp(12) | 12px **≠** |
450
+ | container border | 1, `borderSubdued`, drawn beneath members | 1 | dp(1) | 1px **=** |
451
+ | compact drag height | 68 | 68 | max(68, parent + 2 × inset) | 68 **≠** |
452
+
453
+ ### rail
454
+
455
+ | Key | Default | iOS | Android | Web |
456
+ | --- | --- | --- | --- | --- |
457
+ | `title` | `$bodySm` | 12 medium / 16 | sp(12) / dp(16) | 12px / 500 **=** |
458
+ | `badge` | `$bodySm` | 12 medium / 16 | sp(12) medium / dp(16) | — |
459
+ | `status` | `$bodySm` | 12 regular / 16 | sp(12) | — |
460
+ | box | pad 4, gap 6 | 4 / -4, spacing 6 | dp(4), spacing 6 | `padding:4px;gap:6px` **=** |
461
+ | `image` | 20 | 20×20 | 20 | 20px **=** |
462
+ | row radius | 8 | 8 | 8 | 8px **=** |
463
+
464
+ ### activity
465
+
466
+ | Key | Default | iOS | Android | Web |
467
+ | --- | --- | --- | --- | --- |
468
+ | `title` | `$headingSm` | lineHeight 24 | dp(24) | 16px / 20px **≠** |
469
+ | `description` | `$bodyMd` | lineHeight 20, 2 lines | dp(20), 2 lines | 14px / 20px **=** |
470
+ | `primaryAmount` | `$headingSm` | 16 medium / 24, tabular | sp(16) medium / dp(24) | 16px / 20px **≠** |
471
+ | `secondaryAmount` | `$bodyMd` | 14 regular / 20, tabular | sp(14) / dp(20) | 14px **=** |
472
+ | footer action | `$bodyMd` | 12 medium, r8, pad 4/8 | sp(14) semibold, r8f, pad 8/4 | 12px / 16px / 600 **≠** |
473
+
474
+ ### message
475
+
476
+ | Key | Default | iOS | Android | Web |
477
+ | --- | --- | --- | --- | --- |
478
+ | `title` | `$headingXs` | 14 semibold / 20, 2 lines | sp(14) semibold / dp(20) | 15px / 20px / 600 **≠** |
479
+ | `body` | `$bodyMd` | 14 regular / 20 | sp(14) / dp(20) | 13px / 18px **≠** |
480
+ | `time` | `$bodySm` | 12 / 16, `textDisabled` | sp(12) / dp(16), `disabledText` | 11px / 16px **≠** |
481
+ | `image` | 28 | 28×28 | 28 | 40px **≠** |
482
+ | thumbnail | 64, r6 | 64×64, r6 | dp(64), r6f | 64px, r10 **≠** |
483
+ | box | pad 12/16 | 16 / -16 | dp(12), dp(16) | `padding:8px 12px` **≠** |
484
+
485
+ ### dataRow
486
+
487
+ | Key | Default | iOS | Android | Web |
488
+ | --- | --- | --- | --- | --- |
489
+ | `columns` (linear) | `$bodyMd` | 14 medium / 20 | sp(16) medium / dp(20) | 16px / 500 **≠** |
490
+ | `columns` (table) | `$bodyMd` | 14 medium / 20 | sp(14) medium / dp(20) | 16px / 500 **≠** |
491
+ | column secondary | `$bodySm` | 12 regular / 16 | sp(12) / dp(16) | — |
492
+ | `index` | `$bodySm` | — | dp(32) slot | 13px, 28px slot **≠** |
493
+ | favorite icon | 20 | 20×20 | dp(20) | 24px / 22px glyph **≠** |
494
+ | box | pad 12/6 | table 20/10 | table dp(20)/dp(10) | `padding:6px 12px` **=** |
495
+
496
+ ### market
497
+
498
+ Already fully data-driven through `MarketRowStyle`. The values below are the
499
+ defaults applied when `style` is absent.
500
+
501
+ | Key | Default | All platforms |
502
+ | --- | --- | --- |
503
+ | `title` | `$headingSm` | 16 / 24 / medium |
504
+ | `subtitle` | `$bodyMd` | 14 / 20 / regular |
505
+ | `price` | `$headingSm` | 16 / 24 / medium |
506
+ | `change` | `$bodyMd` | 14 / 20 / medium, block 80×32 r8 |
507
+ | box | pad 20/12, gap 14 | perp 16, gap 8 |
508
+ | `image` | 32 (stock 40) | circle |
509
+
510
+ ### mediaTile
511
+
512
+ | Key | Default | iOS | Android | Web |
513
+ | --- | --- | --- | --- | --- |
514
+ | `title` | `$headingSm` | 16 medium / 24 | sp(16) / dp(24) | 16px / 500 **=** |
515
+ | `subtitle` | `$bodySm` | 12 regular / 16 | sp(12) / dp(16) | 12px **=** |
516
+ | `badge` | `$bodyMd` | 14 medium, h24, r10 | sp(14), h dp(24), r10f | — |
517
+ | image radius | 10 | 10 | dp(10) | 10px **=** |
518
+ | tile radius | 16 | 16 | 16 | 16px **=** |
519
+ | network badge | 14 | 14×14, r7 | dp(14), circle | 14px, r50% **=** |
520
+
521
+ ### metricCard
522
+
523
+ `title` is the small label; `value` is the large number. Style keys follow these
524
+ model fields, whatever the renderer's internal view names are.
525
+
526
+ | Key | Default | iOS | Android | Web |
527
+ | --- | --- | --- | --- | --- |
528
+ | `title` | `$bodyXs` | 11 regular, `textDisabled` | sp(11), `disabledText` | 14px **≠** |
529
+ | `value` | `$headingMd` | 18 semibold (24 when `size: large`) | sp(18) / sp(24) semibold | 22px / 28px / 700 **≠** |
530
+ | `trend` | `$bodySm` | 12, tone colored | sp(12), tone colored | — |
531
+ | metric label | `$bodyXs` | 11 regular / 14 | sp(11) / dp(14) | — |
532
+ | metric value | `$bodyMd` | 14 medium / 20 | sp(14) medium / dp(20) | 18px / 600 **≠** |
533
+ | card padding | 14 | 14 | dp(14) | 12px **≠** |
534
+ | card radius | 12 | 12 | 12 | 12px **=** |
535
+ | progress bar | 4, r2 | 4, r2 | dp(4), r dp(2) | 4px, r2 **=** |
536
+
537
+ ### sectionHeader
538
+
539
+ | Variant | Default | iOS | Android | Web |
540
+ | --- | --- | --- | --- | --- |
541
+ | default | `$headingXs` | 14 semibold / 20 | sp(14) semibold / dp(20) | 13px / 18px / 700 **≠** |
542
+ | `summary` | `$bodyLg` | 16 medium / 24 | sp(16) medium / dp(24) | 16px / 500 **=** |
543
+ | `gallery` | `$headingMd` | 18 semibold / 24 | sp(18) semibold / dp(24) | 16px **≠** |
544
+ | `history` | `$bodySm` | 12 semibold / 16, tracking 0.8 | sp(12) semibold / dp(16), tracking 0.8 | 12px / 16px **=** |
545
+ | table layout | `$bodyXs` | 11 regular / 14 | sp(11) regular / dp(14) | — |
546
+ | `value` | `$bodyLg` | 16 / 24 | sp(16) medium / dp(24) | 14px / 20px / 500 **≠** |
547
+
548
+ ### action
549
+
550
+ | Key | Default | iOS | Android | Web |
551
+ | --- | --- | --- | --- | --- |
552
+ | `title` | `$bodyLg` | 16 / 24 | sp(16) / dp(24) | 15px / 600 **≠** |
553
+ | `title` (accountSelector) | `$bodyLg` | 16 medium / regular | medium / regular | 16px / 24px / 400 **=** |
554
+ | icon slot | 40 (32 accountSelector) | 40 / 32, glyph 24 | 40 / 32, glyph dp(24) | 40px / 32px **=** |
555
+ | box | pad 16 | — | — | `padding:0 16px` **=** |
556
+
557
+ ### system
558
+
559
+ | Variant | Default | iOS | Android | Web |
560
+ | --- | --- | --- | --- | --- |
561
+ | `loading` skeleton | — | marks 32 / 80×16 / 60×12 / 80×18 | same | same **=** |
562
+ | `loading` spinner | 20 | 20, market 32 | dp(20), market 32 | 20px, market 32px **=** |
563
+ | `retry` | `$bodyMd` | 14 medium / 20, r14 | sp(14) / dp(20), r16f | 12px **≠** |
564
+ | `noMatch` / `end` | `$bodyMd` | 14 regular / 20 | sp(14) / dp(20) | 14px **=** |
565
+ | `warning` | `$bodyMd` | 14 medium + regular / 20 | sp(14) / dp(20) | 14px / 20px **=** |
566
+ | `spacer` | — | `height` only | `height` only | `height` only **=** |
567
+
568
+ ### Carrier coverage
569
+
570
+ Rows in `snapshot.rows`, `snapshot.fixedFooter`, and `snapshot.emptyState` reuse
571
+ the row binders. The two standalone descriptors currently accept only `action`
572
+ or `system` in the public TypeScript API. `sectionHeader` is a structural row in
573
+ `rows`. Reusing a binder does not transfer ownership of footer positioning,
574
+ empty-state placement or header pinning to the row template. Android's pinned
575
+ header reuses the ordinary header row view and callbacks (§6.3).
576
+
577
+ ## 5. T3 — List chrome
578
+
579
+ Chrome is not part of a row. This branch has `snapshot.listStyle` for separator
580
+ inset/color and grouped-card radius. Common capabilities remain in their existing
581
+ container APIs; being public does not require placing everything in `listStyle`.
582
+
583
+ ### 5.1 Common capabilities and current API
584
+
585
+ | Capability | Existing entry point | Contract and current limit |
586
+ | --- | --- | --- |
587
+ | List header | Leading structural/summary rows; `CollapsiblePagerView.header` and `stickyHeader` for composed React content | Header placement belongs to the container. There is currently **no generic public `listHeader`/`ListHeaderComponent` prop**; arbitrary React children inside NativeList are not part of the current template API |
588
+ | List footer | A trailing `action`/`system` row scrolls with content; `snapshot.fixedFooter` stays outside the scrolling rows | Available independently of the content row type. `fixedFooter` currently accepts only `ActionRow \| SystemRow`; row style controls its content, not its fixed placement |
589
+ | Sections / sticky headers | `layout.kind: 'sectioned'`, `sectionHeader.sectionKey`, member `sectionKey`, `layout.stickyHeaders`, header `sticky` | A section can contain different content templates. `sectionHeader` describes the heading; the container owns membership, positioning and pinning. Complex headers share the normal row renderer when pinned; summary headers remain non-sticky on all three platforms (§6.3) |
590
+ | Scroll and restoration | `NativeListRef.scrollToKey/Index/Item/Offset/End/Location`, `initialScrollKey/Index` and view position/offset props | Shared viewport behavior, independent of row template. Style must not silently change measured offsets or invalidate stable row keys |
591
+ | Indexed bar | `capabilities.sectionIndex`, `sectionHeader.indexTitle`; Web has `webSectionIndexContainerRef` | Requires vertical `sectioned` layout; entries follow indexed header order and target header keys. No dependency on `identity`, `market`, or another content template |
592
+ | Refresh / pagination | `capabilities.pullToRefresh/refreshing/loadMore/endReachedThreshold`, `onRefresh`, `onEndReached`, `setRefreshing` | Container gestures/state; indicator implementation may differ by platform |
593
+ | Empty state | `snapshot.emptyState` (`ActionRow \| SystemRow`) | Container decides when to display it, independently of the regular content templates |
594
+ | Selection / reorder | `snapshot.selection`, `capabilities.reorderable`, selection/reorder events | Common orchestration; each row's available controls/draggable support still depend on its declared model |
595
+ | Separator / grouped cards | `listStyle`, row `separator`, `groupId` and `groupPosition` | Styling scope is the list; group membership is data. A custom group radius must not change unrelated template cards |
596
+
597
+ The header/footer slots in the diagram are ownership boundaries, not newly
598
+ available props. Do not introduce `marketHeader`, `identityFooter`, or per-template
599
+ scroll/index configuration. A future public header or broader footer carrier must
600
+ extend the container contract once, with the same placement rules for all content.
601
+
602
+ `activity.footerActions` means buttons **inside one activity row**. It is unrelated
603
+ to the list footer. Similarly, `walletGroup` is a composite wallet template, not
604
+ the list's general section mechanism.
605
+
606
+ ### 5.2 Chrome surface
607
+
608
+ | Item | Data-driven today | iOS | Android | Web |
609
+ | --- | --- | --- | --- | --- |
610
+ | Content padding | yes (`layout.contentPadding*`) | `contentInset` | `setPadding` | `paddingValues` |
611
+ | Item spacing | yes (`layout.itemSpacing`) | flow layout spacing | `ItemSpacingDecoration` | layout gap |
612
+ | Separator | **yes** — `listStyle.separator.{inset,color}` | 1/scale, inset 60/12 | 1px, inset dp(60)/dp(12) | `1px`, class rule, **no inset** |
613
+ | Group card radius | **yes** — `listStyle.groupCornerRadius` | 20 / 12 | dp(12) | 12px |
614
+ | Section index rail | partial (`capabilities.sectionIndex`) | static constants, label 10 | `SECTION_INDEX_*_DP`, raw density | `SECTION_INDEX_*` + CSS |
615
+ | Section index preview | no | 48×48 r14, 22 semibold | preview w/h/margin constants | 48px r14, 22px |
616
+ | Pull to refresh | partial (`capabilities.pullToRefresh`) | `UIRefreshControl` | `SwipeRefreshLayout` | custom pill, 12px |
617
+ | Reorder preview | no | — | — | r12, shadow `0 4px 24px` |
618
+ | Reorder count badge | no | 24 h, r12, 12 semibold | dp(24), r dp(12), sp(12) semibold | 24px, r12, 12/22/600 |
619
+
620
+ The section index rail is three separate constant sets for one control; Android's
621
+ `sectionIndexDp()` deliberately never follows the source-scale switch.
622
+
623
+ `listStyle` carries only what all three platforms can honour. The rest of this table
624
+ is deliberately excluded rather than declared and half-implemented:
625
+
626
+ - **Pull to refresh** is a system control on both native platforms (`UIRefreshControl`,
627
+ `SwipeRefreshLayout`); only Web draws its own indicator.
628
+ - **Section index rail and preview** belong to `capabilities.sectionIndex`, which is
629
+ where their geometry should go if it is ever exposed, and are three independent
630
+ constant sets today.
631
+ - **Reorder preview and count badge** are drawn with platform-specific primitives —
632
+ Android paints the badge on `Canvas` inside `dispatchDraw`, Web uses a CSS overlay,
633
+ and iOS has neither.
634
+ - **Content padding and item spacing** are already `layout.contentPadding*` and
635
+ `layout.itemSpacing`; duplicating them here would give one value two homes.
636
+
637
+ Validated values: `separator.inset` is `0…64` logical units; `separator.color`
638
+ must be `#RRGGBB` or `#RRGGBBAA`, the same format as every row style color;
639
+ `groupCornerRadius` is `0…40` logical units. Any other `listStyle` or separator
640
+ key is rejected.
641
+
642
+ Every `listStyle` value is absent by default, and each platform keeps its own number
643
+ as the fallback, so an untouched list renders exactly as before. On Web that required
644
+ the inset separator to keep the transparent `border-bottom` for layout and paint the
645
+ visible line with a logical-inset overlay, rather than moving the row by a pixel.
646
+
647
+ ## 6. T4 — Known divergences
648
+
649
+ Registered, **not** fixed by this document. Changing any of these requires a PR
650
+ that cites this table and carries device verification.
651
+
652
+ ### 6.1 Row heights
653
+
654
+ Row height is computed from data by three independent implementations —
655
+ the iOS registered renderer measurement, Android registered row measurement, and
656
+ Web registered estimate/DOM measurement. Preserved defaults still differ:
657
+
658
+ | Template | iOS | Android | Web |
659
+ | --- | --- | --- | --- |
660
+ | `rail` | 40 | 28 | 40 |
661
+ | `activity` + footer actions | 100 | 104 | 100 |
662
+ | `dataRow` linear, no secondary text | 56 | 56 | 56 |
663
+ | `dataRow` linear + secondary text | 60 | **64** | 60 |
664
+ | `dataRow` table, no secondary text | 48 (56 − 8) | **52** (60 − 8) | 48 (56 − 8) |
665
+ | `dataRow` table + secondary text | 60 | 60 | 60 |
666
+ | `sectionHeader` `summary` | 68 | 80 | 68 |
667
+ | `sectionHeader` value + checkbox | 56 | 40 | 56 |
668
+ | `message`, `mediaTile`, composite `metricCard` | computed / 244 / 160+ | 0 (wrap content) | computed / 244 / 161 |
669
+
670
+ DataRow provenance (pre-migration engines at `52849b86f`): iOS
671
+ `RNCNativeListView.rowHeight()` used base 56, or 60 with secondary text, then
672
+ −8 in table layout without secondary text; legacy Web applied the same rule.
673
+ Android `NativeListRowView` used base 60 for every table row (−8 without
674
+ secondary text) and 64/56 for linear rows with/without secondary text. The
675
+ registered renderers return these values directly; `size` and explicit heights
676
+ apply as for other templates.
677
+
678
+ Message now uses its resolved text/spacing/image metrics for intrinsic
679
+ measurement; iOS measures the actual allocated column width and Web corrects
680
+ its conservative estimate from the rendered DOM. This explicitly fixes the old
681
+ Message estimator-only fallback differences. Explicit/model heights keep their
682
+ precedence. Other legacy mismatches remain: iOS `bindSystem()` uses 52/120 for a
683
+ market retry row where `rowHeight()` uses 44, and 88 for `noMatch` where
684
+ `rowHeight()` uses 44.
685
+
686
+ These values may have been tuned per platform on real devices. They are recorded
687
+ here and left alone.
688
+
689
+ ### 6.2 Typography and geometry
690
+
691
+ | Concept | iOS | Android | Web |
692
+ | --- | --- | --- | --- |
693
+ | `identity.title` | 16 medium / 24 | sp(16) medium / dp(24) | 16px / **20px** / **600** |
694
+ | `identity.badge` | 12 / 16, pad 8/2, r4 | sp(12) / dp(16), pad 8/2, r4 | **11 / 18, pad 0 5, r5** |
695
+ | `message.body` | 14 / 20 | sp(14) / dp(20) | **13 / 18** |
696
+ | `message` leading | 28 | 28 | **40** |
697
+ | `sectionHeader` default | 14 semibold / 20 | sp(14) semibold / dp(20) | **13 / 18 / 700** |
698
+ | `action.title` | 16 / 24 | sp(16) / dp(24) | **15 / 600** |
699
+ | `metricCard.value` | 18 semibold | sp(18) semibold | **22 / 28 / 700** |
700
+ | visual `rounded` radius | `min(10, h/4)` | `min(10, size/4)`; 8 for accountSelector | **10px**; 8 `!important` for account action; 8 for market |
701
+ | `walletSidebar` row radius | **20** | 12 | 12 |
702
+ | `identity` subtitle lines (no `subtitleLines`) | 1 | **2** | 1 (CSS single line) |
703
+ | pressed row corners | 12 all corners (rail 8, mediaTile 16 resting, wallet sidebar **20**), ignoring `groupPosition` | 12 all corners (rail 8, mediaTile 16 resting); no wallet-sidebar exception | background only; resting group/template radius kept **≠** |
704
+ | `walletGroup` row `backgroundColor` | ignored (`style.container.backgroundColor` or theme `subduedBackground`) | ignored (same) | applied inline **≠** |
705
+ | `walletGroup` `row.height` | ignored (members / `style.container.height`) | ignored (same) | honored **≠** |
706
+ | System retry | literal "Retry" button (non-Market); `actionText` button (Market) | same as iOS | no button for any presentation; message only, row press retries **≠** |
707
+ | separator default inset | 60 identity / 12 | dp(60) identity / dp(12) | **0** |
708
+ | separator thickness | `1 / scale` (hairline) | `1f` raw px | `1px` |
709
+
710
+ Separator inset and colour are now settable through `listStyle.separator` (§5); the
711
+ defaults above are what a list gets when it does not set them. Thickness is not
712
+ exposed — a hairline is correct on iOS and a whole pixel is correct elsewhere.
713
+
714
+ ### 6.3 Behavioral
715
+
716
+ - **Android pinned headers reuse normal row rendering.** The old Canvas-only
717
+ `StickySectionHeaderDecoration` is replaced by an overlay registered `NativeListSectionHeaderRowView`.
718
+ Typography, local metrics, values, checkbox/action callbacks and theme now follow
719
+ the same binding path. The overlay pushes away for the next sticky header and
720
+ forwards drag gestures to the RecyclerView. Summary and `sticky: false` headers
721
+ are excluded consistently. Device touch/accessibility acceptance remains required.
722
+ - **Source scale is list-wide on Android.**
723
+ `usesSelectorSourceScale` is computed with `items.any { … }`, so one selector row
724
+ switches the metric system (the sub-400dp 0.9 factor) for the entire list.
725
+ Only the legacy `row.height` (a WalletGroup's parent `height`) triggers it;
726
+ `style.container.height` switches its own row to selector explicit-height
727
+ geometry (as on iOS and Web) but never this list-wide metric system (§7 rule 3).
728
+ `NativeListTableColumnView`, the market skeleton, and the section index never
729
+ follow it. Explicit non-Market row styles now use density directly, so this
730
+ legacy baseline policy cannot scale an explicitly supplied style value.
731
+ Under source scaling, accountSelector/networkSelector/walletSidebar rows apply
732
+ the legacy selector typography (whole-pixel unscaled size, fractional advances,
733
+ original line box) to every text in the row, including trailing accessory
734
+ text and the action leading fallback text; a `fontSize` or `lineHeight` set in
735
+ that slot's style is applied as given. A walletSidebar leading visual under
736
+ source scaling draws a 1dp dashed border (else 2dp); its text-only overlays
737
+ are 16dp pills with 12sp text on a 16dp line, and other text overlays use 10sp.
738
+ - **`accent` is not an accent.** The consumer maps the application's `iconActive`
739
+ onto the theme key `accent`. The name should be retired in favour of an alias.
740
+
741
+ ### 6.4 Load-bearing oddities (do not "clean up")
742
+
743
+ - `.ok-native-list-account-action-row .ok-native-list-visual{border-radius:8px!important}`
744
+ overrides an **inline** radius written by `createVisual`. Removing `!important`
745
+ regresses the account action row to 10px.
746
+ - `.ok-native-list-market-change{color:#fff;background:#8d8d8d}` are literals, not
747
+ tokens, because `--nl-inverse-text` defaults to `#fcfcfc` and `--nl-secondary` to
748
+ `#6b7280`. Swapping them changes untinted rendering.
749
+
750
+ ### 6.5 Consolidated gap audit
751
+
752
+ | Former gap | Resolution in source | Remaining verification |
753
+ | --- | --- | --- |
754
+ | Non-Market box/image parameters accepted without effect | Per-template allowlists and native/Web geometry/image mappings; properties for nonexistent template parts rejected | Native image loading, decoration clipping and variant geometry |
755
+ | Native table and secondary-column styling | Separate primary/secondary labels in linear and table layouts; styles target only their semantic text | Rendered alignment and reuse in both native layouts |
756
+ | Missing semantic text targets | Activity status, rich identity subtitles, wallet badges, retry action and composite heading routed to actual fields | Device variant coverage |
757
+ | Selector defaults overwrite styles | Explicit style applied after defaults; summary/selection updates restore and reapply styles | Rapid selection/summary updates on device |
758
+ | Native reuse leaks | Restore the actual bound text and local geometry before rebinding; preserve unspecified rich-text attributes | Styled → unstyled → another template during scrolling |
759
+ | Android sticky header uses a separate partial renderer | Normal row view renders pinned text, values and controls | Pin/push-off, checkbox/action taps, scrolling and accessibility |
760
+ | List chrome propagation and group scope | Initial/update paths receive listStyle; custom radius only applies to actual groups | Initial and chrome-only snapshot interactions |
761
+ | Parent style cascades to wallet members | Group exposes container appearance and padding; members bind their own styles | Independent group/member styling during reorder |
762
+ | Row container surface | Shared `style.container`, explicit height and appearance precedence, disabled opacity, border/radius and content alignment | Press/selection/reorder and nested members on device |
763
+ | Text line controls | Shared 1–3 lines, tail/clip, vertical alignment and optical offset; measured text paths updated | Long/CJK/RTL/rich text and fixed-height clipping on device |
764
+
765
+ ### 6.6 Follow-up source audit (2026-09-22)
766
+
767
+ | Difference found | Repair |
768
+ | --- | --- |
769
+ | Web clip height guessed before CSS was mounted; iOS clip disabled natural wrapping | CSS line-height-relative clipping and iOS wrapping without ellipsis |
770
+ | Web ignored legacy identity line limits and Market badge metrics | Apply declared fields with style precedence and restore defaults on clear |
771
+ | Market rich subscripts retained their old size despite explicit font size | Superseded 2026-09-24: legacy (all platforms) sized a subscript at `ceil(0.6 × field fontSize)`. Web, iOS and Android restore that ratio for an explicit `fontSize` (native keeps the field line height, Web a `fontSize` line box, each as in legacy) |
772
+ | Web Market center fit used an invalid CSS value; change color used reversed precedence | Map center to object-fit none; explicit style color wins |
773
+ | Android line height excluded first/last text lines | Apply an exact line-height span including first/last font metrics |
774
+ | Legacy native centering overrode container alignment | Apply legacy positioning only when container alignment is omitted |
775
+ | Media leading gap changed when container alignment was supplied | Keep image-to-metadata spacing independent from vertical alignment |
776
+ | Opaque group members covered Web/Android explicit borders | Draw the border above member content without changing padding or hit targets |
777
+ | Web fixed footer was laid out beside the viewport | Stack viewport and footer vertically; styled footer height participates in allocation |
778
+ | Message timestamp line limits were absent from measured height | Include the timestamp text style in the existing message measurement |
779
+ | Runtime Market badge validation accepted text properties absent from its public type | Restrict badge style to its five declared metrics; it is not a row text role |
780
+
781
+ Market badge metrics remain `fontSize`, `fontWeight`, `lineHeight`, `height` and
782
+ `horizontalPadding`; badge colors stay on its existing model fields. This does
783
+ not extend the row-style vocabulary to arbitrary nested parts.
784
+
785
+ Common headers/footers already exist through structural rows, `fixedFooter` and
786
+ pager composition (§5.1). This work does not add an arbitrary React header slot.
787
+ Layout fitting is caller-owned, not an unresolved component feature. Legacy
788
+ unstyled defaults in §6 remain distinct from the unified configurable surface;
789
+ this change is not a wholesale redesign of those defaults.
790
+
791
+ ## 7. Isolation rules
792
+
793
+ Every template has its own renderer reuse family; there is no shared legacy
794
+ pool. A styled row must not affect another row, whether it reuses the same host
795
+ or not. Four rules:
796
+
797
+ 1. **Style types are per template.** `IdentityRowStyle` carries only identity's
798
+ fields; `MetricCardRowStyle` only metricCard's. A key that the row type does not
799
+ declare fails `validateSnapshot`/`validatePatches` instead of being ignored.
800
+ This strictness is new with the style surface: extra keys used to be ignored.
801
+ 2. **Every property gets an explicit default.** Restore font, line height, line
802
+ count, alignment/gravity, padding, color and all other changed state before
803
+ binding the next row. Native style passes save the actual bound text defaults
804
+ for restoration (§6.5). An omitted
805
+ value must use that template's default, never a previous row's value. The
806
+ shared container pass clears every property it wrote on the next bind,
807
+ including `align-self`/`flex-shrink` on template children. Image loading
808
+ visibility is image lifecycle state, not a template default: restoring
809
+ defaults must not hide an image that already loaded.
810
+ 3. **No list-wide side effects.** Any style-dependent decision is made per row.
811
+ `usesSelectorSourceScale` (§6.3) is the counter-example to avoid.
812
+ 4. **Bounded and fail-soft.** Validate input in JavaScript before serialization;
813
+ native readers must safely handle absent fields. A per-field default does not
814
+ prove that the whole combination fits. Invalid style must not affect another
815
+ row or crash the list. §7.1 states the required layout boundary.
816
+
817
+ ### 7.1 Template structure and integration layout responsibility
818
+
819
+ The component preserves template structure and style isolation. Developers
820
+ integrating the list are responsible for choosing sizes and content that fit;
821
+ overflow measurement, automatic size correction and combination-fit rejection
822
+ are explicitly out of scope. The following rules distinguish those responsibilities:
823
+
824
+ 1. **Keep the skeleton fixed.** A style may change a declared semantic field's
825
+ typography/color and supported local padding/gaps/image metrics. It cannot
826
+ change slot order, orientation, column count/weight, structural spans, section
827
+ membership, control placement or parent constraints. Those belong to the
828
+ template, its explicit variant, or the container layout.
829
+ 2. **Resolve row height consistently.** `style.container.height` defines the row
830
+ allocation before legacy `row.height`; without either, retain the template
831
+ measurement rules, including existing style-dependent text/image/padding
832
+ sizing. A new template documents whether its default height is a preset or
833
+ measured. Integrators can set a fitting style height without changing the
834
+ row model. Do not add automatic content fitting or font shrinking.
835
+ 3. **Integrator: fit content within that frame.** After local padding, each visual/control
836
+ and text line box must fit. For a simple two-line identity row, a necessary
837
+ check is `2 * verticalPadding + max(visualHeight, titleLineHeight + lineGap +
838
+ subtitleLineHeight, trailingHeight) <= resolvedRowHeight`. More lines and template
839
+ subrows add their own budget. This formula is an example, not a general row
840
+ measurement algorithm; it does not cover platform font scaling or rounding.
841
+ 4. **Integrator: verify compression and interaction.** Text may truncate/wrap only inside
842
+ its declared slot and line limit. It must not push a checkbox, menu, price or
843
+ action outside the row, overlap a neighbor, or steal another control's hit
844
+ target. Do not shrink hit areas to make an oversized style fit. `start`/`end`
845
+ follow layout direction; color changes must preserve readable contrast.
846
+ 5. **Reject unsupported structure.** No arbitrary React/RN/CSS style bag is part
847
+ of this contract. `position`, `transform`, `flexDirection`, negative margins,
848
+ freeform children and view-slot names are not supported style parameters.
849
+ Public types and nested runtime allowlists constrain this surface. These
850
+ checks deliberately do not validate whether a combination fits the row.
851
+ 6. **Retain local scope through every path.** Snapshot, patch, style removal,
852
+ scrolling reuse, footer binding and sticky rendering must agree. A parent
853
+ `walletGroup.style` must not silently become the child members' text style;
854
+ members use their own `IdentityRow.style`.
855
+
856
+ Example: `height: 48` with two 24-point text lines and 12-point top/bottom padding
857
+ needs at least 72 points before considering other content. Each number is legal
858
+ individually, but that combination is **not layout-safe**. Current validation
859
+ accepts such combinations; callers must choose a fitting explicit height and
860
+ verify the result. Do not describe numeric bounds as a clipping-prevention feature.
861
+
862
+ ## 8. Where style is applied
863
+
864
+ The style pass belongs after template and presentation defaults. All 12 row types
865
+ retain their own binders; adding a local style must not replace their structure.
866
+
867
+ | Platform | Owner | Order and reset |
868
+ | --- | --- | --- |
869
+ | iOS | Registered template renderer and `NativeListRendererCell` | Renderer restores/resolves current template defaults, then applies semantic text/image/spacing styles; host owns common container appearance and action epochs |
870
+ | Android | Registered template renderer and `NativeListRendererRowView` | Renderer applies current defaults and local styles; shared host owns allocation, container appearance and separators |
871
+ | Web | Registered template `bind()` plus `RowContainerStyle` | Template resets its own nodes and applies local styles; engine applies common row chrome. WalletGroup delegates each member to Identity before applying its group container |
872
+
873
+ All twelve templates have dedicated renderer families. Bind must restore omitted
874
+ style fields even without a prior recycle call; same-key template changes replace
875
+ an incompatible host/body. No global restoration/slot map remains. A renderer
876
+ may retain a bounded, template-local typography baseline where its platform
877
+ needs it; it must never obtain defaults from another template's previous binding.
878
+
879
+ Measurement uses the current data/theme/style inputs. Web renderers report
880
+ rendered intrinsic heights through the registry; the container has no separate
881
+ warning-view measurement fallback. The System renderer reports an automatic-height
882
+ warning's rendered border-box height (padding, both borders, wrapped text), as
883
+ the legacy engine did; `row.height`/`style.container.height` skip it. Image primitives retain unchanged effective
884
+ requests and invalidate callbacks on source/slot replacement or recycle.
885
+
886
+ Selector tabular typography belongs to Identity, SectionHeader and Action.
887
+ WalletGroup supplies its own compact preview through the internal renderer
888
+ contract; the container does not search for a parent view inside its DOM.
889
+ Native containers likewise call the row-host lifecycle instead of casting to
890
+ concrete template views. Shared action hit-testing, checkbox/selection routing
891
+ and image leases remain common list capabilities.
892
+
893
+ The six-stage migration and dated runtime results are recorded in
894
+ [DESIGN.md](DESIGN.md#incremental-renderer-migration) and SPEC.md. New templates
895
+ must implement the same binding/reset/measurement lifecycle and register a
896
+ family on all three platforms; there is no legacy fallback or public plugin API.
897
+
898
+ ## 9. Review checklist
899
+
900
+ For any PR that touches list typography or geometry:
901
+
902
+ - [ ] Does the change introduce a new bare font size or radius? If so, which token
903
+ in §3 should it be, or does §3 need a new step?
904
+ - [ ] Is the value applied identically on all three platforms? If not, is the
905
+ divergence recorded in §6?
906
+ - [ ] Does a new style key modify a **model field** name (§4), not a view name?
907
+ - [ ] Does the style pass write an explicit default for every property it can set
908
+ (§7 rule 2)?
909
+ - [ ] Is any decision derived from the whole row list rather than one row
910
+ (§7 rule 3)?
911
+ - [ ] For a section header: does the shared pinned row retain styles and interaction
912
+ (§6.3)?
913
+ - [ ] Does each style-only update preserve the allocated row frame and slot order?
914
+ Has the integrator verified fitting content and control hit areas (§7.1)?
915
+ - [ ] Can a content template be replaced while retaining header/footer, sections,
916
+ scrolling and the indexed bar (§5.1)?
917
+
918
+ ### Acceptance matrix
919
+
920
+ Run these checks on iOS, Android and Web before calling the spec implemented.
921
+ Source review, Swift parsing and JS unit tests do not replace native builds and
922
+ rendered interaction checks.
923
+
924
+ | Area | Required cases | Pass condition |
925
+ | --- | --- | --- |
926
+ | Template inventory | All 12 types in the catalog; selector presentations; composite metric cards; linear/table data rows; system variants | Named fields receive the intended style, unsupported fields fail explicitly |
927
+ | Baseline | Style omitted, style toggled off, existing Market consumers | Platform's baseline rendering and row geometry preserved |
928
+ | Integration layout | Integrator-selected values at narrow widths, with long/localized text, RTL and supported font scaling | The integrator verifies overflow and hit areas; the component preserves the template structure and allocated frame |
929
+ | Reuse and updates | Styled → unstyled → another template; snapshot and patch; rapid scrolling | No leaked typography, alignment, colors or metrics |
930
+ | Public composition | Mixed `identity`/`market`/`dataRow` sections, summary header, fixed footer, indexed bar | Swapping content templates preserves common capabilities and stable scroll targets |
931
+ | Scroll / chrome | Initial scroll, scroll to key/index/location/end, resize, refresh, pagination, initial/chrome-only snapshots | Correct target/offset and consistent separator/group style without row-size changes |
932
+ | Independent scopes | Two list instances, grouped and non-grouped rows, nested wallet members | No style or index-host effect outside the intended list/group/row |
933
+
934
+ Next acceptance work should execute the device cases in §6.5 and record
935
+ platform-specific build and interaction evidence. The shared document is
936
+ the contract; no shared cross-language renderer or code generator is required.
937
+
938
+ ### Verification — 2026-09-24 (Web legacy-default audit)
939
+
940
+ - Package typecheck passes; 180 package tests in five suites pass, including
941
+ (review round 3) Market subscript runs keeping the legacy 60% size under an
942
+ explicit `fontSize`, System warning layout at its rendered DOM height,
943
+ System/MetricCard text-layout styles (`lines: 1` text rewrite, `offsetY`
944
+ wrapper) clearing to a fresh unstyled render, walletSidebar `leadingGap`
945
+ cleared from reused visuals (standalone and WalletGroup members),
946
+ regressions for loaded MetricCard images across content/style rebinding,
947
+ WalletGroup vertical padding replacing the legacy inset in measured and
948
+ rendered height, message-only retry rows (Market included) firing on click
949
+ and Enter, member-only hover and member-press blocking for WalletGroup,
950
+ renderer values equal to the previous container write surviving a rebind,
951
+ container alignment cleared
952
+ from reused Activity/DataRow children, legacy table DataRow height (48) and
953
+ linear column structure,
954
+ selector geometry from `style.container.height`, attached/detached text
955
+ styling parity, `listStyle.separator.color` validation and the app Market
956
+ builder's style keys.
957
+ - These are jsdom/unit checks of the Web renderers and validation; they are not
958
+ rendered browser or native device acceptance.
959
+
960
+ ### Verification — 2026-09-22 (historical, before the renderer migration)
961
+
962
+ - Package TypeScript and JavaScript: 109 tests in five suites pass. Added cases
963
+ cover per-template keys/colors, rich text and badge isolation, independent data
964
+ columns, media dimensions, local gaps, status/retry targets and value accessories.
965
+ - Package lint: zero errors, 21 existing warnings. Example and documented
966
+ composition TypeScript pass; the example covers 12 templates with 42 rows.
967
+ - Android: `:onekeyfe_react-native-native-list:compileDebugKotlin` and
968
+ `:onekeyfe_react-native-native-list:testDebugUnitTest` pass (seven tests in three
969
+ suites), using the example Gradle project with `--configure-on-demand`.
970
+ - iOS: the modified models/assets/cell typecheck with the real simulator UIKit
971
+ SDK and a test double matching the public OneKeyImage view interface. This does
972
+ not verify the image implementation, linking or a full application build.
973
+ - An isolated iOS simulator check could not start: CoreSimulator was denied
974
+ permission to create the external-drive device data (Cocoa 513 / EPERM).
975
+ No native rendered interaction acceptance is claimed. Execute the matrix above
976
+ before treating the refactor as device-accepted.
977
+
978
+ Overflow fitting is the integrating developer's responsibility.