@3fn/core 13.0.0 → 14.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (284) hide show
  1. package/.kiro/agents/ada-prompt.md +80 -132
  2. package/.kiro/agents/ada-prompt.md.attribution.json +45 -0
  3. package/.kiro/agents/ada.json +44 -59
  4. package/.kiro/agents/ada.json.attribution.json +13 -0
  5. package/.kiro/agents/data-prompt.md +83 -74
  6. package/.kiro/agents/data-prompt.md.attribution.json +53 -0
  7. package/.kiro/agents/data.json +31 -38
  8. package/.kiro/agents/data.json.attribution.json +13 -0
  9. package/.kiro/agents/kenya-prompt.md +83 -72
  10. package/.kiro/agents/kenya-prompt.md.attribution.json +53 -0
  11. package/.kiro/agents/kenya.json +27 -35
  12. package/.kiro/agents/kenya.json.attribution.json +13 -0
  13. package/.kiro/agents/leonardo-prompt.md +176 -234
  14. package/.kiro/agents/leonardo-prompt.md.attribution.json +45 -0
  15. package/.kiro/agents/leonardo.json +28 -36
  16. package/.kiro/agents/leonardo.json.attribution.json +13 -0
  17. package/.kiro/agents/lina-prompt.md +110 -151
  18. package/.kiro/agents/lina-prompt.md.attribution.json +53 -0
  19. package/.kiro/agents/lina.json +46 -59
  20. package/.kiro/agents/lina.json.attribution.json +13 -0
  21. package/.kiro/agents/sparky-prompt.md +89 -72
  22. package/.kiro/agents/sparky-prompt.md.attribution.json +53 -0
  23. package/.kiro/agents/sparky.json +37 -37
  24. package/.kiro/agents/sparky.json.attribution.json +13 -0
  25. package/.kiro/agents/stacy-prompt.md +74 -48
  26. package/.kiro/agents/stacy-prompt.md.attribution.json +45 -0
  27. package/.kiro/agents/stacy.json +28 -30
  28. package/.kiro/agents/stacy.json.attribution.json +13 -0
  29. package/.kiro/agents/thurgood-prompt.md +94 -138
  30. package/.kiro/agents/thurgood-prompt.md.attribution.json +45 -0
  31. package/.kiro/agents/thurgood.json +31 -35
  32. package/.kiro/agents/thurgood.json.attribution.json +13 -0
  33. package/.kiro/steering/AI-Collaboration-Principles.md +3 -3
  34. package/.kiro/steering/Civitas-System-Overview.md +7 -16
  35. package/.kiro/steering/DesignerPunk-Systems-Overview.md +6 -6
  36. package/.kiro/steering/Spec-Feedback-Protocol.md +2 -11
  37. package/.kiro/steering/Task-Completion-Protocol.md +98 -17
  38. package/.kiro/steering/core-goals.md +3 -3
  39. package/.kiro/steering/personal-note.md +1 -1
  40. package/.kiro/steering/start-up-tasks.md +17 -6
  41. package/application-mcp-server/src/index.ts +26 -0
  42. package/dist/ComponentTokens.android.kt +12 -12
  43. package/dist/ComponentTokens.ios.swift +12 -12
  44. package/dist/ComponentTokens.web.css +3 -3
  45. package/dist/DesignTokens.android.kt +1 -1
  46. package/dist/DesignTokens.dtcg.json +8 -5
  47. package/dist/DesignTokens.figma.json +2 -2
  48. package/dist/DesignTokens.ios.swift +1 -1
  49. package/dist/DesignTokens.web.css +1 -1
  50. package/dist/android/DesignTokens.android.kt +1 -1
  51. package/dist/blend/OklchBlendCalculator.js +1 -0
  52. package/dist/blend/ThemeAwareBlendUtilities.web.d.ts +13 -2
  53. package/dist/blend/ThemeAwareBlendUtilities.web.js +6 -1
  54. package/dist/browser/designerpunk.esm.js +36 -90
  55. package/dist/browser/designerpunk.esm.min.js +33 -36
  56. package/dist/browser/designerpunk.umd.js +36 -90
  57. package/dist/browser/designerpunk.umd.min.js +47 -50
  58. package/dist/browser/tokens.css +3 -3
  59. package/dist/build/tokens/defineComponentTokens.d.ts +10 -0
  60. package/dist/build/tokens/defineComponentTokens.js +26 -0
  61. package/dist/components/core/Avatar-Base/avatar.tokens.d.ts +21 -26
  62. package/dist/components/core/Avatar-Base/avatar.tokens.js +31 -34
  63. package/dist/components/core/Avatar-Base/index.d.ts +1 -1
  64. package/dist/components/core/Avatar-Base/index.js +2 -2
  65. package/dist/components/core/Avatar-Base/platforms/web/Avatar.web.js +24 -5
  66. package/dist/components/core/Button-CTA/examples/BasicUsage.d.ts +16 -28
  67. package/dist/components/core/Button-CTA/examples/BasicUsage.js +18 -43
  68. package/dist/components/core/Button-CTA/platforms/web/ButtonCTA.web.d.ts +3 -15
  69. package/dist/components/core/Button-CTA/platforms/web/ButtonCTA.web.js +9 -58
  70. package/dist/components/core/Button-CTA/types.d.ts +0 -24
  71. package/dist/components/core/Button-CTA/types.js +6 -0
  72. package/dist/components/core/Button-Icon/buttonIcon.tokens.d.ts +28 -14
  73. package/dist/components/core/Button-Icon/buttonIcon.tokens.js +35 -20
  74. package/dist/components/core/Input-Text-Base/types.d.ts +13 -1
  75. package/dist/components/core/Input-Text-Password/platforms/web/InputTextPassword.web.js +11 -2
  76. package/dist/generators/DTCGFormatGenerator.js +8 -0
  77. package/dist/generators/TokenFileGenerator.js +7 -2
  78. package/dist/integration/BuildErrorHandler.js +2 -2
  79. package/dist/ios/DesignTokens.ios.swift +1 -1
  80. package/dist/mcp/application-mcp.js +24 -0
  81. package/dist/mcp/docs-mcp.js +130 -15
  82. package/dist/mcp/product-mcp.js +25 -0
  83. package/dist/tokens/OpacityTokens.js +1 -1
  84. package/dist/tokens/component/progress.d.ts +65 -5
  85. package/dist/tokens/component/progress.js +79 -18
  86. package/dist/tokens/semantic/BlendTokens.d.ts +10 -3
  87. package/dist/tokens/semantic/BlendTokens.js +17 -5
  88. package/dist/tokens/semantic/OpacityTokens.d.ts +4 -4
  89. package/dist/tokens/semantic/OpacityTokens.js +4 -4
  90. package/dist/types/ComponentTypes.d.ts +1 -1
  91. package/dist/types/generated/TokenTypes.d.ts +1 -1
  92. package/dist/types/generated/TokenTypes.js +1 -1
  93. package/dist/validators/StemmaTokenUsageValidator.js +3 -2
  94. package/dist/web/DesignTokens.web.css +1 -1
  95. package/governance/BUILD-SYSTEM-SETUP.md +1 -2
  96. package/governance/Component-Development-Guide.md +23 -13
  97. package/governance/Component-Development-Standards.md +21 -20
  98. package/governance/Component-Family-Avatar.md +6 -7
  99. package/governance/Component-Family-Badge.md +19 -20
  100. package/governance/Component-Family-Button.md +30 -43
  101. package/governance/Component-Family-Chip.md +14 -15
  102. package/governance/Component-Family-Container.md +12 -13
  103. package/governance/Component-Family-Data-Display.md +1 -2
  104. package/governance/Component-Family-Divider.md +1 -2
  105. package/governance/Component-Family-Form-Inputs.md +65 -65
  106. package/governance/Component-Family-Icon.md +9 -10
  107. package/governance/Component-Family-Loading.md +1 -2
  108. package/governance/Component-Family-Modal.md +1 -2
  109. package/governance/Component-Family-Navigation.md +1 -2
  110. package/governance/Component-Family-Progress.md +0 -1
  111. package/governance/Component-Inheritance-Structures.md +219 -99
  112. package/governance/Component-MCP-Document-Template.md +6 -5
  113. package/governance/Component-Primitive-vs-Semantic-Philosophy.md +1 -1
  114. package/governance/Component-Quick-Reference.md +33 -33
  115. package/governance/Component-Readiness-Status.md +59 -44
  116. package/governance/Component-Templates.md +57 -61
  117. package/governance/Contract-System-Reference.md +7 -7
  118. package/governance/MCP-Integration-Guide.md +1 -1
  119. package/governance/Process-Cross-Reference-Standards.md +31 -13
  120. package/governance/Process-Development-Workflow.md +49 -59
  121. package/governance/Process-File-Organization.md +25 -28
  122. package/governance/Process-Hook-Operations.md +22 -11
  123. package/governance/Process-Orchestration-Model-Selection.md +92 -0
  124. package/governance/Process-Spec-Planning.md +98 -52
  125. package/governance/Process-Task-Type-Definitions.md +80 -4
  126. package/governance/Product-Handoff-Protocol.md +2 -0
  127. package/governance/Rosetta-System-Architecture.md +13 -11
  128. package/governance/Test-Behavioral-Contract-Validation.md +38 -31
  129. package/governance/Test-Failure-Audit-Methodology.md +1 -1
  130. package/governance/Token-Family-Accessibility.md +1 -2
  131. package/governance/Token-Family-Blend.md +18 -16
  132. package/governance/Token-Family-Blur.md +0 -1
  133. package/governance/Token-Family-Border.md +1 -2
  134. package/governance/Token-Family-Color.md +0 -1
  135. package/governance/Token-Family-Glow.md +1 -2
  136. package/governance/Token-Family-Layering.md +0 -1
  137. package/governance/Token-Family-Motion.md +1 -2
  138. package/governance/Token-Family-Opacity.md +0 -1
  139. package/governance/Token-Family-Radius.md +1 -2
  140. package/governance/Token-Family-Responsive.md +1 -2
  141. package/governance/Token-Family-Shadow.md +1 -2
  142. package/governance/Token-Family-Sizing.md +0 -1
  143. package/governance/Token-Family-Spacing.md +1 -2
  144. package/governance/Token-Family-Typography.md +1 -2
  145. package/governance/Token-Governance.md +8 -8
  146. package/governance/Token-Quick-Reference.md +47 -34
  147. package/governance/Token-Resolution-Patterns.md +1 -1
  148. package/governance/Token-Semantic-Structure.md +1 -1
  149. package/governance/Web-Authoring-Standards.md +5 -5
  150. package/governance/browser-distribution-guide.md +1 -4
  151. package/governance/classification-map.md +474 -0
  152. package/governance/completion-documentation-guide.md +23 -37
  153. package/governance/component-meta-authoring-guide.md +1 -1
  154. package/governance/cross-platform-vs-platform-specific-decision-framework.md +1 -1
  155. package/governance/platform-implementation-guidelines.md +2 -3
  156. package/governance/release-management-system.md +28 -63
  157. package/governance/rosetta-system-principles.md +8 -6
  158. package/governance/stemma-system-principles.md +18 -17
  159. package/mcp-server/src/index.ts +24 -6
  160. package/mcp-server/src/indexer/DocumentIndexer.ts +119 -9
  161. package/mcp-server/src/indexer/__tests__/bare-id-crossrefs.test.ts +250 -0
  162. package/mcp-server/src/indexer/cross-ref-parser.ts +29 -1
  163. package/mcp-server/src/indexer/index-health.ts +27 -2
  164. package/mcp-server/src/query/__tests__/find-docs-calibration.test.ts +11 -26
  165. package/mcp-server/src/relocation-integrity-gate/__tests__/relocation-integrity-gate.test.ts +72 -5
  166. package/mcp-server/src/relocation-integrity-gate/relocation-integrity-gate.ts +81 -24
  167. package/mcp-server/src/tools/list-cross-references.ts +2 -2
  168. package/package.json +24 -26
  169. package/src/__tests__/browser-distribution/css-bundling.test.ts +6 -4
  170. package/src/__tests__/console-allowlist.json +14 -0
  171. package/src/__tests__/console-fail-setup.ts +169 -0
  172. package/src/__tests__/integration/Spec107-DesignLanguageContext.test.ts +16 -0
  173. package/src/__tests__/stemma-system/behavioral-contract-validation.test.ts +70 -17
  174. package/src/__tests__/stemma-system/contract-catalog-name-validation.test.ts +28 -0
  175. package/src/__tests__/stemma-system/form-inputs-contracts.test.ts +223 -16
  176. package/src/__tests__/stemma-system/input-text-native-base-call-alignment.test.ts +298 -0
  177. package/src/blend/OklchBlendCalculator.ts +3 -0
  178. package/src/blend/ThemeAwareBlendUtilities.android.kt +3 -0
  179. package/src/blend/ThemeAwareBlendUtilities.ios.swift +3 -0
  180. package/src/blend/ThemeAwareBlendUtilities.web.ts +9 -1
  181. package/src/blend/__tests__/InteractionStateAudit.test.ts +12 -9
  182. package/src/build/errors/__tests__/ErrorHandler.integration.test.ts +8 -0
  183. package/src/build/errors/__tests__/ErrorHandler.test.ts +5 -0
  184. package/src/build/tokens/__tests__/defineComponentTokens.test.ts +113 -0
  185. package/src/build/tokens/defineComponentTokens.ts +43 -1
  186. package/src/build/workflow/__tests__/CICDIntegration.test.ts +12 -1
  187. package/src/cli/__tests__/init.test.ts +45 -11
  188. package/src/components/core/Avatar-Base/Avatar-Base.schema.yaml +1 -1
  189. package/src/components/core/Avatar-Base/__tests__/Avatar.accessibility.test.ts +121 -7
  190. package/src/components/core/Avatar-Base/__tests__/Avatar.image.test.ts +3 -0
  191. package/src/components/core/Avatar-Base/__tests__/Avatar.test.ts +15 -6
  192. package/src/components/core/Avatar-Base/avatar.tokens.ts +31 -34
  193. package/src/components/core/Avatar-Base/contracts.yaml +11 -1
  194. package/src/components/core/Avatar-Base/index.ts +1 -1
  195. package/src/components/core/Avatar-Base/platforms/web/Avatar.web.ts +24 -5
  196. package/src/components/core/Badge-Count-Base/contracts.yaml +1 -1
  197. package/src/components/core/Badge-Label-Base/contracts.yaml +1 -1
  198. package/src/components/core/Button-CTA/Button-CTA.schema.yaml +2 -12
  199. package/src/components/core/Button-CTA/README.md +3 -6
  200. package/src/components/core/Button-CTA/__tests__/ButtonCTA.test.ts +35 -89
  201. package/src/components/core/Button-CTA/__tests__/setup.test.ts +0 -2
  202. package/src/components/core/Button-CTA/__tests__/test-utils.ts +0 -2
  203. package/src/components/core/Button-CTA/contracts.yaml +6 -29
  204. package/src/components/core/Button-CTA/examples/BasicUsage.html +2 -14
  205. package/src/components/core/Button-CTA/examples/BasicUsage.tsx +17 -44
  206. package/src/components/core/Button-CTA/platforms/android/ButtonCTA.android.kt +12 -20
  207. package/src/components/core/Button-CTA/platforms/ios/ButtonCTA.ios.swift +12 -51
  208. package/src/components/core/Button-CTA/platforms/web/ButtonCTA.web.css +2 -26
  209. package/src/components/core/Button-CTA/platforms/web/ButtonCTA.web.ts +18 -71
  210. package/src/components/core/Button-CTA/types.ts +10 -28
  211. package/src/components/core/Button-Icon/buttonIcon.tokens.ts +43 -27
  212. package/src/components/core/Chip-Base/__tests__/ChipBase.test.ts +13 -0
  213. package/src/components/core/Chip-Filter/__tests__/ChipFilter.test.ts +13 -0
  214. package/src/components/core/Chip-Input/__tests__/ChipInput.test.ts +13 -0
  215. package/src/components/core/Input-Text-Base/Input-Text-Base.schema.yaml +30 -2
  216. package/src/components/core/Input-Text-Base/README.md +25 -2
  217. package/src/components/core/Input-Text-Base/__tests__/focusIndicators.test.ts +16 -15
  218. package/src/components/core/Input-Text-Base/contracts.yaml +90 -0
  219. package/src/components/core/Input-Text-Base/platforms/android/InputTextBase.android.kt +26 -12
  220. package/src/components/core/Input-Text-Base/platforms/ios/InputTextBase.ios.swift +195 -59
  221. package/src/components/core/Input-Text-Base/types.ts +13 -1
  222. package/src/components/core/Input-Text-Email/Input-Text-Email.schema.yaml +5 -1
  223. package/src/components/core/Input-Text-Email/README.md +8 -7
  224. package/src/components/core/Input-Text-Email/platforms/android/InputTextEmail.android.kt +1 -4
  225. package/src/components/core/Input-Text-Email/platforms/ios/InputTextEmail.ios.swift +2 -16
  226. package/src/components/core/Input-Text-Password/Input-Text-Password.schema.yaml +10 -3
  227. package/src/components/core/Input-Text-Password/README.md +9 -8
  228. package/src/components/core/Input-Text-Password/contracts.yaml +5 -0
  229. package/src/components/core/Input-Text-Password/platforms/android/InputTextPassword.android.kt +17 -7
  230. package/src/components/core/Input-Text-Password/platforms/ios/InputTextPassword.ios.swift +22 -20
  231. package/src/components/core/Input-Text-Password/platforms/web/InputTextPassword.web.ts +11 -2
  232. package/src/components/core/Input-Text-PhoneNumber/Input-Text-PhoneNumber.schema.yaml +5 -1
  233. package/src/components/core/Input-Text-PhoneNumber/README.md +9 -8
  234. package/src/components/core/Input-Text-PhoneNumber/platforms/android/InputTextPhoneNumber.android.kt +2 -5
  235. package/src/components/core/Input-Text-PhoneNumber/platforms/ios/InputTextPhoneNumber.ios.swift +3 -17
  236. package/src/components/core/Nav-Header-App/contracts.yaml +1 -1
  237. package/src/components/core/Nav-SegmentedChoice-Base/contracts.yaml +1 -1
  238. package/src/components/core/Progress-Indicator-Connector-Base/contracts.yaml +1 -1
  239. package/src/components/core/Progress-Indicator-Label-Base/contracts.yaml +1 -1
  240. package/src/components/core/Progress-Indicator-Node-Base/contracts.yaml +1 -1
  241. package/src/components/core/Progress-Stepper-Base/__tests__/StepperBase.test.ts +5 -2
  242. package/src/components/core/Progress-Stepper-Detailed/__tests__/StepperDetailed.test.ts +5 -2
  243. package/src/generators/DTCGFormatGenerator.ts +6 -0
  244. package/src/generators/TokenFileGenerator.ts +7 -2
  245. package/src/generators/__tests__/DTCGConfigOptions.test.ts +14 -5
  246. package/src/integration/BuildErrorHandler.ts +2 -2
  247. package/src/tokens/OpacityTokens.ts +1 -1
  248. package/src/tokens/__tests__/OpacityTokens.test.ts +3 -1
  249. package/src/tokens/__tests__/ProgressTokenCompliance.test.ts +5 -3
  250. package/src/tokens/__tests__/ProgressTokenFormulas.test.ts +11 -11
  251. package/src/tokens/__tests__/ProgressTokenTranslation.test.ts +22 -20
  252. package/src/tokens/component/progress.ts +83 -21
  253. package/src/tokens/semantic/BlendTokens.ts +26 -5
  254. package/src/tokens/semantic/OpacityTokens.ts +4 -4
  255. package/src/types/ComponentTypes.ts +1 -1
  256. package/src/types/generated/TokenTypes.ts +1 -1
  257. package/src/validators/StemmaTokenUsageValidator.ts +3 -2
  258. package/token-index/components.yaml +8 -8
  259. package/token-index/semantics.yaml +1 -2
  260. package/src/tools/release/__tests__/ChangeClassifier.test.ts +0 -133
  261. package/src/tools/release/__tests__/ChangeExtractor.test.ts +0 -222
  262. package/src/tools/release/__tests__/GitHubPublisher.test.ts +0 -240
  263. package/src/tools/release/__tests__/NotesRenderer.test.ts +0 -142
  264. package/src/tools/release/__tests__/NpmPublisher.test.ts +0 -289
  265. package/src/tools/release/__tests__/PipelineIntegration.test.ts +0 -188
  266. package/src/tools/release/__tests__/ReleasePipeline.test.ts +0 -192
  267. package/src/tools/release/__tests__/SemanticVersionValidator.test.ts +0 -49
  268. package/src/tools/release/__tests__/SummaryScanner.test.ts +0 -141
  269. package/src/tools/release/__tests__/TagResolver.test.ts +0 -91
  270. package/src/tools/release/__tests__/VersionCalculator.test.ts +0 -270
  271. package/src/tools/release/__tests__/helpers/NpmMockHelper.ts +0 -80
  272. package/src/tools/release/cli/ReleasePipeline.ts +0 -165
  273. package/src/tools/release/cli/release-tool.ts +0 -107
  274. package/src/tools/release/pipeline/ChangeClassifier.ts +0 -61
  275. package/src/tools/release/pipeline/ChangeExtractor.ts +0 -87
  276. package/src/tools/release/pipeline/NotesRenderer.ts +0 -66
  277. package/src/tools/release/pipeline/SummaryScanner.ts +0 -70
  278. package/src/tools/release/pipeline/TagResolver.ts +0 -40
  279. package/src/tools/release/pipeline/VersionCalculator.ts +0 -375
  280. package/src/tools/release/publishers/GitHubPublisher.ts +0 -228
  281. package/src/tools/release/publishers/NpmPublisher.ts +0 -196
  282. package/src/tools/release/release-config.json +0 -5
  283. package/src/tools/release/types/index.ts +0 -282
  284. package/src/tools/release/validators/SemanticVersionValidator.ts +0 -67
@@ -13,21 +13,33 @@
13
13
  * - Color animation (text.subtle → primary with blend.focusSaturate)
14
14
  * - Offset animation (translateY)
15
15
  * - Trailing icon support (error, success, info)
16
+ * - Trailing content slot for semantic variants (e.g., password toggle)
17
+ * - Read-only rendering (B-prime): selectable Text inside the shared field
18
+ * chrome — never disabled semantics
16
19
  * - Respects accessibilityReduceMotion
17
20
  * - WCAG 2.1 AA compliant
18
- * - Uses theme-aware blend utilities for state colors (focus, disabled)
19
- *
21
+ * - Uses theme-aware blend utilities for state colors (focus)
22
+ *
20
23
  * Behavioral Contracts:
21
- * - focusable: Can receive keyboard focus
22
- * - float_label_animation: Label animates on focus
23
- * - validates_on_blur: Validation triggers on blur
24
- * - error_state_display: Shows error message and styling
25
- * - success_state_display: Shows success styling
26
- * - disabled_state: Prevents interaction when disabled
27
- * - trailing_icon_display: Shows contextual trailing icons
28
- * - focus_ring: WCAG 2.4.7 focus visible indicator
29
- * - reduced_motion_support: Respects prefers-reduced-motion
30
- *
24
+ * - interaction_focusable: Can receive keyboard focus (iOS readOnly carve-out
25
+ * declared in contracts.yaml — readOnly content is not in FKA focus order)
26
+ * - content_float_label: Label animates on focus
27
+ * - validation_on_blur: Validation triggers on blur
28
+ * - state_error: Shows error message and styling
29
+ * - state_success: Shows success styling
30
+ * - state_readonly: Read-only display — selectable/copyable, non-editable,
31
+ * announced read-only, never a disabled/dimmed trait (concept ballot
32
+ * pending ratification; see contracts.yaml)
33
+ * - content_trailing_icon: Shows contextual trailing icons
34
+ * - interaction_focus_ring: WCAG 2.4.7 focus visible indicator
35
+ * - accessibility_reduced_motion: Respects prefers-reduced-motion
36
+ *
37
+ * Read-only (B-prime ruling — Peter, 2026-07-15,
38
+ * .kiro/issues/input-text-base-ios-readonly-adjudication.md):
39
+ * readOnly renders as `Text(value).textSelection(.enabled)` inside the same
40
+ * field chrome as the editable field. The SwiftUI disabled modifier is never
41
+ * applied. onFocus/onBlur are DECLARED not-fired on the iOS readOnly path.
42
+ *
31
43
  * Requirements: 1.1, 1.2, 1.3, 1.5, 4.1, 4.2, 4.3, 8.1, 8.2, 8.4, 11.1, 11.2, 11.3, 13.1
32
44
  */
33
45
 
@@ -81,7 +93,9 @@ struct InputTextBase: View {
81
93
  /// Placeholder text (only shown when label is floated and input is empty)
82
94
  let placeholder: String?
83
95
 
84
- /// Read-only state
96
+ /// Read-only state (state_readonly contract): renders the value as
97
+ /// selectable Text inside the field chrome — never disabled semantics.
98
+ /// Ignored for secure fields (see isReadOnlyDisplay).
85
99
  let readOnly: Bool
86
100
 
87
101
  /// Required field indicator
@@ -89,10 +103,12 @@ struct InputTextBase: View {
89
103
 
90
104
  /// Maximum length for input value
91
105
  let maxLength: Int?
92
-
93
- /// Disabled state
94
- let isDisabled: Bool
95
-
106
+
107
+ /// Trailing content slot rendered after the status icons
108
+ /// (e.g., password visibility toggle). Declared as `var` with a
109
+ /// default so the memberwise initializer keeps it optional and last.
110
+ var trailingContent: AnyView? = nil
111
+
96
112
  // MARK: - State
97
113
 
98
114
  /// Whether input currently has focus
@@ -112,6 +128,22 @@ struct InputTextBase: View {
112
128
  private var isFilled: Bool {
113
129
  !value.isEmpty
114
130
  }
131
+
132
+ /// Whether to render the read-only display path (B-prime).
133
+ ///
134
+ /// SECURITY-CRITICAL GATE: the read-only path renders the value as
135
+ /// selectable plaintext `Text`. It MUST be unreachable for secure fields
136
+ /// (`type == .password`), or a readOnly password would render its secret
137
+ /// in plaintext. readOnly is contracted OUT of Input-Text-Password
138
+ /// entirely (adjudication condition 4:
139
+ /// .kiro/issues/input-text-base-ios-readonly-adjudication.md — RULED
140
+ /// B-prime, Peter 2026-07-15); the Password variant additionally strips
141
+ /// readOnly before it reaches this component. On a secure field, readOnly
142
+ /// is ignored (the field stays an editable SecureField) rather than ever
143
+ /// routing to the plaintext Text branch.
144
+ private var isReadOnlyDisplay: Bool {
145
+ readOnly && type != .password
146
+ }
115
147
 
116
148
  /// Whether input has error
117
149
  private var hasError: Bool {
@@ -140,8 +172,6 @@ struct InputTextBase: View {
140
172
  return theme.colorFeedbackErrorText
141
173
  } else if isSuccess {
142
174
  return theme.colorFeedbackSuccessText
143
- } else if isDisabled {
144
- return theme.colorActionPrimary.disabledBlend()
145
175
  } else if isFocused {
146
176
  return theme.colorActionPrimary.focusBlend()
147
177
  } else {
@@ -164,8 +194,6 @@ struct InputTextBase: View {
164
194
  return theme.colorFeedbackErrorText
165
195
  } else if isSuccess {
166
196
  return theme.colorFeedbackSuccessText
167
- } else if isDisabled {
168
- return theme.colorActionPrimary.disabledBlend()
169
197
  } else if isFocused {
170
198
  return theme.colorActionPrimary.focusBlend()
171
199
  } else {
@@ -213,18 +241,45 @@ struct InputTextBase: View {
213
241
  }
214
242
 
215
243
  // Input field
216
- if type == .password {
244
+ if isReadOnlyDisplay {
245
+ // Read-only rendering (B-prime): selectable Text inside
246
+ // the shared field chrome. Never the SwiftUI disabled
247
+ // modifier — it produces disabled semantics (unfocusable,
248
+ // uncopyable, VoiceOver "dimmed"), which DesignerPunk bans.
249
+ // Long-press → select/copy (whole-string copy is the
250
+ // contracted iOS behavior). No keyboard is raised.
251
+ //
252
+ // Declared behavior (state_readonly contract):
253
+ // - NOT in keyboard/FKA focus order (mitigable declared
254
+ // exception — .focusable(interactions:) prototype
255
+ // tracked; contract tightens if it verifies on-device)
256
+ // - onFocus/onBlur DECLARED not-fired on this path
257
+ // (there is no focus system here — nothing simulates
258
+ // or pretends otherwise)
259
+ // - Empty value: label rests in placeholder position
260
+ // (cross-platform parity); the chrome reserves full
261
+ // height unconditionally.
262
+ Text(value)
263
+ .textSelection(.enabled)
264
+ .modifier(InputTextBaseFieldChrome(
265
+ borderColor: borderColor,
266
+ isFocused: false,
267
+ hasTrailingIcon: showErrorIcon || showSuccessIcon || showInfoIconVisible || trailingContent != nil
268
+ ))
269
+ } else if type == .password {
270
+ // Secure path: readOnly is IGNORED here (see
271
+ // isReadOnlyDisplay) — a secure field never routes to
272
+ // the plaintext read-only Text branch, and disabled
273
+ // semantics are never applied.
217
274
  SecureField(isLabelFloated && placeholder != nil ? placeholder! : "", text: $value)
218
275
  .textFieldStyle(InputTextBaseFieldStyle(
219
276
  borderColor: borderColor,
220
277
  isFocused: isFocused,
221
278
  hasError: hasError,
222
279
  isSuccess: isSuccess,
223
- isDisabled: isDisabled,
224
- hasTrailingIcon: showErrorIcon || showSuccessIcon || showInfoIconVisible
280
+ hasTrailingIcon: showErrorIcon || showSuccessIcon || showInfoIconVisible || trailingContent != nil
225
281
  ))
226
282
  .focused($isFocused)
227
- .disabled(readOnly || isDisabled)
228
283
  .textContentType(autocomplete)
229
284
  .onChange(of: value) { newValue in
230
285
  if let maxLength = maxLength, newValue.count > maxLength {
@@ -246,11 +301,9 @@ struct InputTextBase: View {
246
301
  isFocused: isFocused,
247
302
  hasError: hasError,
248
303
  isSuccess: isSuccess,
249
- isDisabled: isDisabled,
250
- hasTrailingIcon: showErrorIcon || showSuccessIcon || showInfoIconVisible
304
+ hasTrailingIcon: showErrorIcon || showSuccessIcon || showInfoIconVisible || trailingContent != nil
251
305
  ))
252
306
  .focused($isFocused)
253
- .disabled(readOnly || isDisabled)
254
307
  .textContentType(autocomplete)
255
308
  .keyboardType(keyboardTypeForInputType(type))
256
309
  .onChange(of: value) { newValue in
@@ -295,6 +348,12 @@ struct InputTextBase: View {
295
348
  value: showInfoIconVisible
296
349
  )
297
350
  }
351
+
352
+ // Trailing content slot (e.g., password visibility toggle)
353
+ if let trailingContent = trailingContent {
354
+ trailingContent
355
+ .padding(.trailing, DesignTokens.spaceInset100)
356
+ }
298
357
  }
299
358
  .frame(minHeight: DesignTokens.tapAreaRecommended)
300
359
 
@@ -319,7 +378,25 @@ struct InputTextBase: View {
319
378
  .accessibilityElement(children: .contain)
320
379
  .accessibilityLabel(label)
321
380
  .accessibilityValue(value)
322
- .accessibilityHint(helperText ?? "")
381
+ .accessibilityHint(accessibilityHintText)
382
+ }
383
+
384
+ /// Composed accessibility hint.
385
+ ///
386
+ /// On the read-only path this composes the read-only indication with any
387
+ /// helper text, so VoiceOver announces label + value + "Read only"
388
+ /// (state_readonly contract) — never a disabled/dimmed trait. iOS has no
389
+ /// read-only accessibility trait; the hint is the correct announcement
390
+ /// route (adjudication, Kenya R1).
391
+ private var accessibilityHintText: String {
392
+ var parts: [String] = []
393
+ if isReadOnlyDisplay {
394
+ parts.append("Read only")
395
+ }
396
+ if let helperText = helperText, !helperText.isEmpty {
397
+ parts.append(helperText)
398
+ }
399
+ return parts.joined(separator: ". ")
323
400
  }
324
401
 
325
402
  // MARK: - Helper Methods
@@ -353,30 +430,48 @@ enum InputType {
353
430
  case url
354
431
  }
355
432
 
356
- // MARK: - Custom Text Field Style
433
+ // MARK: - Shared Field Chrome
357
434
 
358
435
  /**
359
- * Custom text field style for consistent appearance
436
+ * Shared field chrome (B-prime chrome extraction, adjudication 2026-07-15).
437
+ *
438
+ * The visual treatment of the field box — typography, text color, insets,
439
+ * canvas background, border overlay, and focus-ring overlay — extracted from
440
+ * InputTextBaseFieldStyle so it applies to BOTH rendering paths: the editable
441
+ * TextField/SecureField (via InputTextBaseFieldStyle, which delegates here)
442
+ * and the read-only Text (applied directly). One chrome, two contents — the
443
+ * two paths cannot drift visually.
360
444
  */
361
- struct InputTextBaseFieldStyle: TextFieldStyle {
445
+ struct InputTextBaseFieldChrome: ViewModifier {
362
446
  let borderColor: Color
363
447
  let isFocused: Bool
364
- let hasError: Bool
365
- let isSuccess: Bool
366
- let isDisabled: Bool
367
448
  let hasTrailingIcon: Bool
368
-
449
+
369
450
  @Environment(\.accessibilityReduceMotion) var reduceMotion
370
451
  @Environment(\.dpTheme) private var theme
371
-
372
- func _body(configuration: TextField<Self._Label>) -> some View {
373
- configuration
452
+
453
+ /// Minimum content height, reserved UNCONDITIONALLY on both paths:
454
+ /// input line height (fontSize × unitless lineHeight multiplier, the
455
+ /// Badge/Nav token-derived pattern) + vertical insets. This deliberately
456
+ /// does NOT reason about empty `Text` intrinsic size (undocumented, has
457
+ /// varied across SwiftUI releases): an empty readOnly value keeps the
458
+ /// same field height as a filled editable field. Note: the field uses a
459
+ /// fixed token font size (no Dynamic Type) today — if the family adopts
460
+ /// Dynamic Type, this minHeight must scale with it.
461
+ private var minFieldHeight: CGFloat {
462
+ (DesignTokens.typographyInput.fontSize * DesignTokens.typographyInput.lineHeight)
463
+ + (DesignTokens.spaceInset100 * 2)
464
+ }
465
+
466
+ func body(content: Content) -> some View {
467
+ content
374
468
  .font(Font.system(size: DesignTokens.typographyInput.fontSize)
375
469
  .weight(DesignTokens.typographyInput.fontWeight))
376
- .foregroundColor(isDisabled ? theme.colorTextMuted : theme.colorTextDefault)
470
+ .foregroundColor(theme.colorTextDefault)
377
471
  .padding(.leading, DesignTokens.spaceInset100)
378
472
  .padding(.vertical, DesignTokens.spaceInset100)
379
473
  .padding(.trailing, hasTrailingIcon ? 0 : DesignTokens.spaceInset100)
474
+ .frame(maxWidth: .infinity, minHeight: minFieldHeight, alignment: .leading)
380
475
  .background(theme.colorStructureCanvas)
381
476
  .overlay(
382
477
  RoundedRectangle(cornerRadius: DesignTokens.radius150)
@@ -386,7 +481,7 @@ struct InputTextBaseFieldStyle: TextFieldStyle {
386
481
  RoundedRectangle(cornerRadius: DesignTokens.radius150)
387
482
  .stroke(theme.colorActionPrimary, lineWidth: DesignTokens.accessibilityFocusWidth)
388
483
  .padding(-DesignTokens.accessibilityFocusOffset)
389
- .opacity(isFocused && !isDisabled ? 1 : 0)
484
+ .opacity(isFocused ? 1 : 0)
390
485
  .animation(
391
486
  reduceMotion ? .none : Animation.timingCurve(0.4, 0.0, 0.2, 1.0, duration: DesignTokens.MotionFocusTransition.duration),
392
487
  value: isFocused
@@ -395,6 +490,30 @@ struct InputTextBaseFieldStyle: TextFieldStyle {
395
490
  }
396
491
  }
397
492
 
493
+ // MARK: - Custom Text Field Style
494
+
495
+ /**
496
+ * Custom text field style for consistent appearance.
497
+ * Delegates all visual treatment to InputTextBaseFieldChrome (shared with
498
+ * the read-only rendering path).
499
+ */
500
+ struct InputTextBaseFieldStyle: TextFieldStyle {
501
+ let borderColor: Color
502
+ let isFocused: Bool
503
+ let hasError: Bool
504
+ let isSuccess: Bool
505
+ let hasTrailingIcon: Bool
506
+
507
+ func _body(configuration: TextField<Self._Label>) -> some View {
508
+ configuration
509
+ .modifier(InputTextBaseFieldChrome(
510
+ borderColor: borderColor,
511
+ isFocused: isFocused,
512
+ hasTrailingIcon: hasTrailingIcon
513
+ ))
514
+ }
515
+ }
516
+
398
517
  // MARK: - Preview
399
518
 
400
519
  #if DEBUG
@@ -417,8 +536,7 @@ struct InputTextBase_Previews: PreviewProvider {
417
536
  placeholder: nil,
418
537
  readOnly: false,
419
538
  required: false,
420
- maxLength: nil,
421
- isDisabled: false
539
+ maxLength: nil
422
540
  )
423
541
 
424
542
  InputTextBase(
@@ -437,8 +555,7 @@ struct InputTextBase_Previews: PreviewProvider {
437
555
  placeholder: nil,
438
556
  readOnly: false,
439
557
  required: false,
440
- maxLength: nil,
441
- isDisabled: false
558
+ maxLength: nil
442
559
  )
443
560
 
444
561
  InputTextBase(
@@ -457,8 +574,7 @@ struct InputTextBase_Previews: PreviewProvider {
457
574
  placeholder: nil,
458
575
  readOnly: false,
459
576
  required: true,
460
- maxLength: nil,
461
- isDisabled: false
577
+ maxLength: nil
462
578
  )
463
579
 
464
580
  InputTextBase(
@@ -477,28 +593,48 @@ struct InputTextBase_Previews: PreviewProvider {
477
593
  placeholder: nil,
478
594
  readOnly: false,
479
595
  required: false,
480
- maxLength: nil,
481
- isDisabled: false
596
+ maxLength: nil
482
597
  )
483
-
598
+
599
+ // Read-only (B-prime): selectable Text in the shared chrome
484
600
  InputTextBase(
485
- id: "preview-disabled",
486
- label: "Email",
487
- value: .constant("user@example.com"),
601
+ id: "preview-read-only",
602
+ label: "Reference number",
603
+ value: .constant("REF-2026-07-15"),
488
604
  onChange: nil,
489
605
  onFocus: nil,
490
606
  onBlur: nil,
491
- helperText: "This field is disabled",
607
+ helperText: "Long-press to copy",
492
608
  errorMessage: nil,
493
609
  isSuccess: false,
494
610
  showInfoIcon: false,
495
- type: .email,
496
- autocomplete: .emailAddress,
611
+ type: .text,
612
+ autocomplete: nil,
497
613
  placeholder: nil,
498
- readOnly: false,
614
+ readOnly: true,
615
+ required: false,
616
+ maxLength: nil
617
+ )
618
+
619
+ // Read-only, empty: label rests in placeholder position,
620
+ // chrome reserves full field height
621
+ InputTextBase(
622
+ id: "preview-read-only-empty",
623
+ label: "Reference number",
624
+ value: .constant(""),
625
+ onChange: nil,
626
+ onFocus: nil,
627
+ onBlur: nil,
628
+ helperText: nil,
629
+ errorMessage: nil,
630
+ isSuccess: false,
631
+ showInfoIcon: false,
632
+ type: .text,
633
+ autocomplete: nil,
634
+ placeholder: nil,
635
+ readOnly: true,
499
636
  required: false,
500
- maxLength: nil,
501
- isDisabled: true
637
+ maxLength: nil
502
638
  )
503
639
  }
504
640
  .padding()
@@ -54,7 +54,19 @@ export interface InputTextBaseProps {
54
54
  /** Placeholder text (only shown when label is floated and input is empty) */
55
55
  placeholder?: string;
56
56
 
57
- /** Read-only state (alternative to disabled) */
57
+ /**
58
+ * Read-only state (state_readonly contract): the value is displayed
59
+ * non-editably while remaining perceivable, selectable, and copyable —
60
+ * never rendered with disabled semantics on any platform.
61
+ *
62
+ * Platform note: web and Android readOnly fields participate in keyboard
63
+ * focus order; iOS renders readOnly as selectable static text in the
64
+ * field chrome and is not in focus order (declared carve-out — see
65
+ * contracts.yaml interaction_focusable).
66
+ *
67
+ * NOT supported by Input-Text-Password: readOnly is contracted out of the
68
+ * Password variant (see its contracts.yaml excludes) and is ignored there.
69
+ */
58
70
  readOnly?: boolean;
59
71
 
60
72
  /** Required field indicator */
@@ -101,7 +101,11 @@ properties:
101
101
  type: boolean
102
102
  required: false
103
103
  default: false
104
- description: Read-only state (alternative to disabled)
104
+ description: >-
105
+ Read-only display state (state_readonly contract) — the value is
106
+ non-editable but stays perceivable, selectable, and copyable; no
107
+ keyboard/IME is raised and disabled semantics are never applied. iOS
108
+ renders it as selectable static text inside the field chrome.
105
109
 
106
110
  required:
107
111
  type: boolean
@@ -97,7 +97,7 @@ InputTextEmail(
97
97
  | `isSuccess` | boolean | No | false | Success state indicator |
98
98
  | `showInfoIcon` | boolean | No | false | Show info icon |
99
99
  | `placeholder` | string | No | - | Placeholder text |
100
- | `readOnly` | boolean | No | false | Read-only state |
100
+ | `readOnly` | boolean | No | false | Read-only display state (`state_readonly` contract): value is non-editable but selectable and copyable; no keyboard raised; iOS renders selectable text in the field chrome |
101
101
  | `required` | boolean | No | false | Required field indicator |
102
102
  | `maxLength` | number | No | - | Maximum input length |
103
103
  | `testID` | string | No | - | Test ID for automation |
@@ -126,15 +126,16 @@ InputTextEmail(
126
126
  3. **validates_on_blur**: Validation triggers on blur
127
127
  4. **error_state_display**: Shows error message and styling
128
128
  5. **success_state_display**: Shows success styling
129
- 6. **disabled_state**: Prevents interaction when disabled
130
- 7. **trailing_icon_display**: Shows contextual trailing icons
131
- 8. **focus_ring**: WCAG 2.4.7 focus visible indicator
132
- 9. **reduced_motion_support**: Respects prefers-reduced-motion
129
+ 6. **trailing_icon_display**: Shows contextual trailing icons
130
+ 7. **focus_ring**: WCAG 2.4.7 focus visible indicator
131
+ 8. **reduced_motion_support**: Respects prefers-reduced-motion
132
+
133
+ **Excluded**: `state_disabled` — DesignerPunk does not support disabled states for usability and accessibility reasons. If an action is unavailable, the component should not be rendered (adjudicated 2026-07-15).
133
134
 
134
135
  ### Email-Specific Contracts
135
136
 
136
- 10. **validates_email_format**: Validates email against RFC 5322 pattern
137
- 11. **provides_email_autocomplete**: Enables browser/platform email autofill
137
+ 9. **validates_email_format**: Validates email against RFC 5322 pattern
138
+ 10. **provides_email_autocomplete**: Enables browser/platform email autofill
138
139
 
139
140
  ## Validation
140
141
 
@@ -78,7 +78,6 @@ private fun isValidEmail(email: String): Boolean {
78
78
  * @param maxLength Maximum length for input value
79
79
  * @param imeAction IME action for keyboard
80
80
  * @param keyboardActions Keyboard actions
81
- * @param isDisabled Disabled state
82
81
  * @param invalidEmailMessage Custom invalid email message
83
82
  * @param customValidator Custom email validator function
84
83
  */
@@ -102,7 +101,6 @@ fun InputTextEmail(
102
101
  maxLength: Int? = null,
103
102
  imeAction: ImeAction = ImeAction.Done,
104
103
  keyboardActions: KeyboardActions = KeyboardActions.Default,
105
- isDisabled: Boolean = false,
106
104
  invalidEmailMessage: String = DEFAULT_INVALID_EMAIL_MESSAGE,
107
105
  customValidator: ((String) -> Boolean)? = null
108
106
  ) {
@@ -158,8 +156,7 @@ fun InputTextEmail(
158
156
  required = required,
159
157
  maxLength = maxLength,
160
158
  imeAction = imeAction,
161
- keyboardActions = keyboardActions,
162
- isDisabled = isDisabled
159
+ keyboardActions = keyboardActions
163
160
  )
164
161
  }
165
162
 
@@ -104,10 +104,7 @@ struct InputTextEmail: View {
104
104
 
105
105
  /// Maximum length for input value
106
106
  let maxLength: Int?
107
-
108
- /// Disabled state
109
- let isDisabled: Bool
110
-
107
+
111
108
  /// Custom invalid email message
112
109
  let invalidEmailMessage: String
113
110
 
@@ -155,7 +152,6 @@ struct InputTextEmail: View {
155
152
  readOnly: Bool = false,
156
153
  required: Bool = false,
157
154
  maxLength: Int? = nil,
158
- isDisabled: Bool = false,
159
155
  invalidEmailMessage: String = defaultInvalidEmailMessage,
160
156
  customValidator: ((String) -> Bool)? = nil
161
157
  ) {
@@ -174,7 +170,6 @@ struct InputTextEmail: View {
174
170
  self.readOnly = readOnly
175
171
  self.required = required
176
172
  self.maxLength = maxLength
177
- self.isDisabled = isDisabled
178
173
  self.invalidEmailMessage = invalidEmailMessage
179
174
  self.customValidator = customValidator
180
175
  }
@@ -207,8 +202,7 @@ struct InputTextEmail: View {
207
202
  placeholder: placeholder,
208
203
  readOnly: readOnly,
209
204
  required: required,
210
- maxLength: maxLength,
211
- isDisabled: isDisabled
205
+ maxLength: maxLength
212
206
  )
213
207
  }
214
208
 
@@ -268,14 +262,6 @@ struct InputTextEmail_Previews: PreviewProvider {
268
262
  isSuccess: true
269
263
  )
270
264
 
271
- InputTextEmail(
272
- id: "preview-disabled",
273
- label: "Email",
274
- value: .constant("user@example.com"),
275
- helperText: "This field is disabled",
276
- isDisabled: true
277
- )
278
-
279
265
  InputTextEmail(
280
266
  id: "preview-required",
281
267
  label: "Email",
@@ -103,7 +103,12 @@ properties:
103
103
  type: boolean
104
104
  required: false
105
105
  default: false
106
- description: Read-only state (alternative to disabled)
106
+ description: >-
107
+ Accepted for API stability but IGNORED — readOnly is contracted out of
108
+ Input-Text-Password (contracts.yaml excludes.state_readonly; adjudication
109
+ .kiro/issues/input-text-base-ios-readonly-adjudication.md, RULED B-prime,
110
+ Peter 2026-07-15). Implementations never forward it to the base on any
111
+ platform.
107
112
 
108
113
  required:
109
114
  type: boolean
@@ -262,13 +267,15 @@ platform_notes:
262
267
  ios:
263
268
  implementation: SwiftUI View wrapping InputTextBase
264
269
  file: platforms/ios/InputTextPassword.ios.swift
265
- toggle: Button with SF Symbol eye/eye.slash
270
+ toggle: Button with SF Symbol eye/eye.slash, injected via the base's trailingContent slot
266
271
  autocomplete: UITextContentType.password or .newPassword
272
+ visibility: Toggles by switching base type between .password and .text
267
273
  android:
268
274
  implementation: Jetpack Compose Composable wrapping InputTextBase
269
275
  file: platforms/android/InputTextPassword.android.kt
270
- toggle: IconButton with visibility icons
276
+ toggle: IconButton with visibility icons, injected via the base's trailingContent slot
271
277
  autocomplete: Autofill framework password type
278
+ visibility: Toggles via the base's visualTransformation override (type stays PASSWORD)
272
279
 
273
280
  # Inheritance relationship
274
281
  inheritance:
@@ -178,7 +178,7 @@ InputTextPassword(
178
178
  | `isSuccess` | boolean | No | false | Success state indicator |
179
179
  | `showInfoIcon` | boolean | No | false | Show info icon |
180
180
  | `placeholder` | string | No | - | Placeholder text |
181
- | `readOnly` | boolean | No | false | Read-only state |
181
+ | `readOnly` | boolean | No | false | Accepted but **ignored** — readOnly is contracted out of Input-Text-Password (never forwarded to the base; see contracts.yaml `excludes.state_readonly` and the adjudication `.kiro/issues/input-text-base-ios-readonly-adjudication.md`, RULED B-prime 2026-07-15) |
182
182
  | `required` | boolean | No | false | Required field indicator |
183
183
  | `maxLength` | number | No | - | Maximum input length |
184
184
  | `testID` | string | No | - | Test ID for automation |
@@ -216,16 +216,17 @@ InputTextPassword(
216
216
  3. **validates_on_blur**: Validation triggers on blur
217
217
  4. **error_state_display**: Shows error message and styling
218
218
  5. **success_state_display**: Shows success styling
219
- 6. **disabled_state**: Prevents interaction when disabled
220
- 7. **trailing_icon_display**: Shows contextual trailing icons
221
- 8. **focus_ring**: WCAG 2.4.7 focus visible indicator
222
- 9. **reduced_motion_support**: Respects prefers-reduced-motion
219
+ 6. **trailing_icon_display**: Shows contextual trailing icons
220
+ 7. **focus_ring**: WCAG 2.4.7 focus visible indicator
221
+ 8. **reduced_motion_support**: Respects prefers-reduced-motion
222
+
223
+ **Excluded**: `state_disabled` — DesignerPunk does not support disabled states for usability and accessibility reasons. If an action is unavailable, the component should not be rendered (adjudicated 2026-07-15).
223
224
 
224
225
  ### Password-Specific Contracts
225
226
 
226
- 10. **provides_secure_input**: Masks password input by default
227
- 11. **supports_password_toggle**: Show/hide password functionality
228
- 12. **provides_password_autocomplete**: Enables browser/platform password autofill
227
+ 9. **provides_secure_input**: Masks password input by default
228
+ 10. **supports_password_toggle**: Show/hide password functionality
229
+ 11. **provides_password_autocomplete**: Enables browser/platform password autofill
229
230
 
230
231
  ## Validation
231
232
 
@@ -61,3 +61,8 @@ excludes:
61
61
  state_disabled:
62
62
  reason: "DesignerPunk does not support disabled states for usability and accessibility reasons. If an action is unavailable, the component should not be rendered."
63
63
  category: state
64
+
65
+ state_readonly:
66
+ reason: "readOnly is contracted out of Input-Text-Password: zero named consumers, and excluding it deletes a security fork — the base's iOS read-only path (B-prime) renders the value as selectable plaintext Text, which must never be reachable for a secret. The profile idiom for a locked password is a display row ('Password ••••••••') plus a Change action, not a read-only password field. Implementations strip/ignore the inherited readOnly prop. If a product case ever materializes, the invariant-based fallback rule recorded in the adjudication ([DATA R2] §3: masked value never copyable; plaintext never in the accessibility tree while masked; announced semantically as hidden; no keyboard; toggle functional; revealed plaintext selectable/copyable) is the recorded alternative."
67
+ category: state
68
+ reference: ".kiro/issues/input-text-base-ios-readonly-adjudication.md (RULED B-prime, Peter 2026-07-15, condition 4)"