@incodetech/prizma-core-screens 0.2.4 → 0.2.12

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 (243) hide show
  1. package/dist/BiometricConsentScreen.d.ts +95 -0
  2. package/dist/BiometricConsentScreen.js +91 -0
  3. package/dist/BiometricConsentStrings.d.ts +84 -0
  4. package/dist/BiometricConsentStrings.js +31 -0
  5. package/dist/CaptureScreen.d.ts +13 -0
  6. package/dist/CaptureScreen.js +2 -2
  7. package/dist/CurpEnterScreen.d.ts +7 -0
  8. package/dist/CurpEnterScreen.js +17 -3
  9. package/dist/CurpGenerateScreen.d.ts +8 -0
  10. package/dist/CurpGenerateScreen.js +28 -13
  11. package/dist/CurpLayout.css +8 -34
  12. package/dist/CurpStrings.d.ts +16 -2
  13. package/dist/DemoScreensLayout.css +16 -0
  14. package/dist/DemoScreensRedirectScreen.d.ts +6 -1
  15. package/dist/DemoScreensRedirectScreen.js +3 -2
  16. package/dist/DemoScreensStrings.d.ts +5 -0
  17. package/dist/DigitalIdUploadErrorScreen.d.ts +31 -0
  18. package/dist/DigitalIdUploadErrorScreen.js +26 -0
  19. package/dist/DigitalIdUploadFileTooLargeScreen.d.ts +27 -0
  20. package/dist/DigitalIdUploadFileTooLargeScreen.js +21 -0
  21. package/dist/DigitalIdUploadReviewScreen.d.ts +57 -0
  22. package/dist/DigitalIdUploadReviewScreen.js +44 -0
  23. package/dist/DigitalIdUploadStrings.d.ts +64 -0
  24. package/dist/DigitalIdUploadStrings.js +19 -0
  25. package/dist/DigitalIdUploadTutorialScreen.d.ts +28 -0
  26. package/dist/DigitalIdUploadTutorialScreen.js +23 -0
  27. package/dist/DocumentCaptureIllustration.d.ts +1 -1
  28. package/dist/EkybAddressDetailsScreen.d.ts +7 -0
  29. package/dist/EkybAddressDetailsScreen.js +25 -7
  30. package/dist/EkybBusinessDetailsScreen.d.ts +6 -0
  31. package/dist/EkybBusinessDetailsScreen.js +23 -6
  32. package/dist/EkybCountryScreen.d.ts +13 -5
  33. package/dist/EkybCountryScreen.js +41 -10
  34. package/dist/EkybOwnersScreen.d.ts +8 -0
  35. package/dist/EkybOwnersScreen.js +26 -3
  36. package/dist/EkybStrings.d.ts +32 -10
  37. package/dist/EkycCountryCatalog.d.ts +4 -1
  38. package/dist/EkycCountryCatalog.js +5 -3
  39. package/dist/EkycCountryFormScreen.d.ts +12 -3
  40. package/dist/EkycCountryFormScreen.js +28 -10
  41. package/dist/EkycDetailsScreen.d.ts +7 -0
  42. package/dist/EkycDetailsScreen.js +21 -4
  43. package/dist/EkycStrings.d.ts +14 -2
  44. package/dist/ElectronicSignatureLayout.css +6 -0
  45. package/dist/EmailInputStrings.d.ts +11 -1
  46. package/dist/EmailInputVerifyScreen.d.ts +7 -0
  47. package/dist/EmailInputVerifyScreen.js +17 -3
  48. package/dist/ErrorScreensScreen.d.ts +5 -2
  49. package/dist/ErrorScreensScreen.js +15 -4
  50. package/dist/ErrorScreensStrings.d.ts +17 -2
  51. package/dist/ErrorScreensStrings.js +18 -10
  52. package/dist/FaceCaptureCapturingScreen.d.ts +63 -0
  53. package/dist/FaceCaptureCapturingScreen.js +62 -2
  54. package/dist/FaceCaptureLayout.css +69 -4
  55. package/dist/FaceCaptureStrings.d.ts +23 -0
  56. package/dist/FaceMatchComparisonScreen.d.ts +3 -3
  57. package/dist/FaceMatchLayout.css +21 -12
  58. package/dist/FormScreen.css +30 -0
  59. package/dist/FormScreen.d.ts +28 -0
  60. package/dist/FormScreen.js +3 -2
  61. package/dist/FormsLongFormScreen.d.ts +8 -0
  62. package/dist/FormsLongFormScreen.js +34 -13
  63. package/dist/FormsStrings.d.ts +11 -1
  64. package/dist/GeolocationBrowserStepsScreen.d.ts +6 -2
  65. package/dist/GeolocationBrowserStepsScreen.js +7 -3
  66. package/dist/GeolocationStrings.d.ts +3 -0
  67. package/dist/IdCaptureLayout.css +205 -2
  68. package/dist/IdCaptureStageScreen.d.ts +47 -0
  69. package/dist/IdCaptureStageScreen.js +39 -3
  70. package/dist/IdCaptureStrings.d.ts +19 -0
  71. package/dist/ListScreen.d.ts +21 -0
  72. package/dist/ListScreen.js +7 -2
  73. package/dist/LoaderScreen.d.ts +7 -5
  74. package/dist/LoaderScreen.js +7 -5
  75. package/dist/LoadersStrings.d.ts +18 -5
  76. package/dist/OcrReviewErrorScreen.d.ts +30 -0
  77. package/dist/OcrReviewErrorScreen.js +24 -0
  78. package/dist/OcrReviewLayout.css +24 -0
  79. package/dist/OcrReviewLoadingScreen.d.ts +25 -0
  80. package/dist/OcrReviewLoadingScreen.js +19 -0
  81. package/dist/OcrReviewScreen.d.ts +76 -0
  82. package/dist/OcrReviewScreen.js +65 -0
  83. package/dist/OcrReviewStrings.d.ts +47 -0
  84. package/dist/OcrReviewStrings.js +17 -0
  85. package/dist/OcrReviewSuccessScreen.d.ts +30 -0
  86. package/dist/OcrReviewSuccessScreen.js +24 -0
  87. package/dist/PermissionsAllowScreen.d.ts +8 -11
  88. package/dist/PermissionsAllowScreen.js +8 -11
  89. package/dist/PermissionsAskScreen.d.ts +41 -0
  90. package/dist/PermissionsAskScreen.js +43 -0
  91. package/dist/PermissionsBrowserStepsScreen.d.ts +9 -2
  92. package/dist/PermissionsBrowserStepsScreen.js +12 -3
  93. package/dist/PermissionsLayout.css +47 -0
  94. package/dist/PermissionsStrings.d.ts +20 -9
  95. package/dist/PermissionsStrings.js +10 -4
  96. package/dist/PhoneInputLayout.css +10 -9
  97. package/dist/PhoneInputOtpScreen.d.ts +8 -0
  98. package/dist/PhoneInputOtpScreen.js +26 -19
  99. package/dist/PhoneInputStrings.d.ts +22 -4
  100. package/dist/PhoneInputVerifyScreen.d.ts +8 -0
  101. package/dist/PhoneInputVerifyScreen.js +36 -7
  102. package/dist/QualifiedSignatureAcceptScreen.js +6 -1
  103. package/dist/QualifiedSignatureDocumentScreen.d.ts +17 -10
  104. package/dist/QualifiedSignatureDocumentScreen.js +22 -17
  105. package/dist/QualifiedSignatureLayout.css +76 -0
  106. package/dist/QualifiedSignatureResultScreen.d.ts +16 -13
  107. package/dist/QualifiedSignatureResultScreen.js +17 -18
  108. package/dist/QualifiedSignatureStrings.d.ts +19 -2
  109. package/dist/ResultScreen.d.ts +8 -0
  110. package/dist/ResultScreen.js +14 -3
  111. package/dist/ReusabilityCredentialSelectionScreen.d.ts +95 -0
  112. package/dist/ReusabilityCredentialSelectionScreen.js +94 -0
  113. package/dist/ReusabilityLayout.css +241 -0
  114. package/dist/ReusabilityPrivacyPolicyScreen.d.ts +55 -0
  115. package/dist/ReusabilityPrivacyPolicyScreen.js +44 -0
  116. package/dist/ReusabilityStrings.d.ts +85 -0
  117. package/dist/ReusabilityStrings.js +37 -0
  118. package/dist/ReviewScreen.d.ts +38 -12
  119. package/dist/ReviewScreen.js +41 -8
  120. package/dist/ScreenLayout.css +382 -6
  121. package/dist/ScreenNav.css +8 -0
  122. package/dist/ScreenNav.d.ts +15 -2
  123. package/dist/ScreenNav.js +10 -3
  124. package/dist/VideoSelfieErrorScreen.js +1 -1
  125. package/dist/VideoSelfieIdCaptureScreen.d.ts +10 -3
  126. package/dist/VideoSelfieIdCaptureScreen.js +45 -14
  127. package/dist/VideoSelfieLayout.css +467 -34
  128. package/dist/VideoSelfieSelfieCaptureScreen.d.ts +44 -27
  129. package/dist/VideoSelfieSelfieCaptureScreen.js +65 -39
  130. package/dist/VideoSelfieSpeakScreen.js +19 -2
  131. package/dist/VideoSelfieStrings.d.ts +82 -13
  132. package/dist/VideoSelfieTurnHeadScreen.d.ts +51 -0
  133. package/dist/VideoSelfieTurnHeadScreen.js +66 -0
  134. package/dist/VideoSelfieTutorialScreen.d.ts +9 -0
  135. package/dist/VideoSelfieTutorialScreen.js +22 -5
  136. package/dist/VideoSelfieWaveScreen.d.ts +53 -0
  137. package/dist/VideoSelfieWaveScreen.js +62 -0
  138. package/dist/ViewfinderScreen.css +36 -0
  139. package/dist/WatchlistBusinessFormScreen.d.ts +11 -7
  140. package/dist/WatchlistBusinessFormScreen.js +27 -13
  141. package/dist/WatchlistBusinessStrings.d.ts +21 -7
  142. package/dist/flows.d.ts +8 -0
  143. package/dist/flows.js +17 -0
  144. package/dist/index.d.ts +19 -1
  145. package/dist/index.js +14 -0
  146. package/dist/styles.css +3 -0
  147. package/dist/types.d.ts +3 -1
  148. package/package.json +79 -3
  149. package/samples/video-selfie-camera-wide.jpg +0 -0
  150. package/samples/video-selfie-room-wide.jpg +0 -0
  151. package/samples/video-selfie-room.png +0 -0
  152. package/src/BiometricConsentScreen.tsx +192 -0
  153. package/src/BiometricConsentStrings.ts +119 -0
  154. package/src/CaptureScreen.tsx +17 -0
  155. package/src/CurpEnterScreen.tsx +21 -4
  156. package/src/CurpGenerateScreen.tsx +46 -22
  157. package/src/CurpLayout.css +8 -34
  158. package/src/CurpStrings.ts +22 -2
  159. package/src/DemoScreensLayout.css +16 -0
  160. package/src/DemoScreensRedirectScreen.tsx +14 -6
  161. package/src/DemoScreensStrings.ts +5 -0
  162. package/src/DigitalIdUploadErrorScreen.tsx +83 -0
  163. package/src/DigitalIdUploadFileTooLargeScreen.tsx +67 -0
  164. package/src/DigitalIdUploadReviewScreen.tsx +119 -0
  165. package/src/DigitalIdUploadStrings.ts +88 -0
  166. package/src/DigitalIdUploadTutorialScreen.tsx +59 -0
  167. package/src/DocumentCaptureIllustration.tsx +1 -1
  168. package/src/EkybAddressDetailsScreen.tsx +32 -4
  169. package/src/EkybBusinessDetailsScreen.tsx +23 -5
  170. package/src/EkybCountryScreen.tsx +45 -7
  171. package/src/EkybOwnersScreen.tsx +34 -3
  172. package/src/EkybStrings.ts +34 -10
  173. package/src/EkycCountryCatalog.ts +9 -4
  174. package/src/EkycCountryFormScreen.tsx +51 -12
  175. package/src/EkycDetailsScreen.tsx +22 -1
  176. package/src/EkycStrings.ts +14 -2
  177. package/src/ElectronicSignatureLayout.css +6 -0
  178. package/src/EmailInputStrings.ts +12 -1
  179. package/src/EmailInputVerifyScreen.tsx +21 -4
  180. package/src/ErrorScreensScreen.tsx +15 -4
  181. package/src/ErrorScreensStrings.ts +37 -12
  182. package/src/FaceCaptureCapturingScreen.tsx +102 -13
  183. package/src/FaceCaptureLayout.css +69 -4
  184. package/src/FaceCaptureStrings.ts +24 -0
  185. package/src/FaceMatchComparisonScreen.tsx +3 -3
  186. package/src/FaceMatchLayout.css +21 -12
  187. package/src/FormScreen.css +30 -0
  188. package/src/FormScreen.tsx +34 -0
  189. package/src/FormsLongFormScreen.tsx +41 -13
  190. package/src/FormsStrings.ts +12 -1
  191. package/src/GeolocationBrowserStepsScreen.tsx +6 -3
  192. package/src/GeolocationStrings.ts +3 -0
  193. package/src/IdCaptureLayout.css +205 -2
  194. package/src/IdCaptureStageScreen.tsx +75 -0
  195. package/src/IdCaptureStrings.ts +19 -0
  196. package/src/ListScreen.tsx +58 -10
  197. package/src/LoaderScreen.tsx +7 -5
  198. package/src/LoadersStrings.ts +18 -5
  199. package/src/OcrReviewErrorScreen.tsx +53 -0
  200. package/src/OcrReviewLayout.css +24 -0
  201. package/src/OcrReviewLoadingScreen.tsx +48 -0
  202. package/src/OcrReviewScreen.tsx +190 -0
  203. package/src/OcrReviewStrings.ts +69 -0
  204. package/src/OcrReviewSuccessScreen.tsx +52 -0
  205. package/src/PermissionsAllowScreen.tsx +9 -10
  206. package/src/PermissionsAskScreen.tsx +105 -0
  207. package/src/PermissionsBrowserStepsScreen.tsx +11 -3
  208. package/src/PermissionsLayout.css +47 -0
  209. package/src/PermissionsStrings.ts +42 -12
  210. package/src/PhoneInputLayout.css +10 -9
  211. package/src/PhoneInputOtpScreen.tsx +23 -3
  212. package/src/PhoneInputStrings.ts +23 -4
  213. package/src/PhoneInputVerifyScreen.tsx +41 -5
  214. package/src/QualifiedSignatureAcceptScreen.tsx +12 -1
  215. package/src/QualifiedSignatureDocumentScreen.tsx +31 -19
  216. package/src/QualifiedSignatureLayout.css +76 -0
  217. package/src/QualifiedSignatureResultScreen.tsx +28 -26
  218. package/src/QualifiedSignatureStrings.ts +19 -2
  219. package/src/ResultScreen.tsx +33 -2
  220. package/src/ReusabilityCredentialSelectionScreen.tsx +246 -0
  221. package/src/ReusabilityLayout.css +241 -0
  222. package/src/ReusabilityPrivacyPolicyScreen.tsx +109 -0
  223. package/src/ReusabilityStrings.ts +127 -0
  224. package/src/ReviewScreen.tsx +96 -27
  225. package/src/ScreenLayout.css +382 -6
  226. package/src/ScreenNav.css +8 -0
  227. package/src/ScreenNav.tsx +35 -5
  228. package/src/VideoSelfieErrorScreen.tsx +1 -1
  229. package/src/VideoSelfieIdCaptureScreen.tsx +125 -31
  230. package/src/VideoSelfieLayout.css +467 -34
  231. package/src/VideoSelfieSelfieCaptureScreen.tsx +110 -57
  232. package/src/VideoSelfieSpeakScreen.tsx +34 -15
  233. package/src/VideoSelfieStrings.ts +89 -14
  234. package/src/VideoSelfieTurnHeadScreen.tsx +162 -0
  235. package/src/VideoSelfieTutorialScreen.tsx +45 -23
  236. package/src/VideoSelfieWaveScreen.tsx +143 -0
  237. package/src/ViewfinderScreen.css +36 -0
  238. package/src/WatchlistBusinessFormScreen.tsx +35 -16
  239. package/src/WatchlistBusinessStrings.ts +21 -7
  240. package/src/flows.ts +25 -0
  241. package/src/index.ts +88 -3
  242. package/src/styles.css +3 -0
  243. package/src/types.ts +3 -1
@@ -0,0 +1,88 @@
1
+ // Digital ID Upload module — copy contract.
2
+ //
3
+ // Every Digital ID Upload screen receives the SAME `strings:
4
+ // DigitalIdUploadStrings` object (rule: all copy via props, no hardcoded
5
+ // English — GUIDELINES.md §9).
6
+ //
7
+ // Authoritative screens: the "Web Happy Path - Light Mode" purple section of
8
+ // the Upload Digital ID Figma file (wxFsHRvllern4VnjRqM4JO, node 25351:14721)
9
+ // — covers `tutorial` and the `review` / `analyzing` / `success` stages of
10
+ // the confirm flow — plus TWO separate `Errors` sections: node 25874:11955
11
+ // (`ID type is not accepted`) and node 26083:3981 (`File Limit Error`, "File
12
+ // is too large"). Three frames of the happy-path section are NOT part of
13
+ // this contract: `1-1-1-how to verify` (a verification-method chooser shared
14
+ // with ID Capture — see IdCaptureDocumentSelectionScreen.tsx, which is one
15
+ // row short of this Figma's version), a native iOS file-browser frame (OS
16
+ // chrome, DEVIATIONS.md #DC-20 precedent), and `1-18-1-id-hold-on` (ID
17
+ // Capture's own server-side-wait screen — dropped from this module by
18
+ // product decision, not a gap to fill).
19
+
20
+ /** The three stages the confirm flow's screen shares — matches
21
+ * `PrizmaReviewScreen`'s own `PrizmaReviewStage` (ReviewScreen.tsx). Kept as
22
+ * a separate module-scoped type rather than importing it, same convention as
23
+ * `DocumentCaptureConfirmStage` (DocumentCaptureStrings.ts). */
24
+ export type DigitalIdUploadConfirmStage = 'review' | 'analyzing' | 'success';
25
+
26
+ export interface DigitalIdUploadStrings {
27
+ /** Accessible name for the logo nav region (i18n — no baked-in English). */
28
+ navLabel?: string;
29
+ /** "verified by" attribution copy for the footer tag. */
30
+ verifiedLabel: string;
31
+
32
+ /** `Tutorial Screen` (node 25351:14722). */
33
+ tutorial: {
34
+ title: string;
35
+ subtitle?: string;
36
+ /** "Upload PDF" */
37
+ ctaLabel: string;
38
+ };
39
+
40
+ /** `Review Document` / `Analyzing` / `Success` (nodes
41
+ * 25351:14725/14726/14727) — one screen, three stages. */
42
+ review: {
43
+ /** `review` title ("Review document"). */
44
+ title: string;
45
+ /** `review` description. Figma draws no subtitle on `analyzing` or
46
+ * `success` — the shared `PrizmaReviewScreen` archetype already only
47
+ * shows one on `review`, so this needs no per-stage handling here. */
48
+ subtitle?: string;
49
+ /** Loading-bar caption on `analyzing` ("Analyzing.."). */
50
+ analyzingLabel: string;
51
+ /** H2 on `success` ("Successfully processed!"). */
52
+ successTitle: string;
53
+ /** Secondary CTA on `review` ("Replace file"). */
54
+ replaceLabel: string;
55
+ /** Primary CTA on `review` / `success` ("Continue"). */
56
+ ctaLabel: string;
57
+ };
58
+
59
+ /** `Errors` section, `Error` frame (node 25874:11957) — the uploaded file
60
+ * itself was rejected (wrong document type). Built on the SAME
61
+ * `PrizmaReviewScreen` archetype as `review`, stage `"error"`: the card
62
+ * reuses `PrizmaCaptureDigitalId state="error"` (red 4px border), not a
63
+ * generic archetype-F verdict screen (DigitalIdUploadErrorScreen.tsx). */
64
+ error: {
65
+ /** "ID type is not accepted" */
66
+ title: string;
67
+ /** "Please try scanning a different document" */
68
+ subtitle?: string;
69
+ /** "Scan your ID" */
70
+ ctaLabel: string;
71
+ };
72
+
73
+ /** `Errors` section, `File Limit Error` frame (node 26083:3981) — the file
74
+ * was never uploaded at all (client-side size check), so there is no
75
+ * rejected document to show. Built on Archetype F (`PrizmaResultScreen`,
76
+ * ResultScreen.tsx) rather than the review archetype `error` uses above —
77
+ * Figma centres the status + title + subtitle in the full available
78
+ * height with no media stage at all, exactly F's shape, not H's
79
+ * header-above-a-card one (DigitalIdUploadFileTooLargeScreen.tsx). */
80
+ fileTooLarge: {
81
+ /** "File is too large" */
82
+ title: string;
83
+ /** "The selected file is larger than 5 MB, please upload a smaller one" */
84
+ subtitle?: string;
85
+ /** "Choose another file" */
86
+ ctaLabel: string;
87
+ };
88
+ }
@@ -0,0 +1,59 @@
1
+ import React, { forwardRef } from 'react';
2
+ import { PrizmaCaptureDigitalId } from '@incodetech/prizma-core-web/capture-digital-id';
3
+ import { PrizmaTutorialScreen, type PrizmaTutorialScreenProps } from './TutorialScreen';
4
+ import type { PrizmaScreenActionHandler, PrizmaScreenOverrides } from './types';
5
+ import type { DigitalIdUploadStrings } from './DigitalIdUploadStrings';
6
+ import './ScreenLayout.css';
7
+
8
+ export interface DigitalIdUploadTutorialScreenProps
9
+ extends Omit<
10
+ PrizmaTutorialScreenProps,
11
+ 'title' | 'subtitle' | 'ctaLabel' | 'illustration' | 'verifiedLabel' | 'navLabel'
12
+ > {
13
+ /** Module copy — this screen reads the `tutorial` slice (+ shared labels). */
14
+ strings: DigitalIdUploadStrings;
15
+ screenId?: string;
16
+ overrides?: PrizmaScreenOverrides;
17
+ onAction?: PrizmaScreenActionHandler;
18
+ }
19
+
20
+ /**
21
+ * Digital ID Upload — tutorial, built on the `PrizmaTutorialScreen` archetype
22
+ * (GUIDELINES.md §6-A). Figma: `Upload Digital ID` file
23
+ * (wxFsHRvllern4VnjRqM4JO), "Web Happy Path - Light Mode" section, `Tutorial
24
+ * Screen` (node 25351:14722).
25
+ *
26
+ * The illustration slot is Core's own `PrizmaCaptureDigitalId` in its
27
+ * `tutorial` state — the Figma file names this exact instance "Capture /
28
+ * Digital ID" (node 26161:10686, Figma description: "Incode specific
29
+ * component for Digital ID module experience"), the same managed component
30
+ * already shipped as `core/platforms/web/src/CaptureDigitalId.tsx`. No module
31
+ * illustration is drawn here — the component owns its own tutorial artwork
32
+ * (`PrizmaIllustrationDigitalId`).
33
+ */
34
+ export const DigitalIdUploadTutorialScreen = forwardRef<
35
+ HTMLDivElement,
36
+ DigitalIdUploadTutorialScreenProps
37
+ >(
38
+ (
39
+ { strings, screenId = 'digital-id-upload-tutorial', overrides, onAction, ...shell },
40
+ ref,
41
+ ) => (
42
+ <PrizmaTutorialScreen
43
+ ref={ref}
44
+ title={strings.tutorial.title}
45
+ subtitle={strings.tutorial.subtitle}
46
+ ctaLabel={strings.tutorial.ctaLabel}
47
+ illustration={<PrizmaCaptureDigitalId state="tutorial" />}
48
+ verifiedLabel={strings.verifiedLabel}
49
+ navLabel={strings.navLabel}
50
+ screenId={screenId}
51
+ overrides={overrides}
52
+ onAction={onAction}
53
+ {...shell}
54
+ />
55
+ ),
56
+ );
57
+ DigitalIdUploadTutorialScreen.displayName = 'DigitalIdUploadTutorialScreen';
58
+
59
+ export default DigitalIdUploadTutorialScreen;
@@ -7,7 +7,7 @@ import './DocumentCaptureLayout.css';
7
7
  *
8
8
  * WHY IT LIVES IN THE MODULE LAYER: `prizma-core-web` ships
9
9
  * `PrizmaIllustrationSelfie`, `…IdTutorial`, `…IdIntro`, `…IdFlip`,
10
- * `…IdHint`, `…IdFeedback`, `…NfcScan`, `…NfcIssue`, `…Geolocation`,
10
+ * `…IdHint`, `…IdFeedback`, `…NfcScan`, `…Geolocation`,
11
11
  * `…DigitalId` and `…Avatar` — there is NO document / proof-of-address
12
12
  * illustration. GUIDELINES.md §10 is the standing asset rule for that gap
13
13
  * ("Illustrations…: inline SVG React components (Figma-sourced), tintable via
@@ -82,6 +82,13 @@ export interface EkybAddressDetailsScreenProps
82
82
  * segment 2 solid, segment 3 grey) is Core's `PrizmaStepper` at
83
83
  * `currentState={2}`, composed into the archetype's `progress` slot. This is
84
84
  * step 2 of 3.
85
+ *
86
+ * `emptyError` is Continue pressed with the fields still empty (Elena,
87
+ * 2026-09-30, GUIDELINES §6-E empty-submit rule; precedent `FormsFieldScreen`'s
88
+ * `emptyError`): every field renders empty, the five REQUIRED ones (State
89
+ * included) in Core's `error` state with `requiredError` and no placeholder,
90
+ * Address line 2 unflagged (optional, DEVIATIONS #EKC-6's rule), and Continue
91
+ * disabled. `empty` keeps Continue enabled so the press can happen.
85
92
  */
86
93
  export const EkybAddressDetailsScreen = forwardRef<
87
94
  HTMLDivElement,
@@ -91,9 +98,10 @@ export const EkybAddressDetailsScreen = forwardRef<
91
98
  {
92
99
  strings,
93
100
  state = 'empty',
94
- values,
101
+ values: valuesProp,
95
102
  states = [],
96
103
  screenId = `ekyb-address-${state}`,
104
+ ctaDisabled,
97
105
  className,
98
106
  overrides,
99
107
  onAction,
@@ -102,6 +110,14 @@ export const EkybAddressDetailsScreen = forwardRef<
102
110
  ref,
103
111
  ) => {
104
112
  const copy = strings.address;
113
+ const emptyError = state === 'emptyError';
114
+ const values = emptyError ? undefined : valuesProp;
115
+ // Core's own error treatment, spread AFTER each required field's own
116
+ // placeholder so the error state shows none (the `FormsFieldScreen`
117
+ // precedent). Address line 2 never receives it.
118
+ const missing = emptyError
119
+ ? { error: true, errorText: strings.requiredError, placeholder: undefined }
120
+ : {};
105
121
 
106
122
  return (
107
123
  <PrizmaFormScreen
@@ -122,6 +138,8 @@ export const EkybAddressDetailsScreen = forwardRef<
122
138
  title={copy.title}
123
139
  subtitle={copy.subtitle}
124
140
  ctaLabel={copy.ctaLabel}
141
+ /* Disabled only after the empty submit; `empty` keeps it enabled. */
142
+ ctaDisabled={emptyError || ctaDisabled}
125
143
  linkContent={
126
144
  <PrizmaButton
127
145
  variant="secondary"
@@ -136,17 +154,20 @@ export const EkybAddressDetailsScreen = forwardRef<
136
154
  screenId={screenId}
137
155
  overrides={{ align: overrides?.align ?? 'left', logo: overrides?.logo }}
138
156
  onAction={onAction}
139
- /* Exploration ekyb-desktop: on a wide frame only State + Postal code
140
- share a row (Figma 6407:43637); the four text fields above span the
141
- column. On the phone nothing changes. */
157
+ /* Exploration ekyb-desktop: on a wide frame the fields pair up in
158
+ three rows, Street + House number, Address line 2 + City (Elena,
159
+ 2026-09-28), and State + Postal code (Figma 6407:43637). On the
160
+ phone nothing changes. */
142
161
  responsive
143
162
  {...shell}
144
163
  >
164
+ <PrizmaFormRow>
145
165
  <PrizmaTextField
146
166
  label={copy.street.label}
147
167
  placeholder={copy.street.placeholder}
148
168
  required
149
169
  requiredHint={strings.requiredHint}
170
+ {...missing}
150
171
  autoComplete="off"
151
172
  defaultValue={values?.street ?? ''}
152
173
  />
@@ -155,9 +176,12 @@ export const EkybAddressDetailsScreen = forwardRef<
155
176
  placeholder={copy.houseNumber.placeholder}
156
177
  required
157
178
  requiredHint={strings.requiredHint}
179
+ {...missing}
158
180
  autoComplete="off"
159
181
  defaultValue={values?.houseNumber ?? ''}
160
182
  />
183
+ </PrizmaFormRow>
184
+ <PrizmaFormRow>
161
185
  {/* The one field 6340:50731 draws without an asterisk. */}
162
186
  <PrizmaTextField
163
187
  label={copy.addressLine2.label}
@@ -170,15 +194,18 @@ export const EkybAddressDetailsScreen = forwardRef<
170
194
  placeholder={copy.city.placeholder}
171
195
  required
172
196
  requiredHint={strings.requiredHint}
197
+ {...missing}
173
198
  autoComplete="off"
174
199
  defaultValue={values?.city ?? ''}
175
200
  />
201
+ </PrizmaFormRow>
176
202
  <PrizmaFormRow>
177
203
  <PrizmaDropdown
178
204
  label={copy.state.label}
179
205
  placeholder={copy.state.placeholder}
180
206
  required
181
207
  requiredHint={strings.requiredHint}
208
+ {...missing}
182
209
  options={states}
183
210
  /* Both sections draw the placeholder unless the host supplies a
184
211
  value — see the docstring note on 6340:38197. */
@@ -193,6 +220,7 @@ export const EkybAddressDetailsScreen = forwardRef<
193
220
  placeholder={copy.postalCode.placeholder}
194
221
  required
195
222
  requiredHint={strings.requiredHint}
223
+ {...missing}
196
224
  autoComplete="off"
197
225
  defaultValue={values?.postalCode ?? ''}
198
226
  />
@@ -2,7 +2,7 @@ import React, { forwardRef } from 'react';
2
2
  import { PrizmaTextField } from '@incodetech/prizma-core-web/text-field';
3
3
  import { PrizmaButton } from '@incodetech/prizma-core-web/button';
4
4
  import { PrizmaStepper } from '@incodetech/prizma-core-web/stepper';
5
- import { PrizmaFormScreen, PrizmaFormRow, type PrizmaFormScreenProps } from './FormScreen';
5
+ import { PrizmaFormScreen, type PrizmaFormScreenProps } from './FormScreen';
6
6
  import type { PrizmaScreenActionHandler, PrizmaScreenOverrides } from './types';
7
7
  import type { EkybBusinessState, EkybBusinessValues, EkybStrings } from './EkybStrings';
8
8
  import './ScreenLayout.css';
@@ -69,6 +69,12 @@ export interface EkybBusinessDetailsScreenProps
69
69
  * bar is at `currentState="Start"`: segment 1 filled, segments 2 and 3 grey,
70
70
  * exactly what 5919:68599 renders. Both offsets around it live in
71
71
  * `ScreenLayout.css`, never here.
72
+ *
73
+ * `emptyError` is Continue pressed with the fields still empty (Elena,
74
+ * 2026-09-30, GUIDELINES §6-E empty-submit rule; precedent `FormsFieldScreen`'s
75
+ * `emptyError`): both REQUIRED fields render empty in Core's `error` state with
76
+ * `requiredError` under them, and Continue is disabled. `empty` keeps Continue
77
+ * enabled so the press can happen.
72
78
  */
73
79
  export const EkybBusinessDetailsScreen = forwardRef<
74
80
  HTMLDivElement,
@@ -78,8 +84,9 @@ export const EkybBusinessDetailsScreen = forwardRef<
78
84
  {
79
85
  strings,
80
86
  state = 'empty',
81
- values,
87
+ values: valuesProp,
82
88
  screenId = `ekyb-business-${state}`,
89
+ ctaDisabled,
83
90
  overrides,
84
91
  onAction,
85
92
  ...shell
@@ -87,6 +94,13 @@ export const EkybBusinessDetailsScreen = forwardRef<
87
94
  ref,
88
95
  ) => {
89
96
  const copy = strings.business;
97
+ const emptyError = state === 'emptyError';
98
+ const values = emptyError ? undefined : valuesProp;
99
+ // Core's own error treatment, spread onto the REQUIRED fields only; the
100
+ // error state shows no placeholder (the `FormsFieldScreen` precedent).
101
+ const missing = emptyError
102
+ ? { error: true, errorText: strings.requiredError, placeholder: undefined }
103
+ : {};
90
104
 
91
105
  return (
92
106
  <PrizmaFormScreen
@@ -104,6 +118,8 @@ export const EkybBusinessDetailsScreen = forwardRef<
104
118
  title={copy.title}
105
119
  subtitle={copy.subtitle}
106
120
  ctaLabel={copy.ctaLabel}
121
+ /* Disabled only after the empty submit; `empty` keeps it enabled. */
122
+ ctaDisabled={emptyError || ctaDisabled}
107
123
  linkContent={
108
124
  <PrizmaButton
109
125
  variant="secondary"
@@ -121,16 +137,18 @@ export const EkybBusinessDetailsScreen = forwardRef<
121
137
  overrides={{ align: overrides?.align ?? 'left', logo: overrides?.logo }}
122
138
  onAction={onAction}
123
139
  /* Exploration ekyb-desktop: on a tablet or desktop frame the two
124
- fields share a row; on the phone nothing changes. */
140
+ fields stay on two rows, each spanning the column (Elena,
141
+ 2026-09-28); `responsive` still pairs Continue and Back. On the
142
+ phone nothing changes. */
125
143
  responsive
126
144
  {...shell}
127
145
  >
128
- <PrizmaFormRow>
129
146
  <PrizmaTextField
130
147
  label={copy.businessName.label}
131
148
  placeholder={copy.businessName.placeholder}
132
149
  required
133
150
  requiredHint={strings.requiredHint}
151
+ {...missing}
134
152
  autoComplete="off"
135
153
  defaultValue={values?.businessName ?? ''}
136
154
  />
@@ -139,10 +157,10 @@ export const EkybBusinessDetailsScreen = forwardRef<
139
157
  placeholder={copy.taxId.placeholder}
140
158
  required
141
159
  requiredHint={strings.requiredHint}
160
+ {...missing}
142
161
  autoComplete="off"
143
162
  defaultValue={values?.taxId ?? ''}
144
163
  />
145
- </PrizmaFormRow>
146
164
  </PrizmaFormScreen>
147
165
  );
148
166
  },
@@ -1,5 +1,6 @@
1
1
  import React, { forwardRef } from 'react';
2
2
  import { PrizmaDropdown } from '@incodetech/prizma-core-web/dropdown';
3
+ import { PrizmaCountryFlag } from '@incodetech/prizma-core-web/combined';
3
4
  import { PrizmaFormScreen, type PrizmaFormScreenProps } from './FormScreen';
4
5
  import type { PrizmaScreenActionHandler, PrizmaScreenOverrides } from './types';
5
6
  import type { EkybCountryState, EkybOption, EkybStrings } from './EkybStrings';
@@ -55,14 +56,22 @@ export interface EkybCountryScreenProps
55
56
  * live interaction) — the same mechanism Phone Input's `countryCode` and
56
57
  * CURP's `Gene.dropdown` sections use. The search bar Figma draws inside the
57
58
  * menu is Core's own `searchable`.
58
- * 3. The closed field and the menu row are DIFFERENT faces of one option:
59
- * 5707:54174 draws "🇺🇸 United States" in the field while the rows read
60
- * "🇺🇸 +1 United States". That is Core's `PrizmaDropdownOption.fieldLabel`,
61
- * so nothing is hand-composed. The flag travels inside the label text
62
- * because Core still ships no country-flag primitive (#PI-3).
59
+ * 3. Menu rows and the closed field read the same flag + "United States",
60
+ * no dial code. Figma 5707:54174 draws the rows with Phone Input's "+1"
61
+ * prefixes, dropped by the owner (Elena, 2026-09-28) because a country of
62
+ * registration has no phone prefix. The flag is Core's raster in the
63
+ * dropdown's leading slot, not an emoji in the label (Elena, 2026-10-23;
64
+ * #PI-3, the WatchlistBusinessFormScreen precedent).
63
65
  *
64
66
  * This page is the only eKYB form page with NO "Back" secondary and NO Stepper —
65
67
  * it is step 0, before the three-step wizard starts.
68
+ *
69
+ * `emptyError` is Continue pressed with no country chosen (Elena, 2026-09-30,
70
+ * GUIDELINES §6-E empty-submit rule; precedent `FormsFieldScreen`'s
71
+ * `emptyError`): the dropdown renders empty, with no placeholder, in Core's
72
+ * `error` state with `requiredError` under it, and Continue is disabled. The
73
+ * field draws no `*`, but it is the page's only input and the flow cannot go on
74
+ * without it, so it is treated as required. `empty` keeps Continue enabled.
66
75
  */
67
76
  export const EkybCountryScreen = forwardRef<HTMLDivElement, EkybCountryScreenProps>(
68
77
  (
@@ -72,6 +81,7 @@ export const EkybCountryScreen = forwardRef<HTMLDivElement, EkybCountryScreenPro
72
81
  countries = [],
73
82
  value,
74
83
  screenId = `ekyb-country-${state}`,
84
+ ctaDisabled,
75
85
  overrides,
76
86
  onAction,
77
87
  ...shell
@@ -81,7 +91,9 @@ export const EkybCountryScreen = forwardRef<HTMLDivElement, EkybCountryScreenPro
81
91
  const copy = strings.country;
82
92
  // `empty` is the only section drawn on the placeholder; both other sections
83
93
  // draw a chosen country in the closed field.
84
- const selected = state === 'empty' ? null : (value ?? countries[0]?.value ?? null);
94
+ const emptyError = state === 'emptyError';
95
+ const selected =
96
+ state === 'empty' || emptyError ? null : (value ?? countries[0]?.value ?? null);
85
97
 
86
98
  return (
87
99
  <PrizmaFormScreen
@@ -89,6 +101,8 @@ export const EkybCountryScreen = forwardRef<HTMLDivElement, EkybCountryScreenPro
89
101
  title={copy.title}
90
102
  subtitle={copy.subtitle}
91
103
  ctaLabel={copy.ctaLabel}
104
+ /* Disabled only after the empty submit; `empty` keeps it enabled. */
105
+ ctaDisabled={emptyError || ctaDisabled}
92
106
  verifiedLabel={strings.verifiedLabel}
93
107
  navLabel={strings.navLabel}
94
108
  screenId={screenId}
@@ -102,7 +116,15 @@ export const EkybCountryScreen = forwardRef<HTMLDivElement, EkybCountryScreenPro
102
116
  >
103
117
  <PrizmaDropdown
104
118
  label={copy.field.label}
105
- placeholder={copy.field.placeholder}
119
+ /* Required, but UNMARKED: a single-input screen shows no asterisk
120
+ and no legend (Core pattern "Optimistic submission", single-input
121
+ screens; Elena 2026-09-30). The requirement surfaces on submit,
122
+ through the empty-submit error. */
123
+ /* The error state draws the field with nothing in it, not even the
124
+ placeholder (the `FormsFieldScreen` precedent). */
125
+ placeholder={emptyError ? undefined : copy.field.placeholder}
126
+ error={emptyError}
127
+ errorText={emptyError ? strings.requiredError : undefined}
106
128
  options={countries}
107
129
  /* Uncontrolled: a screen is one static spec state, so no `onChange` is
108
130
  needed. Core's `readOnly`/`disabled` are NOT used to freeze it —
@@ -114,6 +136,22 @@ export const EkybCountryScreen = forwardRef<HTMLDivElement, EkybCountryScreenPro
114
136
  clearSearchLabel={copy.clearSearchLabel}
115
137
  /* The `dropdown` section IS the open-menu spec state. */
116
138
  forceOpen={state === 'dropdown'}
139
+ /* Core's canonical flag raster in the closed field and every menu
140
+ row (see note 3). A country outside the bank falls back to its
141
+ `flag` emoji. */
142
+ renderLeading={(option) => {
143
+ const match = countries.find((c) => c.value === option.value);
144
+ return match ? (
145
+ <PrizmaCountryFlag
146
+ country={{
147
+ code: match.value,
148
+ name: match.label,
149
+ dialCode: '',
150
+ flag: match.flag ?? '',
151
+ }}
152
+ />
153
+ ) : null;
154
+ }}
117
155
  />
118
156
  </PrizmaFormScreen>
119
157
  );
@@ -59,6 +59,14 @@ export interface EkybOwnersScreenProps
59
59
  * - `loading` 5663:94390 — `directorFilled` with the submission in
60
60
  * flight: every field, both "Add another" links and "Back" go inert, and the
61
61
  * spinner rides the CTA, never a field (the Phone Input lesson, #PI-5)
62
+ * - `uboEmptyError` / `directorEmptyError` (no frame) — `uboEmpty` /
63
+ * `directorEmpty` after Continue was pressed with the fields still empty
64
+ * (Elena, 2026-09-30, GUIDELINES §6-E empty-submit rule; precedent
65
+ * `FormsFieldScreen`'s `emptyError`): every field renders empty, the two
66
+ * REQUIRED UBO fields in Core's `error` state with `requiredError` and no
67
+ * placeholder, the optional Director fields unflagged, and Continue
68
+ * disabled. The `*Empty` states keep Continue enabled so the press can
69
+ * happen.
62
70
  *
63
71
  * Four things are deliberate:
64
72
  *
@@ -99,8 +107,9 @@ export const EkybOwnersScreen = forwardRef<HTMLDivElement, EkybOwnersScreenProps
99
107
  {
100
108
  strings,
101
109
  state = 'uboEmpty',
102
- values,
110
+ values: valuesProp,
103
111
  screenId = `ekyb-owners-${state}`,
112
+ ctaDisabled,
104
113
  className,
105
114
  overrides,
106
115
  onAction,
@@ -111,8 +120,20 @@ export const EkybOwnersScreen = forwardRef<HTMLDivElement, EkybOwnersScreenProps
111
120
  const copy = strings.owners;
112
121
  const busy = state === 'loading';
113
122
  const secondUbo = state === 'uboAdded';
123
+ const emptyError = state === 'uboEmptyError' || state === 'directorEmptyError';
124
+ const values = emptyError ? undefined : valuesProp;
114
125
  const showDirector =
115
- state === 'directorEmpty' || state === 'directorFilled' || secondUbo || busy;
126
+ state === 'directorEmpty' ||
127
+ state === 'directorFilled' ||
128
+ state === 'directorEmptyError' ||
129
+ secondUbo ||
130
+ busy;
131
+ // Core's own error treatment, spread AFTER each required UBO field's own
132
+ // placeholder so the error state shows none (the `FormsFieldScreen`
133
+ // precedent). The Director fields are optional and never receive it.
134
+ const missing = emptyError
135
+ ? { error: true, errorText: strings.requiredError, placeholder: undefined }
136
+ : {};
116
137
 
117
138
  return (
118
139
  <PrizmaFormScreen
@@ -134,6 +155,9 @@ export const EkybOwnersScreen = forwardRef<HTMLDivElement, EkybOwnersScreenProps
134
155
  title={copy.title}
135
156
  subtitle={copy.subtitle}
136
157
  ctaLabel={copy.ctaLabel}
158
+ /* Disabled only after the empty submit; the `*Empty` states keep it
159
+ enabled. */
160
+ ctaDisabled={emptyError || ctaDisabled}
137
161
  /* `loading` swaps the CTA label for Core's designed spinner. */
138
162
  ctaLoading={busy}
139
163
  ctaLoadingLabel={copy.ctaLoadingLabel}
@@ -164,14 +188,19 @@ export const EkybOwnersScreen = forwardRef<HTMLDivElement, EkybOwnersScreenProps
164
188
  placeholder={copy.uboName.placeholder}
165
189
  required
166
190
  requiredHint={strings.requiredHint}
191
+ {...missing}
167
192
  autoComplete="off"
168
193
  defaultValue={values?.uboName ?? ''}
169
194
  disabled={busy}
170
195
  />
171
- {/* Figma draws no asterisk on the surname — it is optional. */}
196
+ {/* Required since 2026-09-28 (Elena): the frames drew no asterisk on the
197
+ surname, but a beneficial owner needs both names. */}
172
198
  <PrizmaTextField
173
199
  label={copy.uboSurname.label}
174
200
  placeholder={copy.uboSurname.placeholder}
201
+ required
202
+ requiredHint={strings.requiredHint}
203
+ {...missing}
175
204
  autoComplete="off"
176
205
  defaultValue={values?.uboSurname ?? ''}
177
206
  disabled={busy}
@@ -192,6 +221,8 @@ export const EkybOwnersScreen = forwardRef<HTMLDivElement, EkybOwnersScreenProps
192
221
  <PrizmaTextField
193
222
  label={copy.uboSurname.label}
194
223
  placeholder={copy.uboSurname.placeholder}
224
+ required
225
+ requiredHint={strings.requiredHint}
195
226
  autoComplete="off"
196
227
  defaultValue={values?.secondUboSurname ?? ''}
197
228
  />
@@ -34,15 +34,21 @@
34
34
  * - `dropdown` — the same selection with the MENU OPEN over the form: search
35
35
  * bar plus the country rows (Core's documentation-only `forceOpen`, since a
36
36
  * screen is one static spec state and never a live interaction)
37
+ * - `emptyError` — Continue was pressed with no country chosen (GUIDELINES
38
+ * §6-E empty-submit rule, Elena 2026-09-30): the dropdown in Core's error
39
+ * state with `requiredError`, Continue disabled. Not a Figma frame.
37
40
  */
38
- export type EkybCountryState = 'empty' | 'filled' | 'dropdown';
41
+ export type EkybCountryState = 'empty' | 'filled' | 'dropdown' | 'emptyError';
39
42
 
40
43
  /** The two `eKYB.Bdetails` (business identity) sections: every field on its
41
- * resting empty surface, or every field carrying a value. */
42
- export type EkybBusinessState = 'empty' | 'filled';
44
+ * resting empty surface, or every field carrying a value — plus `emptyError`,
45
+ * Continue pressed with the fields empty (empty-submit rule, 2026-09-30):
46
+ * every required field in Core's error state, Continue disabled. */
47
+ export type EkybBusinessState = 'empty' | 'filled' | 'emptyError';
43
48
 
44
- /** The two address sections (the SECOND `eKYB.Bdetails` parent). Same axis. */
45
- export type EkybAddressState = 'empty' | 'filled';
49
+ /** The two address sections (the SECOND `eKYB.Bdetails` parent). Same axis,
50
+ * same `emptyError` (the optional Address line 2 shows no error). */
51
+ export type EkybAddressState = 'empty' | 'filled' | 'emptyError';
46
52
 
47
53
  /**
48
54
  * The `eKYB.UBO.*` and `eKYB.director.*` sections — one screen family, because
@@ -63,6 +69,11 @@ export type EkybAddressState = 'empty' | 'filled';
63
69
  * flight, so EVERY field, both "Add another" links and "Back" go inert and
64
70
  * the spinner rides the CTA, never a field (the Phone Input lesson,
65
71
  * DEVIATIONS #PI-5)
72
+ * - `uboEmptyError` / `directorEmptyError` — `uboEmpty` / `directorEmpty` after
73
+ * Continue was pressed with the fields empty (GUIDELINES §6-E empty-submit
74
+ * rule, Elena 2026-09-30): the two REQUIRED UBO fields in Core's error state
75
+ * with `requiredError`, the optional Director fields unflagged, Continue
76
+ * disabled. Not Figma frames.
66
77
  */
67
78
  export type EkybOwnersState =
68
79
  | 'uboEmpty'
@@ -70,7 +81,9 @@ export type EkybOwnersState =
70
81
  | 'directorEmpty'
71
82
  | 'directorFilled'
72
83
  | 'uboAdded'
73
- | 'loading';
84
+ | 'loading'
85
+ | 'uboEmptyError'
86
+ | 'directorEmptyError';
74
87
 
75
88
  /**
76
89
  * One selectable row in the country / state menus. Runtime data, not copy — the
@@ -84,10 +97,14 @@ export interface EkybOption {
84
97
  /** The row label as drawn in the open menu. */
85
98
  label: string;
86
99
  /** Compact face for the CLOSED field when it differs from the menu row —
87
- * Core's own `PrizmaDropdownOption.fieldLabel`. `eKYB.country.dropdown`
88
- * draws "🇺🇸 +1 United States" in the row and "🇺🇸 United States" in the
89
- * field. */
100
+ * Core's own `PrizmaDropdownOption.fieldLabel`. The eKYB country list
101
+ * does not need it: rows and field both read "United States" after the
102
+ * flag. */
90
103
  fieldLabel?: string;
104
+ /** Country menus only: the emoji fallback for a `value` outside Core's
105
+ * flag raster bank. The country screen draws the bank's flag by `value`,
106
+ * so this never travels inside `label`. */
107
+ flag?: string;
91
108
  }
92
109
 
93
110
  /** The values the business-identity page carries. Runtime data, never copy. */
@@ -139,6 +156,13 @@ export interface EkybStrings {
139
156
  * localizable and never hardcoded in a screen.
140
157
  */
141
158
  requiredHint?: string;
159
+ /**
160
+ * Under every empty REQUIRED field once the primary was pressed with it
161
+ * still empty — the `emptyError` states (GUIDELINES §6-E empty-submit rule,
162
+ * Elena 2026-09-30). Core's own `errorText` slot; SDK key
163
+ * `dynamicForms.fieldRequired`.
164
+ */
165
+ requiredError: string;
142
166
  /** Shared "Back" secondary action drawn under "Continue" on every page except
143
167
  * `country`. Emits `secondary`. */
144
168
  backLabel: string;
@@ -217,7 +241,7 @@ export interface EkybStrings {
217
241
  title: string;
218
242
  subtitle?: string;
219
243
  uboName: EkybFieldStrings;
220
- /** Figma draws NO `*` on the surname — it is optional. */
244
+ /** Required, like the name (Elena, 2026-09-28); the frames drew no `*`. */
221
245
  uboSurname: EkybFieldStrings;
222
246
  /** The tertiary link under the UBO block ("Add another UBO (Optional)"). */
223
247
  addUboLabel: string;