@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
@@ -23,11 +23,45 @@ import * as fs from 'fs';
23
23
  import * as os from 'os';
24
24
  import * as path from 'path';
25
25
  import { runInit } from '../init';
26
+ import { resolvePackageRoot } from '../shared/resolvePackageRoot';
26
27
 
27
28
  // ---------------------------------------------------------------------------
28
29
  // Helpers
29
30
  // ---------------------------------------------------------------------------
30
31
 
32
+ /**
33
+ * Count files recursively under `dir`, matching `copyDir()`'s counting semantics
34
+ * in init.ts (files only, descending into subdirectories).
35
+ *
36
+ * Used to DERIVE the expected governance-doc count from the same source `init`
37
+ * copies from, rather than hard-coding a literal. The governance corpus is the
38
+ * relocated non-identity docs (Spec 119-A) and grows by design — a hard-coded
39
+ * count drifts (and repeatedly HAS drifted, e.g. 80 → 81 → 82) every time a doc
40
+ * is added, with no relation to the behavior under test. Deriving from source
41
+ * keeps the real check — "init copies EVERY governance doc, dropping none" —
42
+ * while decoupling it from the corpus's exact size.
43
+ */
44
+ function countFilesRecursive(dir: string): number {
45
+ let count = 0;
46
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
47
+ count += entry.isDirectory()
48
+ ? countFilesRecursive(path.join(dir, entry.name))
49
+ : entry.isFile()
50
+ ? 1
51
+ : 0;
52
+ }
53
+ return count;
54
+ }
55
+
56
+ // The package root `init` copies from is resolved by `resolvePackageRoot(<init's
57
+ // dir>)` = two levels up from `src/cli/`. This test file lives one level deeper
58
+ // (`src/cli/__tests__/`), so pass its parent (`src/cli/`) to resolve the SAME root.
59
+ const PKG_ROOT = resolvePackageRoot(path.join(__dirname, '..'));
60
+
61
+ // Expected governance-doc count, derived from source (see countFilesRecursive).
62
+ // NOT hard-coded: governance/ grows as the corpus grows.
63
+ const GOVERNANCE_DOC_COUNT = countFilesRecursive(path.join(PKG_ROOT, 'governance'));
64
+
31
65
  /**
32
66
  * Create a unique scratch directory under the OS temp dir.
33
67
  * Returns the absolute path. Caller is responsible for cleanup.
@@ -140,11 +174,12 @@ describe('CLI init — integration', () => {
140
174
  expect(secondOutput).toContain('existing files preserved');
141
175
  // Spec 119-A two-root split: 9 identity docs ship in .kiro/steering/
142
176
  // (8 identity + the NEW Task-Completion-Protocol added in Task 8; the
143
- // meta-guide was removed in 119-A Task 10.5 → back to 9),
144
- // and the 81 relocated non-identity docs ship in governance/
145
- // (80 + Steering-Addressing-Conventions.md added in Task 12).
177
+ // meta-guide was removed in 119-A Task 10.5 → back to 9). The identity set
178
+ // is a LOCKED always-set — asserted as exactly 9 on purpose, so a change to
179
+ // it is a signal worth catching. The governance corpus, by contrast, grows
180
+ // routinely, so its count is derived from source (GOVERNANCE_DOC_COUNT).
146
181
  expect(secondOutput).toContain('✓ steering docs: 9 existing files preserved');
147
- expect(secondOutput).toContain('✓ governance docs: 81 existing files preserved');
182
+ expect(secondOutput).toContain(`✓ governance docs: ${GOVERNANCE_DOC_COUNT} existing files preserved`);
148
183
  });
149
184
 
150
185
  test('preserves existing files — no overwrites after second run', async () => {
@@ -190,11 +225,11 @@ describe('CLI init — integration', () => {
190
225
  // Spec 119-A two-root split: the package contributes 9 identity steering
191
226
  // files (8 identity + the NEW Task-Completion-Protocol from Task 8; the
192
227
  // meta-guide was removed in Task 10.5; no conflict with designerpunk.md
193
- // because the package doesn't have a file by that name) and 81 relocated
194
- // docs into the separate governance/ dir
195
- // (80 + Steering-Addressing-Conventions.md added in Task 12).
228
+ // because the package doesn't have a file by that name) and the relocated
229
+ // non-identity docs into the separate governance/ dir. Steering is the
230
+ // locked 9; governance is derived from source (grows with the corpus).
196
231
  expect(output).toContain('✓ steering docs: 9 new files');
197
- expect(output).toContain('✓ governance docs: 81 new files');
232
+ expect(output).toContain(`✓ governance docs: ${GOVERNANCE_DOC_COUNT} new files`);
198
233
 
199
234
  // Custom file preserved
200
235
  expect(
@@ -205,10 +240,9 @@ describe('CLI init — integration', () => {
205
240
  const steeringFiles = fs.readdirSync(path.join(scratchDir, '.kiro/steering'));
206
241
  expect(steeringFiles.length).toBe(10);
207
242
 
208
- // The 81 relocated docs land in governance/
209
- // (80 + Steering-Addressing-Conventions.md added in Task 12)
243
+ // The relocated docs land in governance/ — every one init copied from source.
210
244
  const governanceFiles = fs.readdirSync(path.join(scratchDir, 'governance'));
211
- expect(governanceFiles.length).toBe(81);
245
+ expect(governanceFiles.length).toBe(GOVERNANCE_DOC_COUNT);
212
246
  });
213
247
  });
214
248
 
@@ -60,7 +60,7 @@ properties:
60
60
  alt:
61
61
  type: string
62
62
  required: false
63
- description: Alt text for accessibility. Required if src provided.
63
+ description: Alt text for accessibility. Required if src provided on human type, unless decorative is true (agent type ignores src entirely, per Req 5 AC 5).
64
64
  interactive:
65
65
  type: boolean
66
66
  required: false
@@ -212,14 +212,20 @@ describe('Avatar Component Accessibility', () => {
212
212
  });
213
213
 
214
214
  it('should apply empty alt when alt is empty string', async () => {
215
+ // Spec 126 (O2): uses the raw-attribute form (setAttribute) to produce a
216
+ // genuine alt="" — the `alt` *property* setter treats '' as falsy and calls
217
+ // removeAttribute, collapsing `avatar.alt = ''` to the same "absent" state as
218
+ // omitting alt entirely (see Avatar.web.ts `set alt`). Only the attribute form
219
+ // yields a real empty-string alt, and under the settled warn condition
220
+ // (`alt == null`), a real empty-string alt does not warn — so no spy is needed.
215
221
  const avatar = document.createElement('avatar-base') as AvatarBaseElement;
216
222
  avatar.type = 'human';
217
223
  avatar.src = 'https://example.com/profile.jpg';
218
- avatar.alt = '';
224
+ avatar.setAttribute('alt', '');
219
225
  document.body.appendChild(avatar);
220
-
226
+
221
227
  await new Promise(resolve => setTimeout(resolve, 0));
222
-
228
+
223
229
  const img = avatar.shadowRoot?.querySelector('.avatar__image');
224
230
  expect(img).toBeTruthy();
225
231
  expect(img?.getAttribute('alt')).toBe('');
@@ -285,16 +291,124 @@ describe('Avatar Component Accessibility', () => {
285
291
 
286
292
  it('should NOT warn when no src is provided', async () => {
287
293
  const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
288
-
294
+
289
295
  const avatar = document.createElement('avatar-base') as AvatarBaseElement;
290
296
  avatar.type = 'human';
291
297
  // No src, no alt
292
298
  document.body.appendChild(avatar);
293
-
299
+
294
300
  await new Promise(resolve => setTimeout(resolve, 0));
295
-
301
+
296
302
  expect(consoleSpy).not.toHaveBeenCalled();
297
-
303
+
304
+ consoleSpy.mockRestore();
305
+ });
306
+ });
307
+
308
+ // ============================================================================
309
+ // Alt Text Warning — Decorative & Type-Scope Edge Semantics (Spec 126, O2)
310
+ // ============================================================================
311
+ //
312
+ // Settled warn condition: `type === 'human' && src && alt == null && !decorative`
313
+ // @see Requirements: 5.4 - Human type + src SHALL require alt for accessibility
314
+ // @see Requirements: 5 AC 5 - Agent type SHALL ignore src entirely
315
+ // @see .kiro/specs/126-avatar-decorative-warn/design-outline.md § 4/5
316
+ //
317
+ // Together with the sentinel test above ('should warn when src is provided
318
+ // without alt'), this block forms the six-test matrix settled by Spec 126 § 5
319
+ // item 2. The sentinel is left unchanged (Spec 126 § 7 non-regression signal).
320
+ describe('Alt Text Warning — Decorative & Type-Scope Edge Semantics (Spec 126)', () => {
321
+ it('should NOT warn when alt="" is set via raw attribute (explicit empty, not "absent")', async () => {
322
+ const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
323
+
324
+ const avatar = document.createElement('avatar-base') as AvatarBaseElement;
325
+ avatar.type = 'human';
326
+ avatar.src = 'https://example.com/profile.jpg';
327
+ // Raw attribute form is the only path that yields a genuine alt="" (getter
328
+ // returns '', not null) — the `alt` property setter collapses falsy values to
329
+ // removeAttribute, so this test must use setAttribute to exercise the real case.
330
+ avatar.setAttribute('alt', '');
331
+ document.body.appendChild(avatar);
332
+
333
+ await new Promise(resolve => setTimeout(resolve, 0));
334
+
335
+ expect(consoleSpy).not.toHaveBeenCalled();
336
+
337
+ consoleSpy.mockRestore();
338
+ });
339
+
340
+ it('should warn when alt is set to empty string via the property (collapses to null via the setter)', async () => {
341
+ const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
342
+
343
+ const avatar = document.createElement('avatar-base') as AvatarBaseElement;
344
+ avatar.type = 'human';
345
+ avatar.src = 'https://example.com/profile.jpg';
346
+ // Property-form empty string: the `alt` setter treats '' as falsy and calls
347
+ // removeAttribute('alt'), so this lands in the "alt absent" bucket and warns —
348
+ // pins the attribute-vs-property collapse explicitly (Spec 126 § 5 item 2).
349
+ avatar.alt = '';
350
+ document.body.appendChild(avatar);
351
+
352
+ await new Promise(resolve => setTimeout(resolve, 0));
353
+
354
+ expect(consoleSpy).toHaveBeenCalledWith(
355
+ expect.stringContaining('alt')
356
+ );
357
+
358
+ consoleSpy.mockRestore();
359
+ });
360
+
361
+ it('should NOT warn when decorative is true, even without alt (co-asserted with aria-hidden)', async () => {
362
+ const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
363
+
364
+ const avatar = document.createElement('avatar-base') as AvatarBaseElement;
365
+ avatar.type = 'human';
366
+ avatar.src = 'https://example.com/profile.jpg';
367
+ avatar.decorative = true;
368
+ // No alt provided
369
+ document.body.appendChild(avatar);
370
+
371
+ await new Promise(resolve => setTimeout(resolve, 0));
372
+
373
+ expect(consoleSpy).not.toHaveBeenCalled();
374
+
375
+ // Co-assert the semantic linkage (Req 9.2): the warn is correctly suppressed
376
+ // because the avatar is genuinely hidden from screen readers, not incidentally.
377
+ const container = avatar.shadowRoot?.querySelector('.avatar');
378
+ expect(container?.getAttribute('aria-hidden')).toBe('true');
379
+
380
+ consoleSpy.mockRestore();
381
+ });
382
+
383
+ it('should NOT warn for agent type with src and no alt (Req 5 AC 5 — agent ignores src entirely)', async () => {
384
+ const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
385
+
386
+ const avatar = document.createElement('avatar-base') as AvatarBaseElement;
387
+ avatar.type = 'agent';
388
+ avatar.src = 'https://example.com/profile.jpg';
389
+ // No alt provided
390
+ document.body.appendChild(avatar);
391
+
392
+ await new Promise(resolve => setTimeout(resolve, 0));
393
+
394
+ expect(consoleSpy).not.toHaveBeenCalled();
395
+
396
+ consoleSpy.mockRestore();
397
+ });
398
+
399
+ it('should NOT warn for agent type with src and alt both present (confirms silencing is type-driven, not alt-driven)', async () => {
400
+ const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
401
+
402
+ const avatar = document.createElement('avatar-base') as AvatarBaseElement;
403
+ avatar.type = 'agent';
404
+ avatar.src = 'https://example.com/profile.jpg';
405
+ avatar.alt = 'Ignored for agent type';
406
+ document.body.appendChild(avatar);
407
+
408
+ await new Promise(resolve => setTimeout(resolve, 0));
409
+
410
+ expect(consoleSpy).not.toHaveBeenCalled();
411
+
298
412
  consoleSpy.mockRestore();
299
413
  });
300
414
  });
@@ -287,6 +287,7 @@ describe('Avatar Component Image Handling', () => {
287
287
  const avatar = document.createElement('avatar-base') as AvatarBaseElement;
288
288
  avatar.type = 'agent';
289
289
  avatar.src = 'https://example.com/agent-image.jpg';
290
+ avatar.alt = 'Agent profile'; // honor the alt-with-src contract (Req 5.4)
290
291
  document.body.appendChild(avatar);
291
292
 
292
293
  await new Promise(resolve => setTimeout(resolve, 0));
@@ -299,6 +300,7 @@ describe('Avatar Component Image Handling', () => {
299
300
  const avatar = document.createElement('avatar-base') as AvatarBaseElement;
300
301
  avatar.type = 'agent';
301
302
  avatar.src = 'https://example.com/agent-image.jpg';
303
+ avatar.alt = 'Agent profile'; // honor the alt-with-src contract (Req 5.4)
302
304
  document.body.appendChild(avatar);
303
305
 
304
306
  await new Promise(resolve => setTimeout(resolve, 0));
@@ -311,6 +313,7 @@ describe('Avatar Component Image Handling', () => {
311
313
  const avatar = document.createElement('avatar-base') as AvatarBaseElement;
312
314
  avatar.type = 'agent';
313
315
  avatar.src = 'https://example.com/agent-image.jpg';
316
+ avatar.alt = 'Agent profile'; // honor the alt-with-src contract (Req 5.4)
314
317
  document.body.appendChild(avatar);
315
318
 
316
319
  await new Promise(resolve => setTimeout(resolve, 0));
@@ -218,27 +218,33 @@ describe('Avatar Component Core API', () => {
218
218
  // ============================================================================
219
219
 
220
220
  describe('Src Prop', () => {
221
+ // Fixtures set `alt` alongside `src` so they honor the component's own a11y
222
+ // contract (Req 5.4: alt is required with src) — otherwise the component
223
+ // rightly warns, polluting CI logs. The assertions below are about src only.
221
224
  it('should accept src value', () => {
222
225
  const avatar = document.createElement('avatar-base') as AvatarBaseElement;
223
226
  avatar.src = 'https://example.com/image.jpg';
227
+ avatar.alt = 'Test avatar';
224
228
  document.body.appendChild(avatar);
225
-
229
+
226
230
  expect(avatar.src).toBe('https://example.com/image.jpg');
227
231
  expect(avatar.getAttribute('src')).toBe('https://example.com/image.jpg');
228
232
  });
229
233
 
230
234
  it('should update src via attribute', () => {
231
235
  const avatar = document.createElement('avatar-base') as AvatarBaseElement;
236
+ avatar.alt = 'Test avatar';
232
237
  document.body.appendChild(avatar);
233
-
238
+
234
239
  avatar.setAttribute('src', 'https://example.com/new-image.jpg');
235
240
  expect(avatar.src).toBe('https://example.com/new-image.jpg');
236
241
  });
237
242
 
238
243
  it('should update src via property', () => {
239
244
  const avatar = document.createElement('avatar-base') as AvatarBaseElement;
245
+ avatar.alt = 'Test avatar';
240
246
  document.body.appendChild(avatar);
241
-
247
+
242
248
  avatar.src = 'https://example.com/profile.png';
243
249
  expect(avatar.getAttribute('src')).toBe('https://example.com/profile.png');
244
250
  });
@@ -246,8 +252,9 @@ describe('Avatar Component Core API', () => {
246
252
  it('should remove src attribute when set to null', () => {
247
253
  const avatar = document.createElement('avatar-base') as AvatarBaseElement;
248
254
  avatar.src = 'https://example.com/image.jpg';
255
+ avatar.alt = 'Test avatar';
249
256
  document.body.appendChild(avatar);
250
-
257
+
251
258
  avatar.src = null;
252
259
  expect(avatar.src).toBeNull();
253
260
  expect(avatar.hasAttribute('src')).toBe(false);
@@ -566,8 +573,9 @@ describe('Avatar Component Core API', () => {
566
573
 
567
574
  it('should reflect src property to attribute', () => {
568
575
  const avatar = document.createElement('avatar-base') as AvatarBaseElement;
576
+ avatar.alt = 'Test avatar'; // honor the alt-with-src contract (Req 5.4)
569
577
  document.body.appendChild(avatar);
570
-
578
+
571
579
  avatar.src = 'https://example.com/test.jpg';
572
580
  expect(avatar.getAttribute('src')).toBe('https://example.com/test.jpg');
573
581
  });
@@ -629,8 +637,9 @@ describe('Avatar Component Core API', () => {
629
637
  it('should read src attribute as property', () => {
630
638
  const avatar = document.createElement('avatar-base') as AvatarBaseElement;
631
639
  avatar.setAttribute('src', 'https://example.com/img.png');
640
+ avatar.setAttribute('alt', 'Test avatar'); // honor the alt-with-src contract (Req 5.4)
632
641
  document.body.appendChild(avatar);
633
-
642
+
634
643
  expect(avatar.src).toBe('https://example.com/img.png');
635
644
  });
636
645
 
@@ -33,7 +33,16 @@
33
33
  * Icon Size Derivations (in Avatar.web.ts):
34
34
  * - xs: calc(icon.size050 × 0.75) = 12px
35
35
  * - xxl: calc(icon.size050 × 4) = 64px
36
- *
36
+ *
37
+ * TOKEN FAMILY: all dimensional Avatar tokens (container `size.*` AND icon
38
+ * `icon.size.*` gap fillers) are registered by the single sizing-family call
39
+ * `AvatarSizingTokens` below. The `icon.size.*` pair previously lived in a
40
+ * separate `AvatarTokens` call stamped `family: 'spacing'` — a mislabel, since
41
+ * both are dimensional sizing values. `AvatarTokens` is removed; consumers use
42
+ * `AvatarSizingTokens` or the `getAvatarIconSize()` / `getAvatarSize()` accessors.
43
+ * Generated platform output is unaffected: both calls declare `component: 'Avatar'`,
44
+ * so all tokens still land in one `AvatarTokens` Swift enum / Kotlin object.
45
+ *
37
46
  * COLOR TOKENS (Spec 058):
38
47
  * Avatar color tokens are defined in this file following the Rosetta System architecture
39
48
  * which mandates component tokens live at src/components/[ComponentName]/tokens.ts.
@@ -56,13 +65,24 @@ import { defineComponentTokens } from '../../../build/tokens';
56
65
  import { SIZING_BASE_VALUE, sizingTokens } from '../../../tokens/SizingTokens';
57
66
 
58
67
  /**
59
- * Avatar sizing tokens — container dimensions for each size variant.
68
+ * Avatar sizing tokens — container dimensions and icon dimensions for each size variant.
60
69
  *
61
70
  * Previously in a separate file (avatar-sizing.tokens.ts). Inlined to prevent
62
71
  * architectural anomaly (no other component splits tokens across files) and
63
72
  * simplify the package surface for sync.
64
73
  *
74
+ * ONE CALL PER FAMILY. Every token registered by a single defineComponentTokens()
75
+ * call is stamped with that call's `family`, and the family drives platform output
76
+ * (Swift type, Kotlin `.dp` suffix, primitive class name). The `icon.size.*` gap
77
+ * fillers below are dimensional sizing values, so they live in this sizing-family
78
+ * call — they were previously mis-stamped `family: 'spacing'` in a separate
79
+ * `AvatarTokens` call, the same latent mislabel class fixed for Button-Icon.
80
+ * Do NOT spread/merge this result with another call's result — the rich metadata
81
+ * rides on a non-enumerable brand (see src/build/tokens/defineComponentTokens.ts,
82
+ * TOKEN_CONTRACT_BRAND) that a spread would silently drop.
83
+ *
65
84
  * @see .kiro/specs/092-sizing-token-family/design.md
85
+ * @see src/components/core/Button-Icon/buttonIcon.tokens.ts for the two-call pattern
66
86
  */
67
87
  export const AvatarSizingTokens = defineComponentTokens({
68
88
  component: 'Avatar',
@@ -92,38 +112,15 @@ export const AvatarSizingTokens = defineComponentTokens({
92
112
  reference: sizingTokens.size1600,
93
113
  reasoning: 'Extra extra large avatar (128px). Full profile view, onboarding.',
94
114
  },
95
- },
96
- });
97
115
 
98
- /**
99
- * Avatar component tokens defined using the hybrid authoring API.
100
- *
101
- * Each token either references a primitive spacing token or uses a family-conformant
102
- * derivation, and includes reasoning explaining why the token exists.
103
- *
104
- * NOTE: Web platform uses CSS calc() with icon tokens for sizing (see Avatar.styles.css).
105
- * These component tokens are primarily used for iOS/Android platforms and documentation.
106
- *
107
- * Size token values:
108
- * - size.xs: 24px (3 × base, references size300)
109
- * - size.sm: 32px (4 × base, references size400)
110
- * - size.md: 40px (5 × base, references size500)
111
- * - size.lg: 48px (6 × base, references size600)
112
- * - size.xl: 80px (10 × base, derivation)
113
- * - size.xxl: 128px (16 × base, derivation)
114
- *
115
- * Icon size token values (gap fillers - web uses calc() instead):
116
- * - icon.size.xs: 12px (1.5 × base, derivation) - web uses calc(icon.size050 × 0.75)
117
- * - icon.size.xxl: 64px (8 × base, derivation) - web uses calc(icon.size050 × 4)
118
- *
119
- * @see Requirements 2.1-2.6, 3.1, 3.6 in .kiro/specs/042-avatar-component/requirements.md
120
- */
121
- export const AvatarTokens = defineComponentTokens({
122
- component: 'Avatar',
123
- family: 'spacing',
124
- tokens: {
125
- // Icon size tokens (gap fillers for sizes without existing icon tokens)
126
- // These fill gaps where no standard icon token exists at the required 50% ratio
116
+ // Icon size tokens (gap fillers for sizes without an existing icon token).
117
+ // Kept on the VALUE path deliberately: sizing primitives exist at both values
118
+ // (size150 = 12, size800 = 64), but the reference path currently emits a
119
+ // fabricated `SizingTokens.<name>` class on iOS/Android that no generated or
120
+ // hand-written platform file defines. Switching these to `reference:` would
121
+ // trade compiling output (`12.dp`) for non-compiling output and break
122
+ // Avatar.android.kt's `val iconSizeXs: Dp = GeneratedAvatarTokens.iconSizeXs`.
123
+ // Revisit once TokenFileGenerator.getFamilyClassName emits a real platform type.
127
124
  'icon.size.xs': {
128
125
  value: SIZING_BASE_VALUE * 1.5,
129
126
  reasoning: 'Icon size for xs avatar (12px = 1.5× base) maintains 50% ratio (12/24). No existing icon token at this size, so component token fills the gap.',
@@ -273,7 +270,7 @@ export function getAvatarSize(variant: AvatarSizeVariant): number {
273
270
  * @see Requirements 3.1, 3.6 in .kiro/specs/042-avatar-component/requirements.md
274
271
  */
275
272
  export function getAvatarIconSize(variant: AvatarIconSizeVariant): number {
276
- return AvatarTokens[`icon.size.${variant}`];
273
+ return AvatarSizingTokens[`icon.size.${variant}`];
277
274
  }
278
275
 
279
276
  /**
@@ -162,7 +162,17 @@ contracts:
162
162
  When not decorative, Avatar provides accessible name:
163
163
  - If alt prop provided: uses alt text
164
164
  - If no alt prop (iOS/Android): "User avatar" for human, "AI agent avatar" for agent
165
- - Web requires alt prop when src is provided
165
+ - Web requires alt prop when src is provided, scoped to human type only
166
+ (agent type ignores src entirely — Req 5 AC 5) and only when decorative
167
+ is false (decorative is the component's own non-informative signal,
168
+ Req 9.2, and takes precedence over the alt requirement)
169
+ - Web edge semantics (settled Spec 126, O2, ratified 2026-07-09): the dev
170
+ warn fires iff `type === 'human' && src && alt == null && !decorative`.
171
+ A raw explicit empty alt (`setAttribute('alt', '')`) does not warn —
172
+ it is treated as the deliberate empty-alt signal. The `alt` property
173
+ setter collapses a falsy value (including '') to removeAttribute, so
174
+ setting `avatar.alt = ''` via the property lands in the "absent" bucket
175
+ and DOES warn — this is a setter-collapse detail, not a design gap.
166
176
  wcag: "1.1.1 Non-text Content"
167
177
  platforms: [web, ios, android]
168
178
  validation:
@@ -11,7 +11,7 @@ export { AVATAR_DEFAULTS } from './types';
11
11
 
12
12
  // Token exports
13
13
  export {
14
- AvatarTokens,
14
+ AvatarSizingTokens,
15
15
  getAvatarSize,
16
16
  getAvatarIconSize,
17
17
  getAvatarSizeTokenReference,
@@ -700,12 +700,31 @@ export class AvatarBaseElement extends HTMLElement {
700
700
  // @see Requirements: 9.2 - Apply aria-hidden="true" when decorative prop is true
701
701
  const ariaHiddenAttr = decorative ? ' aria-hidden="true"' : '';
702
702
 
703
- // Warn in development if src is provided without alt
704
- // @see Requirements: 5.4 - Require alt prop when src is provided
705
- if (src && !alt && typeof console !== 'undefined') {
703
+ // Warn in development if a human-type avatar is missing alt text for its image.
704
+ // @see Requirements: 5.4 - Human type + src SHALL require alt for accessibility
705
+ // @see Requirements: 5 AC 5 - Agent type SHALL ignore src entirely (src not
706
+ // rendered as an image), so agent-type avatars never warn regardless of alt.
707
+ //
708
+ // Settled edge semantics (Spec 126, O2, ratified 2026-07-09):
709
+ // - Scope is human-type only — Req 5.4 is human-type-scoped, and Req 5 AC 5
710
+ // affirmatively establishes that agent-type SHALL ignore src, so a src+no-alt
711
+ // agent avatar is not a violation of anything.
712
+ // - `alt == null` (loose equality — also excludes `undefined`), not `!alt`, is the
713
+ // test. The `alt` attribute getter returns `getAttribute('alt')`. Raw
714
+ // `setAttribute('alt', '')` yields `alt === ''`, which is the HTML-idiomatic
715
+ // explicit-empty-alt signal and must NOT warn. The `alt` *property setter*
716
+ // (see below) treats a falsy value as "remove the attribute," so
717
+ // `avatar.alt = ''` collapses to `alt === null` — indistinguishable from
718
+ // omitting alt entirely, and DOES warn under this condition.
719
+ // - `!decorative` is the operative fix for the confirmed false positive: a
720
+ // `decorative` avatar is the component's official signal that the image is
721
+ // non-informative (Req 9.2 applies `aria-hidden="true"`), so it must never warn
722
+ // regardless of alt.
723
+ if (type === 'human' && src && alt == null && !decorative && typeof console !== 'undefined') {
706
724
  console.warn(
707
- 'AvatarBaseElement: "alt" prop is required when "src" is provided for accessibility. ' +
708
- 'Please add an alt attribute describing the image.'
725
+ 'AvatarBaseElement: "alt" is required when "src" is provided. ' +
726
+ 'Add an alt description of the image; or, if this avatar is purely decorative ' +
727
+ '(non-informative), set the "decorative" prop to hide it from screen readers.'
709
728
  );
710
729
  }
711
730
 
@@ -102,7 +102,7 @@ contracts:
102
102
  - No hover or press visual feedback
103
103
  - pointer-events: none (web)
104
104
  - No gesture handlers (native)
105
- wcag: null
105
+ wcag: "N/A" # legitimate-null: non-interactive negative guarantee, no positive WCAG SC (125-B 4.2)
106
106
  platforms: [web, ios, android]
107
107
  validation:
108
108
  - Badge cannot receive keyboard focus
@@ -77,7 +77,7 @@ contracts:
77
77
  - No hover or press visual feedback
78
78
  - pointer-events: none (web)
79
79
  - No gesture handlers (native)
80
- wcag: null
80
+ wcag: "N/A" # legitimate-null: non-interactive negative guarantee, no positive WCAG SC (125-B 4.2)
81
81
  platforms: [web, ios, android]
82
82
  validation:
83
83
  - Badge cannot receive keyboard focus
@@ -90,20 +90,12 @@ properties:
90
90
  When true, text is truncated with ellipsis instead of wrapping.
91
91
  Default (false) allows text wrapping for accessibility and i18n support.
92
92
 
93
- disabled:
94
- type: boolean
95
- required: false
96
- default: false
97
- description: |
98
- When true, button is non-interactive with disabled visual styling.
99
- Disabled buttons are not keyboard focusable and onPress is not called.
100
-
101
93
  onPress:
102
94
  type: "() => void"
103
95
  required: true
104
96
  description: |
105
97
  Callback function invoked when button is clicked, tapped, or activated
106
- via keyboard (Enter/Space). Not called when disabled.
98
+ via keyboard (Enter/Space).
107
99
 
108
100
  testID:
109
101
  type: string
@@ -153,7 +145,6 @@ tokens:
153
145
  blend:
154
146
  - blend.hoverDarker
155
147
  - blend.pressedDarker
156
- - blend.disabledDesaturate
157
148
  - blend.iconLighter
158
149
  component:
159
150
  - buttonCTA.minWidth.small
@@ -247,11 +238,10 @@ accessibility:
247
238
  - "1.4.11 Non-text Contrast - Focus ring meets 3:1 contrast"
248
239
  - "2.1.1 Keyboard - Fully keyboard accessible (Tab, Enter, Space)"
249
240
  - "2.4.7 Focus Visible - Clear focus indicator with :focus-visible"
250
- - "4.1.2 Name, Role, Value - Proper ARIA attributes and disabled state"
241
+ - "4.1.2 Name, Role, Value - Proper ARIA attributes"
251
242
  - "2.5.5 Target Size - Touch targets meet 44px minimum"
252
243
  aria_attributes:
253
244
  - role: button (implicit from semantic element)
254
- - aria-disabled: true when disabled
255
245
  - aria-label: label prop value
256
246
  keyboard_navigation:
257
247
  - Tab: Move focus to/from button
@@ -41,10 +41,11 @@ This component guarantees 7 behavioral contracts across all platforms:
41
41
  | `pressable` | Responds to press/click events | web, ios, android | 2.1.1 |
42
42
  | `hover_state` | Visual feedback on hover | web | 1.4.13 |
43
43
  | `pressed_state` | Visual feedback when pressed | web, ios, android | 2.4.7 |
44
- | `disabled_state` | Prevents interaction when disabled | web, ios, android | 4.1.2 |
45
44
  | `loading_state` | Shows loading indicator | web, ios, android | 4.1.3 |
46
45
  | `focus_ring` | WCAG 2.4.7 focus visible indicator | web, ios, android | 2.4.7 |
47
46
 
47
+ **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).
48
+
48
49
  ---
49
50
 
50
51
  ## Usage
@@ -62,7 +63,6 @@ This component guarantees 7 behavioral contracts across all platforms:
62
63
  variant="primary"
63
64
  icon="arrow-right"
64
65
  no-wrap="false"
65
- disabled="false"
66
66
  test-id="submit-button"
67
67
  ></button-cta>
68
68
  ```
@@ -122,7 +122,6 @@ ButtonCTA(
122
122
  | `variant` | `'primary' \| 'secondary' \| 'tertiary'` | No | `'primary'` | Button visual variant |
123
123
  | `icon` | `IconName` | No | - | Optional leading icon |
124
124
  | `noWrap` | `boolean` | No | `false` | Prevent text wrapping |
125
- | `disabled` | `boolean` | No | `false` | Disable button interaction |
126
125
  | `onPress` | `() => void` | ✅ Yes | - | Press/click handler |
127
126
  | `testID` | `string` | No | - | Test identifier |
128
127
 
@@ -177,7 +176,6 @@ ButtonCTA(
177
176
  ### Blend
178
177
  - `blend.hoverDarker` - Hover state (8% darker)
179
178
  - `blend.pressedDarker` - Pressed state (12% darker)
180
- - `blend.disabledDesaturate` - Disabled state (12% less saturated)
181
179
  - `blend.iconLighter` - Icon optical balance (8% lighter)
182
180
 
183
181
  ### Accessibility
@@ -205,7 +203,7 @@ ButtonCTA(
205
203
  | 2.1.1 Keyboard | ✅ | Fully keyboard accessible (Tab, Enter, Space) |
206
204
  | 2.4.7 Focus Visible | ✅ | Clear focus indicator with :focus-visible |
207
205
  | 2.5.5 Target Size | ✅ | Touch targets meet 44px minimum |
208
- | 4.1.2 Name, Role, Value | ✅ | Proper ARIA attributes and disabled state |
206
+ | 4.1.2 Name, Role, Value | ✅ | Proper ARIA attributes |
209
207
 
210
208
  ### Keyboard Navigation
211
209
 
@@ -219,7 +217,6 @@ ButtonCTA(
219
217
 
220
218
  - Semantic `<button>` element provides implicit role
221
219
  - `aria-label` set from label prop
222
- - `aria-disabled="true"` when disabled
223
220
  - State changes announced to assistive technology
224
221
 
225
222
  ---