@hjmds/design-contracts 1.7.0 → 1.9.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 (250) hide show
  1. package/THIRD_PARTY_NOTICES.md +58 -0
  2. package/dist/affix.d.ts +8 -0
  3. package/dist/affix.d.ts.map +1 -0
  4. package/dist/affix.js +7 -0
  5. package/dist/affix.js.map +1 -0
  6. package/dist/agreement.d.ts +3 -3
  7. package/dist/agreement.js +1 -1
  8. package/dist/agreement.js.map +1 -1
  9. package/dist/asset.d.ts +1 -1
  10. package/dist/asset.js +1 -1
  11. package/dist/asset.js.map +1 -1
  12. package/dist/base-recipes.d.ts +12 -12
  13. package/dist/base-recipes.d.ts.map +1 -1
  14. package/dist/base-recipes.js +15 -14
  15. package/dist/base-recipes.js.map +1 -1
  16. package/dist/behaviors.d.ts +1 -1
  17. package/dist/catalog.d.ts +445 -651
  18. package/dist/catalog.d.ts.map +1 -1
  19. package/dist/catalog.js +149 -101
  20. package/dist/catalog.js.map +1 -1
  21. package/dist/color-picker.d.ts +11 -0
  22. package/dist/color-picker.d.ts.map +1 -0
  23. package/dist/color-picker.js +20 -0
  24. package/dist/color-picker.js.map +1 -0
  25. package/dist/command-palette.d.ts +3 -3
  26. package/dist/component-contracts.d.ts +4 -4
  27. package/dist/component-contracts.d.ts.map +1 -1
  28. package/dist/component-contracts.js +5 -4
  29. package/dist/component-contracts.js.map +1 -1
  30. package/dist/component-definitions.d.ts +1 -9
  31. package/dist/component-definitions.d.ts.map +1 -1
  32. package/dist/component-definitions.js +1 -9
  33. package/dist/component-definitions.js.map +1 -1
  34. package/dist/component-recipes.d.ts +76 -83
  35. package/dist/component-recipes.d.ts.map +1 -1
  36. package/dist/component-recipes.js +29 -49
  37. package/dist/component-recipes.js.map +1 -1
  38. package/dist/component-references.d.ts +10 -25
  39. package/dist/component-references.d.ts.map +1 -1
  40. package/dist/component-references.js +5 -8
  41. package/dist/component-references.js.map +1 -1
  42. package/dist/data-table.d.ts +3 -3
  43. package/dist/data-table.js +1 -1
  44. package/dist/data-table.js.map +1 -1
  45. package/dist/date-picker.d.ts +2 -2
  46. package/dist/icon-button-recipe.d.ts +2 -2
  47. package/dist/icon-button-recipe.js +1 -1
  48. package/dist/icon-button-recipe.js.map +1 -1
  49. package/dist/image.d.ts +2 -2
  50. package/dist/image.js +2 -2
  51. package/dist/image.js.map +1 -1
  52. package/dist/index.d.ts +1 -1
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.js +1 -1
  55. package/dist/index.js.map +1 -1
  56. package/dist/interaction-adapters.d.ts +53 -0
  57. package/dist/interaction-adapters.d.ts.map +1 -0
  58. package/dist/interaction-adapters.js +45 -0
  59. package/dist/interaction-adapters.js.map +1 -0
  60. package/dist/internal/thinking-orb/braid.d.ts +3 -0
  61. package/dist/internal/thinking-orb/braid.d.ts.map +1 -0
  62. package/dist/internal/thinking-orb/braid.js +47 -0
  63. package/dist/internal/thinking-orb/braid.js.map +1 -0
  64. package/dist/internal/thinking-orb/core.d.ts +60 -0
  65. package/dist/internal/thinking-orb/core.d.ts.map +1 -0
  66. package/dist/internal/thinking-orb/core.js +86 -0
  67. package/dist/internal/thinking-orb/core.js.map +1 -0
  68. package/dist/internal/thinking-orb/lattice.d.ts +5 -0
  69. package/dist/internal/thinking-orb/lattice.d.ts.map +1 -0
  70. package/dist/internal/thinking-orb/lattice.js +187 -0
  71. package/dist/internal/thinking-orb/lattice.js.map +1 -0
  72. package/dist/internal/thinking-orb/morph.d.ts +3 -0
  73. package/dist/internal/thinking-orb/morph.d.ts.map +1 -0
  74. package/dist/internal/thinking-orb/morph.js +127 -0
  75. package/dist/internal/thinking-orb/morph.js.map +1 -0
  76. package/dist/internal/thinking-orb/orbits.d.ts +3 -0
  77. package/dist/internal/thinking-orb/orbits.d.ts.map +1 -0
  78. package/dist/internal/thinking-orb/orbits.js +68 -0
  79. package/dist/internal/thinking-orb/orbits.js.map +1 -0
  80. package/dist/internal/thinking-orb/presets.d.ts +21 -0
  81. package/dist/internal/thinking-orb/presets.d.ts.map +1 -0
  82. package/dist/internal/thinking-orb/presets.js +77 -0
  83. package/dist/internal/thinking-orb/presets.js.map +1 -0
  84. package/dist/internal/thinking-orb/profiles.d.ts +8 -0
  85. package/dist/internal/thinking-orb/profiles.d.ts.map +1 -0
  86. package/dist/internal/thinking-orb/profiles.js +158 -0
  87. package/dist/internal/thinking-orb/profiles.js.map +1 -0
  88. package/dist/internal/thinking-orb/registry.d.ts +9 -0
  89. package/dist/internal/thinking-orb/registry.d.ts.map +1 -0
  90. package/dist/internal/thinking-orb/registry.js +27 -0
  91. package/dist/internal/thinking-orb/registry.js.map +1 -0
  92. package/dist/internal/thinking-orb/ribbon.d.ts +3 -0
  93. package/dist/internal/thinking-orb/ribbon.d.ts.map +1 -0
  94. package/dist/internal/thinking-orb/ribbon.js +87 -0
  95. package/dist/internal/thinking-orb/ribbon.js.map +1 -0
  96. package/dist/internal/thinking-orb/types.d.ts +15 -0
  97. package/dist/internal/thinking-orb/types.d.ts.map +1 -0
  98. package/dist/internal/thinking-orb/types.js +4 -0
  99. package/dist/internal/thinking-orb/types.js.map +1 -0
  100. package/dist/internal/thinking-orb/web.d.ts +3 -0
  101. package/dist/internal/thinking-orb/web.d.ts.map +1 -0
  102. package/dist/internal/thinking-orb/web.js +92 -0
  103. package/dist/internal/thinking-orb/web.js.map +1 -0
  104. package/dist/masonry.d.ts +17 -0
  105. package/dist/masonry.d.ts.map +1 -0
  106. package/dist/masonry.js +18 -0
  107. package/dist/masonry.js.map +1 -0
  108. package/dist/menubar.d.ts +3 -3
  109. package/dist/menubar.js +1 -1
  110. package/dist/menubar.js.map +1 -1
  111. package/dist/number-field.d.ts +2 -2
  112. package/dist/otp-field.d.ts +1 -1
  113. package/dist/password-field.d.ts +2 -2
  114. package/dist/popover.d.ts +7 -8
  115. package/dist/popover.d.ts.map +1 -1
  116. package/dist/popover.js +2 -0
  117. package/dist/popover.js.map +1 -1
  118. package/dist/qr-code-recipe.d.ts +7 -0
  119. package/dist/qr-code-recipe.d.ts.map +1 -0
  120. package/dist/qr-code-recipe.js +2 -0
  121. package/dist/qr-code-recipe.js.map +1 -0
  122. package/dist/qr-code.d.ts +16 -0
  123. package/dist/qr-code.d.ts.map +1 -0
  124. package/dist/qr-code.js +37 -0
  125. package/dist/qr-code.js.map +1 -0
  126. package/dist/semantic-colors.d.ts +1 -1
  127. package/dist/semantic-colors.js +1 -1
  128. package/dist/semantic-colors.js.map +1 -1
  129. package/dist/showcase.d.ts +5 -1
  130. package/dist/showcase.d.ts.map +1 -1
  131. package/dist/showcase.js +53 -12
  132. package/dist/showcase.js.map +1 -1
  133. package/dist/sidebar.d.ts +3 -3
  134. package/dist/sidebar.js +1 -1
  135. package/dist/sidebar.js.map +1 -1
  136. package/dist/tag.d.ts +4 -4
  137. package/dist/tag.js +3 -3
  138. package/dist/tag.js.map +1 -1
  139. package/dist/tags-input.d.ts +3 -3
  140. package/dist/tags-input.js +1 -1
  141. package/dist/tags-input.js.map +1 -1
  142. package/dist/text-formats.d.ts +2 -2
  143. package/dist/text-formats.js +2 -2
  144. package/dist/text-formats.js.map +1 -1
  145. package/dist/thinking-orb-recipe.d.ts +15 -0
  146. package/dist/thinking-orb-recipe.d.ts.map +1 -0
  147. package/dist/thinking-orb-recipe.js +9 -0
  148. package/dist/thinking-orb-recipe.js.map +1 -0
  149. package/dist/thinking-orb.d.ts +25 -0
  150. package/dist/thinking-orb.d.ts.map +1 -0
  151. package/dist/thinking-orb.js +40 -0
  152. package/dist/thinking-orb.js.map +1 -0
  153. package/dist/toast-liquid.d.ts +116 -0
  154. package/dist/toast-liquid.d.ts.map +1 -0
  155. package/dist/toast-liquid.js +103 -0
  156. package/dist/toast-liquid.js.map +1 -0
  157. package/dist/toast.d.ts +7 -1
  158. package/dist/toast.d.ts.map +1 -1
  159. package/dist/toast.js +5 -0
  160. package/dist/toast.js.map +1 -1
  161. package/dist/toggle-group.d.ts +2 -2
  162. package/dist/toggle-group.js +3 -3
  163. package/dist/toggle-group.js.map +1 -1
  164. package/dist/transfer-list.d.ts +3 -3
  165. package/dist/transfer-list.js +1 -1
  166. package/dist/transfer-list.js.map +1 -1
  167. package/dist/tree.d.ts +2 -2
  168. package/dist/version.d.ts +1 -1
  169. package/dist/version.js +1 -1
  170. package/dist/version.js.map +1 -1
  171. package/dist/virtual-list.d.ts +13 -0
  172. package/dist/virtual-list.d.ts.map +1 -0
  173. package/dist/virtual-list.js +13 -0
  174. package/dist/virtual-list.js.map +1 -0
  175. package/dist/watermark.d.ts +15 -0
  176. package/dist/watermark.d.ts.map +1 -0
  177. package/dist/watermark.js +11 -0
  178. package/dist/watermark.js.map +1 -0
  179. package/docs/affix.md +27 -63
  180. package/docs/anchor.md +5 -4
  181. package/docs/ant-design-coverage.md +8 -12
  182. package/docs/architecture.md +6 -2
  183. package/docs/authoring-brief.md +3 -2
  184. package/docs/bottom-navigation.md +7 -6
  185. package/docs/breadcrumb.md +5 -2
  186. package/docs/calendar.md +6 -3
  187. package/docs/carousel.md +2 -2
  188. package/docs/cascader.md +11 -0
  189. package/docs/catalog-cleanup.md +7 -0
  190. package/docs/catalog-decision-status.md +8 -3
  191. package/docs/catalog-freeze.json +9 -7
  192. package/docs/collapsible.md +12 -0
  193. package/docs/color-picker.md +30 -72
  194. package/docs/command-palette.md +5 -3
  195. package/docs/confirm-popover.md +5 -0
  196. package/docs/consumer-policy.md +29 -12
  197. package/docs/context-menu.md +12 -2
  198. package/docs/data-table.md +11 -6
  199. package/docs/date-picker.md +6 -4
  200. package/docs/date-range.md +4 -2
  201. package/docs/description-list.md +8 -9
  202. package/docs/dialog.md +39 -0
  203. package/docs/expansion-roadmap.md +11 -7
  204. package/docs/file-picker.md +5 -3
  205. package/docs/form.md +16 -4
  206. package/docs/generated/component-maturity.md +91 -99
  207. package/docs/generated/renderer-evidence.json +2043 -1059
  208. package/docs/generated/renderer-evidence.md +156 -145
  209. package/docs/generated/showcase-manifest.json +476 -625
  210. package/docs/layout.md +8 -9
  211. package/docs/library-gap-analysis.md +1 -1
  212. package/docs/masonry.md +11 -0
  213. package/docs/mentions.md +14 -7
  214. package/docs/optional-adapters.md +126 -0
  215. package/docs/otp-field.md +2 -1
  216. package/docs/pagination.md +4 -1
  217. package/docs/password-field.md +2 -1
  218. package/docs/popover.md +9 -6
  219. package/docs/promotion-candidates.md +2 -1
  220. package/docs/qr-code.md +11 -65
  221. package/docs/rating.md +5 -0
  222. package/docs/react-native-completion.md +1 -1
  223. package/docs/result.md +8 -5
  224. package/docs/screen-chrome.md +2 -1
  225. package/docs/sheet.md +8 -2
  226. package/docs/showcase.md +10 -2
  227. package/docs/side-panel.md +12 -6
  228. package/docs/sidebar.md +3 -0
  229. package/docs/splitter.md +6 -6
  230. package/docs/stable-core.md +92 -0
  231. package/docs/stable-promotion.md +47 -43
  232. package/docs/tags-input.md +5 -2
  233. package/docs/theme-palette.md +27 -0
  234. package/docs/thinking-orb.md +124 -0
  235. package/docs/time-picker.md +5 -0
  236. package/docs/timeline.md +2 -3
  237. package/docs/toast.md +6 -0
  238. package/docs/tooltip.md +2 -1
  239. package/docs/tour.md +14 -8
  240. package/docs/transfer-list.md +24 -21
  241. package/docs/tree-select.md +7 -1
  242. package/docs/tree.md +12 -7
  243. package/docs/upload-item.md +7 -3
  244. package/docs/virtual-list.md +10 -67
  245. package/docs/watermark.md +26 -58
  246. package/package.json +54 -3
  247. package/docs/app-provider.md +0 -50
  248. package/docs/border-beam.md +0 -66
  249. package/docs/chart.md +0 -33
  250. package/docs/utility.md +0 -53
@@ -1,23 +1,27 @@
1
1
  {
2
- "$comment": "카탈로그 확장 동결. 이 목록에 없는 이름을 catalog.ts에 추가하면 test/catalog-freeze.test.ts가 실패한다.",
2
+ "$comment": "카탈로그 확장 동결. 이 목록에 없는 이름을 catalog.ts에 추가하면 test/catalog-freeze.test.ts가 실패한다. 2026-09-29 사용자 요청으로 도입 제외 후보 네 개 삭제; 기존 dataviz/provider API는 유지.",
3
3
  "frozenAt": "2026-09-26",
4
- "reason": "2026-09-26 설계 점검: 111개 중 stable 4개였고, 세 제품 이상이 쓰는 약 30개가 beta에 묶여 있었다. 1.5.0에서 13개를 실제 증거로 승격했지만 keyboard·platform-parity 증거가 필요한 핵심(Checkbox, Switch, Dialog, Sheet, SegmentedControl, RadioGroup, Link, PasswordField 등)은 아직 beta다. 폭을 넓히기 전에 이것들을 먼저 올린다.",
4
+ "reason": "2026-09-26 설계 점검: 111개 중 stable 4개였고, 세 제품 이상이 쓰는 약 30개가 beta에 묶여 있었다. 1.5.0에서 13개를 실제 증거로 승격했지만 입력·접근성 동작 증거가 필요한 핵심(Checkbox, Switch, Dialog, Sheet, SegmentedControl, RadioGroup, Link, PasswordField 등)은 아직 beta다. 폭을 넓히기 전에 이것들을 먼저 올린다.",
5
5
  "unfreezeWhen": "위 핵심 beta가 stable이 되면 동결을 푼다. 그 전에 추가가 필요하면 이 파일의 exceptions에 이름·날짜·근거(ADR 또는 이슈)를 적는다.",
6
- "exceptions": [],
6
+ "exceptions": [
7
+ {
8
+ "name": "ThinkingOrb",
9
+ "date": "2026-09-29",
10
+ "rationale": "사용자가 Thinking Orbs의 HJM 흡수를 명시적으로 요청함. 기존 Spinner를 교체하지 않는 AI 상태 표시이며 선택형 경로로 격리. docs/thinking-orb.md"
11
+ }
12
+ ],
7
13
  "names": [
8
14
  "Accordion",
9
15
  "Affix",
10
16
  "Agreement",
11
17
  "AlertDialog",
12
18
  "Anchor",
13
- "AppProvider",
14
19
  "AspectRatio",
15
20
  "Asset",
16
21
  "AuthProviderButton",
17
22
  "AuthScreenLayout",
18
23
  "Avatar",
19
24
  "Badge",
20
- "BorderBeam",
21
25
  "BottomCTA",
22
26
  "BottomInfo",
23
27
  "BottomNavigation",
@@ -27,7 +31,6 @@
27
31
  "Card",
28
32
  "Carousel",
29
33
  "Cascader",
30
- "Chart",
31
34
  "Checkbox",
32
35
  "CheckboxGroup",
33
36
  "Chip",
@@ -112,7 +115,6 @@
112
115
  "Tree",
113
116
  "TreeSelect",
114
117
  "UploadItem",
115
- "Utility",
116
118
  "VirtualList",
117
119
  "VisuallyHidden",
118
120
  "Watermark"
@@ -17,3 +17,15 @@ chrome이 따라오고 그걸 다시 CSS로 지우게 된다. 그래서 별도
17
17
  가리키지 않으면 사용자는 방금 나타난 것을 찾아야 한다.
18
18
 
19
19
  **Web·Native 공통.** 네이티브도 `button` role에 expanded 상태를 그대로 쓴다.
20
+
21
+ ## Renderer evidence
22
+
23
+ - Web keyboard proof tabs to the native button, toggles with Enter, then closes with Space;
24
+ it checks `aria-expanded`, the controlled region relationship, and that closed content leaves
25
+ the tree. The narrow long-copy case places a long unbroken label in the trigger to verify that
26
+ disclosure chrome does not widen its container.
27
+ - Native renderer tests inspect the `button` role and expanded state, invoke the rendered
28
+ `Pressable` action, and verify that opening and closing adds and removes the content. This is
29
+ host-renderer evidence; it does not claim physical keyboard, VoiceOver, or TalkBack verification.
30
+ - Native and Web long-copy fixtures put the long label in the trigger itself. The child body stays
31
+ short so the scenario tests the constrained interactive label rather than arbitrary content.
@@ -1,72 +1,30 @@
1
- # ColorPicker — 지금은 만들지 않는다
2
-
3
- ## 문제로 제기된 것
4
-
5
- 임의의 색을 고른다(hex/RGB/HSB 입력, 팔레트, 최근 사용 색). Ant Design `ColorPicker`와
6
- `direct` crosswalk를 따른다. catalog에는 이미 `{ name: "ColorPicker", category: "input",
7
- platform: "web", status: "planned" }` 자리가 예약돼 있다.
8
-
9
- ## 판정: 실사용처 근거 없이 만들지 않는다
10
-
11
- 먼저 가정하지 말라는 지시대로 두 제품에 색을 고르는 화면이 있는지 확인했다.
12
-
13
- - **Yajalal RN**(`modules/app-rn/src`): `grep -rli "colorpicker\|color picker\|color-picker"`가
14
- 0건이다. 코드베이스 전체에 색을 고르는 화면이 없다.
15
- - **BurnTok Web**: 이 머신에 저장소가 없어 코드로 직접 확인할 수 없었다. 로드맵
16
- (`docs/expansion-roadmap.md`)의 제품 적용 순서·완료 슬라이스 기록 어디에도 색 선택
17
- 화면이 등장하지 않는다 — 언급되는 색 관련 작업은 전부 "제품이 이미 고정한 semantic
18
- color를 렌더러가 어떻게 적용하는가"(Toast tone mark, Statistic trend mark, 구단 색
19
- adapter)이고, "사용자가 임의의 색을 고르는" 문제는 한 번도 나오지 않는다.
20
-
21
- 측정된 vertical slice가 없다는 뜻이다. `docs/expansion-roadmap.md`의 maturity gate는
22
- "실제 제품 vertical slice 없이 승격하지 않는다"고 정하고 있고, 이 원칙은 계약을 쓸지
23
- 말지에도 그대로 적용된다 — `docs/notification.md`·`docs/dropdown.md`·
24
- `docs/virtual-list.md`가 이미 "측정된 요구가 없으면 만들지 않는다"로 판정한 것과 같은
25
- 근거다.
26
-
27
- ## 이 판정을 더 무겁게 만드는 이유
28
-
29
- ColorPicker는 만들면 가벼운 계약이 아니다. 로드맵의 상태 축 표 어디에도 없는 완전히
30
- 새로운 세 갈래 문제를 한 번에 열어야 한다.
31
-
32
- 1. **색 공간 표현.** hex/RGB/HSB/alpha 중 무엇을 공개 API로 삼을지, 변환 규칙을 어디
33
- 소유할지부터 새로 정해야 한다 — 이 저장소의 어떤 기존 컴포넌트도 색 값 자체를
34
- 입력 데이터로 다루지 않는다(`semanticColors`/`ColorReference`는 전부 제품이 아니라
35
- 시스템이 미리 고정한 토큰이다).
36
- 2. **대비·접근성.** 사용자가 고른 색이 텍스트/배경 대비 4.5:1을 만족하는지, 선택 UI
37
- 자체의 포커스 인디케이터가 색상 스와치 배경과 충분한 대비를 유지하는지를 계약이
38
- 보장해야 한다 — Statistic의 trend, UploadItem의 상태처럼 이 저장소는 "색으로만
39
- 말하지 않는다"를 지켜왔는데, ColorPicker는 그 규칙과 정반대로 색 자체가 선택 결과인
40
- 컴포넌트라 이 원칙을 어떻게 지킬지부터 새로 설계해야 한다(스와치에 값 텍스트를
41
- 병기하는 것 정도는 쉽지만, 색맹 사용자를 위한 팔레트 순서·명도 대비 규칙은 가볍지
42
- 않다).
43
- 3. **입력 방식.** 팔레트 탭, 텍스트 hex 입력, 슬라이더(hue/saturation/brightness) 최소
44
- 셋을 하나의 컴포넌트가 조율해야 하고, 셋 사이의 값 동기화(hex 입력 중 오타 상태를
45
- 팔레트에 어떻게 반영하는지)는 NumberField/Slider보다 훨씬 큰 상태 기계다.
46
-
47
- 수요 없이 이 세 갈래를 먼저 짜면 다음 사람이 "실제로 무엇을 위해 이렇게 무거운가"를
48
- 또 물어야 하는 계약이 된다. 반대로 실사용처가 나오면, 그 화면이 실제로 필요한 것은
49
- 이 셋 중 일부뿐일 가능성이 높다(예: 구단 색 하나를 브랜드 팔레트에서만 고르는 화면이면
50
- 색 공간 변환도 hex 입력도 필요 없다) — 지금 전체를 설계하면 그 실제 요구보다 큰 계약이
51
- 된다.
52
-
53
- ## 만들지 않은 것
54
-
55
- `src/color-picker.ts`, `test/color-picker.test.ts`는 없다. catalog의
56
- `{ name: "ColorPicker", category: "input", platform: "web", status: "planned" }` row와
57
- `antDesignReferenceComponents`의 `ColorPicker → ColorPicker` crosswalk(`relationship:
58
- "direct"`)는 건드리지 않는다 — 이름 자리를 지우는 것이 아니라, 지금 채울 계약이 없다는
59
- 것이다.
60
-
61
- ## 뒤집힐 조건
62
-
63
- 다음 중 하나가 실제로 측정되면 이 판정을 다시 연다.
64
-
65
- 1. BurnTok 또는 Yajalal에 사용자가 임의의 색을 고르는 실제 화면 요구가 나온다(예: 커스텀
66
- 테마, 사용자 지정 하이라이트 색).
67
- 2. 요구가 팔레트에서 미리 정의된 색 중 하나만 고르는 좁은 범위로 확인되면, ColorPicker
68
- 전체가 아니라 `Chip` 또는 `RadioGroup`의 시각 변형으로 더 가볍게 흡수될 수 있는지부터
69
- 먼저 검토한다 — hex 입력·색 공간 변환이 필요 없는 한 별도 컴포넌트를 열 이유가
70
- 약해진다.
71
- 3. 요구가 hex/RGB 자유 입력까지 포함하면, 이 문서의 세 갈래(색 공간/대비/입력 방식)를
72
- 실제 화면의 좁은 요구에 맞춰 하나씩만 계약하고 나머지는 열지 않는다.
1
+ # ColorPicker — Web sRGB 색상 입력
2
+
3
+ 2026-09-30: 사용자가 남은 세 후보의 개발을 명시적으로 요청하여 기존 수요 대기 결정을 대체했다.
4
+ React Native와 Flutter 구현은 포함하지 않는다. 계약은 HEX sRGB를 저장값으로 선택했다.
5
+ HSV/RGB 입력기를 동시에 만드는 대신 브라우저 색상 선택기와 HEX 입력을 연결해 값 표현을 하나로 유지한다.
6
+
7
+ `@hjmds/react/color-picker`의 `ColorPicker`는 controlled `value`와 `onValueChange`를 받는다.
8
+ `label`과 `labels.color`, `labels.hex`, `labels.opacity`, `labels.invalid`는 제품에서 번역하여 전달한다.
9
+ `alpha`가 false면 `#rrggbb`, true면 `#rrggbbaa`를 내보낸다. 3·4자리 축약 HEX도 입력할 수 있다. alpha가 켜진 상태에서 3·6자리 HEX를 입력하면 색만 바꾸고 현재 투명도를 유지한다(네이티브 색상 입력과 같다).
10
+ alpha를 끈 상태에서 불투명하지 않은 값을 전달하면 투명도를 조용히 버리지 않고 거부한다.
11
+
12
+ - HEX는 Enter/blur에 확정한다. 잘못된 입력은 오류를 보여주고 외부 값은 유지한다.
13
+ Escape는 마지막 controlled 값으로 복구하며 Enter가 상위 폼을 제출하지 않게 한다.
14
+ - 브라우저 색상 선택기는 RGB를 선택하고 기존 alpha를 보존한다. 정확한 선택기 모양은 OS 소유다.
15
+ - 불투명도 range는 0–100%이며 키보드 화살표/Home/End를 지원한다. 8비트 alpha로 반올림한다.
16
+ - `presets`는 HEX 배열이며 색상값을 읽을 수 있는 버튼이다. 중복값은 정규화 후 제거한다.
17
+ - `disabled`는 fieldset을 통해 모든 입력과 팔레트를 비활성화한다.
18
+ - 제어부는 HJM canvas·border·focus 토큰을 사용한다. 견본의 사용자 색상은 콘텐츠 데이터다.
19
+ 선택색을 제품의 본문/배경으로 사용할 때의 대비는 제품이 확인한다.
20
+
21
+ ```tsx
22
+ <ColorPicker label={t('color.title')} labels={{
23
+ color: t('color.choose'), hex: t('color.hex'), opacity: t('color.opacity'), invalid: t('color.invalid'),
24
+ }} value={color} onValueChange={setColor} alpha presets={['#b94627ff', '#338844ff']} />
25
+ ```
26
+
27
+ 계약: [color-picker.ts](../src/color-picker.ts). UI: [renderer](../../react/src/color-picker.tsx).
28
+ 사용 예제: [Web additions](../../../showcase/web/src/patterns/WebAdditions.stories.tsx).
29
+ 검증: [계약 회귀](../test/web-additions.test.ts), [브라우저 동작](../../react/test/web-additions.browser.test.tsx),
30
+ [환경 행렬](../../react/test/scenario-matrix.browser.test.tsx). 브라우저/OS 색상 팝업 자체의 내부 UI는 HJM 검증 대상이 아니다.
@@ -113,12 +113,14 @@ heading을 가지므로) — CommandPalette는 다르다: `role="dialog"` 표면
113
113
 
114
114
  ## 검증 화면
115
115
 
116
- 아직 없음. `planned → beta` 승격은 실제 제품 vertical slice 이후 리드가 진행한다.
116
+ 이 조사 당시 제품 채택은 미확인이었다. 2026-09-29부터 제품 채택은 관측으로 분리하며,
117
+ 현재 성숙도는 catalog와 [승격 기준](stable-promotion.md)을 따른다.
117
118
 
118
119
  ## Web renderer (2026-09-18)
119
120
 
120
- `@hjmds/react/command-palette`의 `CommandPalette`가 이 계약을 실행한다. catalog는
121
- Web `beta`, Native `unsupported`다.
121
+ `@hjmds/react/command-palette`의 `CommandPalette`가 이 계약을 실행한다. Web은 키보드 검색·
122
+ 실행·dismiss와 320px의 긴 한글 명령 설명 proof를 통과해 2026-09-29 stable로 승격한다.
123
+ Native는 `unsupported`다. 전역 단축키는 제품 소유다.
122
124
 
123
125
  - **모달 takeover다.** Dialog·Sheet·SidePanel과 같은 모달 스택·스크롤 락·배경 격리를
124
126
  공유한다(`packages/react/src/modal.tsx`). 별도 `modal` 축은 없다.
@@ -1,5 +1,10 @@
1
1
  # ConfirmPopover — Popover 위의 확인 조합
2
2
 
3
+ > 2026-09-30 정리: 구현된 조합을 미구현으로 집계하지 않도록 독립 Planned 행과 catalog ID를 제거했다.
4
+ > 기존 조합 예제와 공개 helper는 유지하며, 참조표는 실제 구성 컴포넌트로 연결한다.
5
+ > 아래 날짜별 설계 기록의 행 유지 지시는 당시 판단이며 이 결정으로 대체한다.
6
+
7
+
3
8
  ## 문제로 제기된 것
4
9
 
5
10
  Ant Design `Popconfirm`은 "삭제하시겠습니까?" 같은 확인을 Modal보다 가벼운
@@ -1,7 +1,7 @@
1
1
  # HJM 소비 앱 정책
2
2
 
3
3
  상태: **Normative**
4
- 정책 버전: **1.3.0**
4
+ 정책 버전: **1.4.0**
5
5
  적용 대상: 신규 HJM Web·React Native 앱과 기존 앱의 새 화면
6
6
 
7
7
  이 정책 원문은 다음 package patch release부터
@@ -26,25 +26,38 @@
26
26
 
27
27
  ## 2. 성숙도별 채택
28
28
 
29
- | Catalog 상태 | 신규 앱 기본 정책 | 추가 조건 |
29
+ | 사용할 surface 상태 | 신규 앱 기본 정책 | 추가 조건 |
30
30
  | --- | --- | --- |
31
31
  | `stable` | 사용 가능 | 제품 흐름·copy 검증 |
32
- | `beta` | 기본 비활성 | ADR, 앱 테스트, 실제 기기/브라우저 evidence |
32
+ | `beta` | 필요에 따라 선택 가능 | 사용 표면·미완료 항목 확인, 채택 기록과 관련 회귀 검사 |
33
33
  | `planned` | 사용 금지 | HJM package에서 구현·승격한 뒤 채택 |
34
34
  | `deprecated` | 신규 사용 금지 | 공지된 migration 기한 안에 제거 |
35
35
 
36
- 신규 앱 scaffold는 Stable Core만 기본 surface로 채택해야 합니다(MUST). Beta를 사용하려면
37
- 앱의 `docs/decisions/` 아래 ADR에 채택 이유, 대체안, 소유자, 검증 환경과 재검토 날짜를
38
- 기록해야 합니다(MUST).
36
+ `surfaceStatus`가 사용 플랫폼의 채택 기준입니다. contract `status`가 stable이어도
37
+ 다른 renderer까지 안정화됐다는 뜻은 아닙니다. 예를 들어 TopBar는 Web stable / Native beta입니다.
38
+
39
+ 신규 앱 scaffold의 기본 선택은 Stable Core입니다(SHOULD). Beta 사용은 정책 예외가 아닙니다.
40
+ 앱의 기존 contract 또는 TASKS 한 곳에 컴포넌트·표면, 사용하는 이유, 담당자와 관련 회귀
41
+ 검사를 기록해야 합니다(MUST). 여러 Beta를 한 기록으로 묶을 수 있고, 별도 ADR·대안 비교·
42
+ 재검토 날짜는 요구하지 않습니다. 실제 기기·브라우저 QA는 바뀐 사용자 흐름과 플랫폼 위험에
43
+ 맞춰 앱 릴리스에서 수행합니다. Beta라는 이유만으로 제품 전체 QA를 반복하지 않습니다.
44
+
45
+ 2026-09-29 점검에서 HJM 자체 검증을 통과한 표시 컴포넌트도 제품 수·배포 이력 때문에
46
+ 승격이 막혔습니다. 1.4.0은 컴포넌트 호환성 약속과 제품 릴리스 검증을 분리합니다.
47
+ [승격 기준](stable-promotion.md)을 따르며 기존 versioned app profile의 검사 요건은 그
48
+ profile을 명시적으로 갱신하기 전까지 유지합니다.
39
49
 
40
50
  ### 2.1 Stable Core와 필수 foundation bridge
41
51
 
42
- 1.5.0부터 Stable Core는 17개 surface입니다. 1.4까지의 `Surface`, `Button`, `Field`, `TextArea`에
52
+ 1.5.0에서 Stable Core는 17개 컴포넌트였습니다. 1.4까지의 `Surface`, `Button`, `Field`, `TextArea`에
43
53
  더해, 첫 제품 화면에 필요한 foundation인 `Text`, `Icon`, `Stack`, `Container`,
44
54
  `DesignSystemProvider`와 세 제품 이상이 쓰는 `IconButton`, `Badge`, `Card`, `Tag`, `Notice`,
45
55
  `Progress`, `Spinner`, `Skeleton`이 stable로 승격됐습니다. 승격 근거는 이름뿐이던 시나리오 증거를
46
56
  실제 계산 스타일 검사로 바꾼 뒤 두 renderer에서 요구 시나리오가 모두 통과한 것입니다
47
- ([Stable Core](stable-core.md)). 그 밖의 컴포넌트는 여전히 `beta`이며, beta를 `stable`로
57
+ ([Stable Core](stable-core.md)). 이번 개정에서는 `Divider`, `Section`, `ListRow`, `Statistic`,
58
+ `DescriptionList`, `EmptyState`, `Result`, `Heading`, `Top`, `BottomCTA`를 양쪽에서 승격하고,
59
+ `TopBar`는 계약·Web만 승격했습니다. 계약/Web stable은 28개, Native stable은 27개입니다.
60
+ 배포된 패키지의 catalog가 소비 앱의 기준입니다. 남은 beta를 `stable`로
48
61
  간주하거나 `betaAdoptions: []`로 기록하는 것은 허용하지 않습니다(MUST NOT).
49
62
 
50
63
  HJM-APP-STANDARD가 versioned `requiredFoundations` 목록을 선언한 경우 다음 bridge를
@@ -55,8 +68,8 @@ HJM-APP-STANDARD가 versioned `requiredFoundations` 목록을 선언한 경우
55
68
  - 각 앱은 실제 adapter와 사용 환경에 대한 evidence를 소유합니다(MUST). `draft` /
56
69
  `incubating` contract에서는 evidence가 `planned`일 수 있지만, `active` 또는 release gate는
57
70
  timestamp가 있는 `verified` evidence 없이는 통과하면 안 됩니다(MUST NOT).
58
- - 중앙 필수 목록 밖의 `beta`는 계속 제품별 optional adoption입니다. 제품 ADR, 대안,
59
- 제거·승격 조건과 verified app evidence가 모두 필요합니다(MUST).
71
+ - 중앙 필수 목록 밖의 `beta`는 제품별 optional adoption이며 §2의 간단한 채택 기록을
72
+ 사용합니다. 예전 profile이 ADR·verified evidence를 요구하면 해당 profile 갱신 때 이관합니다.
60
73
  - foundation이 catalog에서 `stable`로 승격되면 기존 표준의 의미를 조용히 바꾸지 않고
61
74
  다음 versioned app profile에서 bridge 목록을 줄입니다(MUST).
62
75
 
@@ -194,7 +207,7 @@ on-color 대비, 색에 의존하지 않는 상태 표지, reduce-motion을 제
194
207
  4. 원시 색·간격·radius의 화면 레이어 유입 방지 검사
195
208
  5. typecheck, unit/component test, production build
196
209
  6. light/dark, 100–200% 글자 크기, LTR/RTL, reduce motion 중 앱이 지원한다고 선언한 환경
197
- 7. optional Beta component를 쓴다면 ADR과 해당 component·환경의 versioned consumer evidence
210
+ 7. optional Beta component를 쓴다면 §2의 채택 기록과 변경한 사용자 흐름의 회귀 검사
198
211
 
199
212
  앱이 지원하지 않는 플랫폼이나 환경은 가짜 통과 script로 만들지 않고 app contract에서
200
213
  `not-applicable`과 이유를 선언해야 합니다(MUST).
@@ -218,8 +231,12 @@ package로 승격하지 않습니다.
218
231
 
219
232
  - exact fixed-train dependency와 Provider가 CI에서 검증되는가?
220
233
  - Stable은 기본 사용하고(1.5.0 이전 profile이면 필수 foundation은 중앙 목록+앱 evidence로),
221
- optional Beta는 제품 ADR+evidence로 구분되어 있는가?
234
+ optional Beta는 채택 기록과 관련 회귀 검사로 추적되는가?
222
235
  - screen 코드가 semantic token·component를 우회하지 않는가?
223
236
  - 같은 상태가 Web/RN에서 같은 의미와 자연스러운 플랫폼 행동을 갖는가?
224
237
  - 로고를 제거해도 제품의 핵심 콘텐츠와 정보 구조가 다른 제품과 구분되는가?
225
238
  - 제품 고유 표현을 제거해도 정보 위계·피드백·접근성에서는 HJM의 결이 남는가?
239
+
240
+ ## Optional presentation adapters
241
+
242
+ The [optional adapter contract](optional-adapters.md) lists platform-specific entries and exact peer requirements. These extensions do not promote the canonical component or establish consumer/device compatibility. Native adapters require separately verified development clients; base imports retain their existing dependency contract.
@@ -12,12 +12,22 @@
12
12
 
13
13
  **키보드로 열 때의 앵커는 초점 요소의 상자다.** 좌표가 없다고 (0,0)에 띄우면 메뉴가 방금
14
14
  초점이 있던 항목과 무관해 보인다. `resolveContextMenuAnchor`가 이 판단을 갖고 있고,
15
- 좌표도 초점 상자도 없으면 렌더링 대신 던진다.
15
+ 좌표도 초점 상자도 없으면 렌더링 대신 던진다. 실제 메뉴가 포털에 마운트된 뒤 메뉴에 초점을
16
+ 옮기고 `aria-activedescendant`로 활성 항목을 알린다. 열기 직전의 초점 요소는 보관한다.
16
17
 
17
- **닫으면 열었던 자리로 초점을 돌린다.** 좌표에서 열렸다고 초점까지 떠 있으면 다음 Tab이
18
+ **Escape 또는 항목 실행으로 닫으면 열었던 요소에 초점을 돌린다.** 바깥을 눌러 닫을 때는
19
+ 사용자가 누른 바깥 요소의 초점을 유지한다. 좌표에서 열렸다고 초점까지 떠 있으면 다음 Tab이
18
20
  문서 처음으로 간다.
19
21
 
22
+ **화면 가장자리와 긴 항목도 다룬다.** 메뉴가 포털에 표시된 다음 실제 크기로 위치를 화면 안에
23
+ 맞춘다. 긴 한글·공백 없는 항목명은 줄바꿈해 전체 명령을 읽을 수 있게 한다. 임의의 고정 크기
24
+ 추정은 글꼴과 줄바꿈에 따라 메뉴가 다시 화면 밖으로 나갈 수 있어 사용하지 않는다.
25
+
20
26
  **브라우저 기본 메뉴를 제품이 소유한 표면에서만 대체한다.** 텍스트 선택·링크·이미지 위의
21
27
  기본 메뉴를 빼앗으면 복사·새 탭 열기를 사용자에게서 뺏는 것이다.
22
28
 
23
29
  **Web 전용.** 네이티브의 길게 누르기 메뉴는 OS가 제공하는 표면이라 같은 계약이 아니다.
30
+
31
+ React 구현은 `packages/react/test/context-menu.interactions.browser.test.tsx`에서 Chromium으로
32
+ 포인터 좌표 열기, 키보드 열기, 활성 항목 안내, Escape·실행 후 초점 복귀, 작은 화면에서의 긴
33
+ 항목 배치를 검증한다.
@@ -28,7 +28,7 @@
28
28
  `id`와 `disabled?`만 가진 훨씬 좁은 타입이다 — Collection 기본 계약 중 실제로
29
29
  행에 맞는 부분만 가져오고, 맞지 않는 부분(label/textValue/typeahead)은 그대로
30
30
  두었다.
31
- - `Pagination`(다른 저작자가 이번 배치에서 계약)과 `LoadMore`(이미 beta)는 DataTable이
31
+ - `Pagination`(다른 저작자가 이번 배치에서 계약)과 `LoadMore`(이미 stable)는 DataTable이
32
32
  **소유하지 않고 합성**한다. `asyncState`가 `loadingMore`일 때 그 아래 어느
33
33
  컴포넌트를 두는지는 제품 선택이다 — Native 긴 목록에 LoadMore를 쓰듯 Web 표에도
34
34
  같은 패턴이 통한다.
@@ -75,13 +75,14 @@
75
75
  `native` 필드는 breadcrumb·form 같은 다른 Web 전용 계약과 같은 빈 배열
76
76
  자리표시자다.
77
77
 
78
- **검증 화면.** 아직 실제 제품 vertical slice가 없다 — catalog는 `planned`으로
79
- 남고, `beta` 승격은 로드맵 gate(실제 화면 검증)를 통과한 뒤 리드가 진행한다.
78
+ **검증 화면.** 제품 vertical slice는 필요하지 않다. Renderer 계약을 정렬·tri-state selection·
79
+ async 상태와 narrow long-copy 회귀로 확인하고, 소비 제품의 데이터 정렬·페이지네이션은 계속
80
+ 제품 소유로 둔다. 채택 여부는 [stable 승격 기준](stable-promotion.md)과 별도다.
80
81
 
81
82
  ## Web renderer (2026-09-18)
82
83
 
83
- `@hjmds/react/data-table`의 `DataTable`이 이 계약을 실행한다. catalog는 Web `beta`,
84
- Native `unsupported`다.
84
+ `@hjmds/react/data-table`의 `DataTable`이 이 계약을 실행한다. Web renderer는 전용 keyboard·
85
+ selection·320px long-copy proof를 통과해 2026-09-29 stable로 승격한다. Native는 `unsupported`다.
85
86
 
86
87
  - **기존 `Table`과 겹치지 않는다.** `@hjmds/react`의 `Table`(advanced-display)은 열·행을
87
88
  그려 주는 표시용이고 선택·tri-state·async 상태·정렬 상태 순환이 없다. DataTable은 그
@@ -96,6 +97,10 @@ Native `unsupported`다.
96
97
  - **셀 하나에 focusable 컨트롤은 최대 하나다.** roving tabindex grid 탐색을 도입하지 않고
97
98
  기본 tab 순서를 쓴다.
98
99
  - **페이지네이션은 표 아래에 조합한다.** `footer` slot은 제품이 채우고 표가 소유하지 않는다.
100
+ - 키보드는 표 전체를 하나의 ARIA grid로 만들지 않고 native Tab 순서를 쓴다. 실제 브라우저 입력으로
101
+ select-all·정렬·행 선택 버튼이 Tab 순서에 있고 Space/Enter로 동작하는지 확인한다. 방향키 셀 탐색은
102
+ 계약에 없다 — 제품 수요가 확인되지 않은 full grid interaction은 별도 계약으로 다룬다.
99
103
  - 로컬 검증: `test/data-table.browser.test.tsx` 5개(header 안 정렬 버튼과 aria-sort 3단계,
100
104
  tri-state 선택과 disabled 제외, 단일 선택 radio 의미, 셀당 컨트롤 하나와 기본 tab 순서,
101
- async 상태 발표와 footer 조합)와 `Patterns/CommandPalette`의 표 화면.
105
+ async 상태 발표와 footer 조합), `test/data-table.keyboard.browser.test.tsx` 1개(실제 Tab·Enter·Space
106
+ 입력), 그리고 `Patterns/CommandPalette`의 표 화면.
@@ -78,7 +78,9 @@ Select의 축과 동일하되 `content`(idle/loading/loadingMore/error)는 없
78
78
 
79
79
  ## 검증 화면
80
80
 
81
- first-party Web dialog·Native Sheet renderer, 환경 matrix, 기본 실행 증거는 연결되어 surface는
82
- `beta`다. 다만 `docs/calendar.md`가 밝힌 대로 Yajalal에는 이 문제의 살아있는 vertical
83
- slice가 아직 없다. 값 하나를 고르는 새 폼 필드가 실제 제품에 생기고 보조기기 증거까지
84
- 쌓이기 전에는 `stable`로 승격하지 않는다.
81
+ first-party Web dialog·Native Sheet renderer의 필수 시나리오가 연결되어 양쪽 surface가
82
+ `stable`이다. Web Chromium 검사는 트리거 열기, 실제 날짜 셀 키보드 이동·선택, 닫힘과 focus
83
+ 복귀를 확인한다. Native renderer 검사는 날짜 host action에 따른 선택·닫힘을 확인한다.
84
+ 두 renderer 모두 접근성 이름, 긴 label, 환경 matrix 검사를 통과한다. 실제 iOS/Android 기기와
85
+ 제품 폼의 스크린 리더 검증은 소비 앱 릴리스 QA에 속하며, Yajalal vertical slice나 제품 채택
86
+ 수는 이 공개 API 성숙도 판단의 조건으로 두지 않는다([Stable 승격 기준](stable-promotion.md)).
@@ -27,5 +27,7 @@
27
27
  확정된 구간과 hover 미리보기를 함께 처리한다 — renderer가 두 벌의 칠하기 규칙을 만들지
28
28
  않게 한다.
29
29
 
30
- **Native.** renderer는 아직 없다(catalog `planned`). 구간 선택은 제스처·스크롤과 함께
31
- 검증해야 해서 Web 먼저 낸다.
30
+ **Native.** 같은 Calendar 격자에서 날짜 host action이 범위의 시작·중간·끝으로 반영되고,
31
+ 접근성 이름에도 해당 상태가 포함된다. 시각 범위는 Web에서 band와 hover preview로, Native에서
32
+ 날짜 아래 점과 이름으로 표현한다. 두 renderer의 환경·접근성 matrix와 날짜 선택 회귀가 통과해
33
+ 각 지원 surface의 계약은 stable이다. 기기·VoiceOver·TalkBack 검증은 소비 제품 QA에서 한다.
@@ -62,14 +62,13 @@ Web/Native 모두 `resolveDescriptionListColumnCount`가 돌려준 열 수로 CS
62
62
  flex wrap 레이아웃을 만듭니다. 실제 측정된 컨테이너 폭과 시스템 폰트 배율은 renderer가
63
63
  공급하고, 이 계약은 그 두 입력을 받아 열 수만 결정합니다.
64
64
 
65
- ## 현재 검증과 남은 증거
65
+ ## 현재 검증과 제품 채택
66
66
 
67
- first-party Web/RN renderer가 공통 descriptor와
68
- `resolveDescriptionListColumnCount`를 직접 소비하므로 contract와 두 surface를 `beta`로
69
- 승격했다. Web은 `<dl>/<dt>/<dd>` 구조와 측정 폭 기반 열 전환을, Native는 독립된 라벨-값
70
- 접근성 노드와 window/text-scale 기반 flex 열 전환을 기본 실행 test로 검증한다.
67
+ 공통 descriptor와 `resolveDescriptionListColumnCount`를 소비하는 Web/RN renderer는
68
+ [2026-09-29 승격](stable-core.md)의 대상이다. Web의 `<dl>/<dt>/<dd>`와 폭 기반 열 전환,
69
+ Native의 라벨-값 접근성 노드와 text-scale 기반 열 전환은 renderer 회귀로 검증한다.
70
+ 필수 환경 시나리오가 모두 연결됐으며 계약·양쪽 surface를 stable로 올린다.
71
71
 
72
- 이전 판정이 후보로 든 야잘알 화면은 여전히 승격의 제품 근거로 세지 않는다. 해당 화면은
73
- 단일 열 `List`/`ListRow` 조합이며 DescriptionList의 1/2열 문제와 다르기 때문이다. 실제 소비
74
- 앱에서 이 renderer를 쓰는 1/2열 화면과 200% 글자 크기 실기기 증거는 stable 승격 전까지
75
- 명시적인 debt로 남는다.
72
+ 과거 후보로 든 야잘알 화면은 단일 열 List/ListRow 조합이므로 DescriptionList 채택으로
73
+ 세지 않는다. 제품의 1/2열 화면과 실제 기기 QA는 해당 앱 릴리스에서 확인하며,
74
+ [승격 기준](stable-promotion.md)에 따라 제품 채택 부재를 성숙도 관문으로 쓰지 않는다.
package/docs/dialog.md ADDED
@@ -0,0 +1,39 @@
1
+ # Dialog
2
+
3
+ Dialog provides a modal boundary for a task that needs the user's attention. This
4
+ contract was recorded during the 2026-09-29 promotion audit because Web and Native
5
+ already had different title APIs and dismissal sources, while there was no single
6
+ component guide describing their shared behavior.
7
+
8
+ ## Accessible name and modal boundary
9
+
10
+ - Web renders `role="dialog"`, `aria-modal="true"`, a title reference, and an
11
+ optional description reference. When opened, focus enters the dialog, stays in
12
+ the active modal, and returns to the trigger (or the explicit return-focus
13
+ target) after dismissal.
14
+ - Native renders a `role="dialog"` modal boundary with
15
+ `accessibilityViewIsModal`. A string `title` names the dialog. If `title` is a
16
+ React element, pass `accessibilityTitle`; native renderers cannot derive a
17
+ reliable accessible name from arbitrary element children.
18
+ - The close action uses the required localized `closeLabel` on both renderers.
19
+ Dialog titles, descriptions, and body copy wrap instead of being clipped to one
20
+ line. Web constrains the dialog to the viewport and allows its content to
21
+ scroll.
22
+
23
+ ## Dismissal behavior
24
+
25
+ Dismiss requests are ignored while `busy` is true or `dismissible` is false.
26
+ Web reports `escape`, `outside`, or `close-action`; Native reports `back`,
27
+ `outside`, or `close-action`. A Native primary or secondary action runs its
28
+ callback and requests `close-action`; controlled Dialog owners decide when the
29
+ dialog actually closes. While an async operation is pending, keep `open` true and
30
+ set `busy` so repeated action and dismissal attempts are disabled.
31
+
32
+ ## Renderer proof
33
+
34
+ The React browser tests cover focus entry, Tab containment, Escape dismissal,
35
+ focus restoration, busy dismissal guards, and long title/description/body copy in
36
+ a narrow viewport. React Native renderer tests cover modal role/name/state,
37
+ action labels and close requests, back/outside dismissal, busy guards, and long
38
+ title/description copy. These tests exercise renderer contracts; device and
39
+ screen-reader behavior remains a separate consumer validation concern.
@@ -194,8 +194,9 @@ semantic tone은 독립이고 arrow/minus와 visible copy를 함께 사용합니
194
194
 
195
195
  Yajalal의 선수·FA 기록 vertical slice에서 기존 StatGrid 호환 adapter와 새 Statistic renderer를
196
196
  함께 검증했습니다. 좁은 폭에서는 1열까지 reflow하고 큰 글자에서도 값을 줄 수로 자르지 않으며,
197
- 각 통계의 label/value/hint/trend를 독립된 접근성 이름으로 유지하므로 Statistic을 beta로
198
- 승격합니다.
197
+ 각 통계의 label/value/hint/trend를 독립된 접근성 이름으로 유지해 Statistic을 첫 beta
198
+ renderer로 도입했습니다. 현재 maturity는 [Stable Core](stable-core.md)의 surface 목록을
199
+ 따릅니다.
199
200
 
200
201
  Select/Combobox는 Menu의 collection contract를 공유하지만 role과 dismiss behavior는 별도입니다.
201
202
  Web은 listbox popup, Native는 Sheet를 사용합니다. Native 긴 목록에는 페이지 번호보다
@@ -209,15 +210,18 @@ manual fallback을 유지합니다.
209
210
  BurnTok 홈 피드 vertical slice에서 Web IntersectionObserver와 항상 보이는 manual fallback,
210
211
  Native FlatList onEndReached와 footer fallback을 같은 controller에 연결했습니다. 두 renderer 모두
211
212
  탭과 다음 offset으로 requestKey를 만들고 실제 페이지 요청 Promise가 끝날 때까지 중복 호출을
212
- 차단하며 loading/error/complete 접근성 상태를 검증했으므로 LoadMore를 beta로 승격합니다.
213
+ 차단하며 loading/error/complete 접근성 상태를 검증해 LoadMore를 beta renderer로 도입했습니다.
214
+ 현재 Web·Native stable evidence와 소비 앱 경계는 [stable promotion log](stable-core.md)에
215
+ 기록합니다.
213
216
 
214
217
  BottomNavigation은 route source-of-truth를 복제하지 않고 `selectedKey`를 input으로만 받습니다.
215
218
  Web renderer는 실제 link와 `aria-current`, Native renderer는 navigator의 preventable tabPress와
216
219
  long press를 보존합니다. 숫자 badge는 visual subtree를 접근성에서 숨기고 item root에 합성된
217
220
  이름을 한 번만 전달합니다. BurnTok 중앙 생성 action은 `center-gap`에 놓이는 sibling primary
218
221
  action이며 destination collection에는 들어가지 않습니다. BurnTok Web/RN과 Yajalal RN 실제
219
- navigation에 적용해 route state·disabled/reselect·긴 글자·safe area·RTL 계약을 검증했으므로
220
- BottomNavigation을 beta로 승격합니다.
222
+ navigation에 적용해 route state·disabled/reselect·긴 글자·safe area·RTL 계약을 검증해
223
+ BottomNavigation을 beta renderer로 도입했습니다. 현재 Web·Native stable evidence는
224
+ [stable promotion log](stable-core.md)에 기록합니다.
221
225
 
222
226
  선행 타입은 Select의 nullable stable key와 Combobox의 `selectedKey`/`inputValue` 분리를
223
227
  고정합니다. local filtering과 server-driven external filtering도 구분해 선택 상태와 비동기
@@ -233,7 +237,7 @@ state도 selection state와 별도 controlled/uncontrolled 축으로 유지합
233
237
 
234
238
  surface별 `planned → beta` gate는 public renderer export와 package CI가 실행하는 canonical
235
239
  `default` proof를 요구합니다. 실제 제품 vertical slice는 별도의 adoption evidence이며,
236
- 없다면 beta의 환경 debt와 함께 공개되고 stable 승격을 막습니다. 제품 채택을 검토하는
240
+ 없다면 채택 관측으로 남기며 stable 승격을 막지 않습니다(2026-09-29 [기준 개정](stable-promotion.md)). 제품 채택을 검토하는
237
241
  과정에서 **전제가 이미 바뀌어 있던 사례**도 나왔습니다.
238
242
 
239
243
  `Calendar`·`DatePicker`를 위임할 때 "야잘알 일정 찾기가 월 달력 격자를 자체 구현 중"이라는
@@ -244,7 +248,7 @@ surface별 `planned → beta` gate는 public renderer export와 package CI가
244
248
 
245
249
  이후 DatePicker와 Calendar에는 first-party Web·Native renderer와 canonical 환경 증거가 추가되어
246
250
  `beta`로 승격됐다. 다만 위 실측은 여전히 유효하다. **제품 adoption evidence는 없고**,
247
- Yajalal의 날짜 레일을 DatePicker 채택으로 세지 않는다. 따라서 `stable` 승격 근거는 없다.
251
+ Yajalal의 날짜 레일을 DatePicker 채택으로 세지 않는다. 성숙도는 제품 채택 여부 대신 현재 renderer 필수 증거로 판정한다.
248
252
 
249
253
  **교훈**: 위임할 때 준 실사용처 전제를 저작자가 **확인하게** 해야 한다. 리드의 기억은
250
254
  커밋 하나로 낡는다.
@@ -54,6 +54,8 @@ FilePicker는 파일 선택 의도만 소유하고, 진행·성공·실패 표
54
54
  - 거부 발표는 색에 의존하지 않는다 — `reason`과 한계값으로 제품이 만든 문장을
55
55
  live 영역/에러 카피로 보여준다.
56
56
 
57
- **검증 화면.** first-party Web input/dropzone과 Native picker-adapter renderer, 선택 판정
58
- 상호작용 테스트는 연결되어 surface는 `beta`다. 실제 제품 vertical slice와 플랫폼 picker
59
- 실기기 증거는 아직 없으므로 `stable` 승격 gate는 닫혀 있다.
57
+ **검증 범위.** Web의 native file input/dropzone과 Native의 이름 있는 제품 adapter action,
58
+ 양쪽의 같은 선택 판정 resolver를 renderer 회귀로 검증한다. Native adapter가 실제 OS
59
+ document/image picker를 여는지는 제품 통합 책임이며 이 패키지는 이를 구현하거나 기기에서
60
+ 검증했다고 주장하지 않는다. 소비 앱은 해당 adapter를 연결하고 제품별 릴리스 QA에서 OS
61
+ 동작을 확인한다.
package/docs/form.md CHANGED
@@ -98,10 +98,22 @@ dispose()가 submitting 중 호출되면 그 attempt는 interrupted로 한 번
98
98
  live-region 동등물(`AccessibilityInfo.announceForAccessibility` 또는 동등 API)로
99
99
  발표. `submitting` 중에는 제출 버튼이 `accessibilityState.busy`를 얻습니다.
100
100
 
101
+ Native 소비자는 제품 검증 결과로 `resolveFirstInvalidFieldFocusTarget`을 호출해
102
+ 선택한 입력의 ref를 `Form.firstInvalidFieldRef`에 전달합니다. ref가 있으면 제출 버튼은
103
+ `TextInput.focus()`와 `AccessibilityInfo.setAccessibilityFocus()`를 실행하고 `onSubmit`을
104
+ 호출하지 않습니다. ref가 없으면 기존 제출 경로를 따릅니다. Form은 필드 순서·오류 상태를
105
+ 등록하거나 추론하지 않습니다.
106
+
101
107
  ## 검증 화면
102
108
 
103
- Form은 이제 `beta`입니다. BurnTok Web의
104
- `apps/web/src/app/ideas/page.tsx`가 실제 2필드 생성 흐름에서 아래 계약을 소비합니다.
109
+ Form은 surface별 상태를 갖습니다. Web은 실제 Chromium 입력·Enter 제출, 비동기 중복 제출 방지,
110
+ 필드·form 오류 semantics와 긴 자식 콘텐츠·환경 matrix 검증을 통과해 stable입니다. Native는
111
+ 현재 값 입력·제출·busy 상태와, 제품이 선택한 첫 무효 필드로 Native 키보드 및 accessibility
112
+ focus를 전달하는 host-action 회귀까지 검증합니다. 테스트는 React Native host 동작을 확인하며,
113
+ 실기기 VoiceOver/TalkBack 경험은 별도 검증 근거로 기록해야 합니다.
114
+
115
+ BurnTok Web의 `apps/web/src/app/ideas/page.tsx`는 2필드 생성 흐름에서 제품 소유 검증과 첫 오류
116
+ 필드 focus를 연결한 소비 사례입니다.
105
117
 
106
118
  - `apps/web/src/lib/form-contract.ts`: `formRecipe` 간격, `createFormSubmitSession`,
107
119
  `resolveFirstInvalidFieldFocusTarget`을 제품 validation과 React lifecycle에 연결합니다.
@@ -111,8 +123,8 @@ Form은 이제 `beta`입니다. BurnTok Web의
111
123
  - `apps/web/src/components/ui/AppTextField.tsx`: input/textarea ref를 실제 control까지
112
124
  전달해 focus 계약이 설명에 그치지 않게 합니다.
113
125
 
114
- Web 한 제품의 vertical slice만으로 `stable`을 주장하지 않습니다. Native renderer의
115
- accessibility focus와 busy 발표 증거가 추가되기 전까지 `beta`를 유지합니다.
126
+ 제품 채택 수는 stable blocker가 아닙니다. 실기기 VoiceOver/TalkBack 확인은 제품 QA 범위이며,
127
+ HJM renderer의 Native action 회귀와 사용 가능한 자동 검증을 기준으로 surface 성숙도를 평가합니다.
116
128
 
117
129
  ## 공개하지 않기로 한 것
118
130