@hjmds/design-contracts 0.8.2

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 (353) hide show
  1. package/README.md +208 -0
  2. package/dist/alert-dialog.d.ts +102 -0
  3. package/dist/alert-dialog.d.ts.map +1 -0
  4. package/dist/alert-dialog.js +136 -0
  5. package/dist/alert-dialog.js.map +1 -0
  6. package/dist/base-recipes.d.ts +184 -0
  7. package/dist/base-recipes.d.ts.map +1 -0
  8. package/dist/base-recipes.js +129 -0
  9. package/dist/base-recipes.js.map +1 -0
  10. package/dist/behaviors.d.ts +1254 -0
  11. package/dist/behaviors.d.ts.map +1 -0
  12. package/dist/behaviors.js +972 -0
  13. package/dist/behaviors.js.map +1 -0
  14. package/dist/bottom-navigation-defaults.d.ts +13 -0
  15. package/dist/bottom-navigation-defaults.d.ts.map +1 -0
  16. package/dist/bottom-navigation-defaults.js +10 -0
  17. package/dist/bottom-navigation-defaults.js.map +1 -0
  18. package/dist/bottom-navigation.d.ts +114 -0
  19. package/dist/bottom-navigation.d.ts.map +1 -0
  20. package/dist/bottom-navigation.js +223 -0
  21. package/dist/bottom-navigation.js.map +1 -0
  22. package/dist/breadcrumb.d.ts +97 -0
  23. package/dist/breadcrumb.d.ts.map +1 -0
  24. package/dist/breadcrumb.js +99 -0
  25. package/dist/breadcrumb.js.map +1 -0
  26. package/dist/calendar.d.ts +285 -0
  27. package/dist/calendar.d.ts.map +1 -0
  28. package/dist/calendar.js +297 -0
  29. package/dist/calendar.js.map +1 -0
  30. package/dist/card.d.ts +39 -0
  31. package/dist/card.d.ts.map +1 -0
  32. package/dist/card.js +50 -0
  33. package/dist/card.js.map +1 -0
  34. package/dist/carousel.d.ts +180 -0
  35. package/dist/carousel.d.ts.map +1 -0
  36. package/dist/carousel.js +172 -0
  37. package/dist/carousel.js.map +1 -0
  38. package/dist/catalog.d.ts +7158 -0
  39. package/dist/catalog.d.ts.map +1 -0
  40. package/dist/catalog.js +220 -0
  41. package/dist/catalog.js.map +1 -0
  42. package/dist/collection.d.ts +123 -0
  43. package/dist/collection.d.ts.map +1 -0
  44. package/dist/collection.js +211 -0
  45. package/dist/collection.js.map +1 -0
  46. package/dist/color-references.d.ts +46 -0
  47. package/dist/color-references.d.ts.map +1 -0
  48. package/dist/color-references.js +39 -0
  49. package/dist/color-references.js.map +1 -0
  50. package/dist/colors.d.ts +56 -0
  51. package/dist/colors.d.ts.map +1 -0
  52. package/dist/colors.js +90 -0
  53. package/dist/colors.js.map +1 -0
  54. package/dist/command-palette.d.ts +240 -0
  55. package/dist/command-palette.d.ts.map +1 -0
  56. package/dist/command-palette.js +97 -0
  57. package/dist/command-palette.js.map +1 -0
  58. package/dist/component-contracts.d.ts +137 -0
  59. package/dist/component-contracts.d.ts.map +1 -0
  60. package/dist/component-contracts.js +53 -0
  61. package/dist/component-contracts.js.map +1 -0
  62. package/dist/component-definitions.d.ts +135 -0
  63. package/dist/component-definitions.d.ts.map +1 -0
  64. package/dist/component-definitions.js +142 -0
  65. package/dist/component-definitions.js.map +1 -0
  66. package/dist/component-recipes.d.ts +3583 -0
  67. package/dist/component-recipes.d.ts.map +1 -0
  68. package/dist/component-recipes.js +1503 -0
  69. package/dist/component-recipes.js.map +1 -0
  70. package/dist/component-references.d.ts +416 -0
  71. package/dist/component-references.d.ts.map +1 -0
  72. package/dist/component-references.js +140 -0
  73. package/dist/component-references.js.map +1 -0
  74. package/dist/content-state.d.ts +104 -0
  75. package/dist/content-state.d.ts.map +1 -0
  76. package/dist/content-state.js +116 -0
  77. package/dist/content-state.js.map +1 -0
  78. package/dist/counter-badge-recipe.d.ts +80 -0
  79. package/dist/counter-badge-recipe.d.ts.map +1 -0
  80. package/dist/counter-badge-recipe.js +44 -0
  81. package/dist/counter-badge-recipe.js.map +1 -0
  82. package/dist/counter-badge.d.ts +12 -0
  83. package/dist/counter-badge.d.ts.map +1 -0
  84. package/dist/counter-badge.js +21 -0
  85. package/dist/counter-badge.js.map +1 -0
  86. package/dist/data-table.d.ts +203 -0
  87. package/dist/data-table.d.ts.map +1 -0
  88. package/dist/data-table.js +182 -0
  89. package/dist/data-table.js.map +1 -0
  90. package/dist/date-picker.d.ts +268 -0
  91. package/dist/date-picker.d.ts.map +1 -0
  92. package/dist/date-picker.js +168 -0
  93. package/dist/date-picker.js.map +1 -0
  94. package/dist/description-list.d.ts +68 -0
  95. package/dist/description-list.d.ts.map +1 -0
  96. package/dist/description-list.js +85 -0
  97. package/dist/description-list.js.map +1 -0
  98. package/dist/design-system-provider.d.ts +80 -0
  99. package/dist/design-system-provider.d.ts.map +1 -0
  100. package/dist/design-system-provider.js +147 -0
  101. package/dist/design-system-provider.js.map +1 -0
  102. package/dist/evidence.d.ts +49 -0
  103. package/dist/evidence.d.ts.map +1 -0
  104. package/dist/evidence.js +133 -0
  105. package/dist/evidence.js.map +1 -0
  106. package/dist/file-picker.d.ts +183 -0
  107. package/dist/file-picker.d.ts.map +1 -0
  108. package/dist/file-picker.js +224 -0
  109. package/dist/file-picker.js.map +1 -0
  110. package/dist/floating-action-button.d.ts +143 -0
  111. package/dist/floating-action-button.d.ts.map +1 -0
  112. package/dist/floating-action-button.js +149 -0
  113. package/dist/floating-action-button.js.map +1 -0
  114. package/dist/form.d.ts +143 -0
  115. package/dist/form.d.ts.map +1 -0
  116. package/dist/form.js +206 -0
  117. package/dist/form.js.map +1 -0
  118. package/dist/foundations.d.ts +300 -0
  119. package/dist/foundations.d.ts.map +1 -0
  120. package/dist/foundations.js +238 -0
  121. package/dist/foundations.js.map +1 -0
  122. package/dist/grid.d.ts +75 -0
  123. package/dist/grid.d.ts.map +1 -0
  124. package/dist/grid.js +133 -0
  125. package/dist/grid.js.map +1 -0
  126. package/dist/icon-button-recipe.d.ts +99 -0
  127. package/dist/icon-button-recipe.d.ts.map +1 -0
  128. package/dist/icon-button-recipe.js +53 -0
  129. package/dist/icon-button-recipe.js.map +1 -0
  130. package/dist/icon.d.ts +41 -0
  131. package/dist/icon.d.ts.map +1 -0
  132. package/dist/icon.js +147 -0
  133. package/dist/icon.js.map +1 -0
  134. package/dist/image.d.ts +82 -0
  135. package/dist/image.d.ts.map +1 -0
  136. package/dist/image.js +100 -0
  137. package/dist/image.js.map +1 -0
  138. package/dist/index.d.ts +58 -0
  139. package/dist/index.d.ts.map +1 -0
  140. package/dist/index.js +66 -0
  141. package/dist/index.js.map +1 -0
  142. package/dist/layout.d.ts +118 -0
  143. package/dist/layout.d.ts.map +1 -0
  144. package/dist/layout.js +118 -0
  145. package/dist/layout.js.map +1 -0
  146. package/dist/link.d.ts +57 -0
  147. package/dist/link.d.ts.map +1 -0
  148. package/dist/link.js +142 -0
  149. package/dist/link.js.map +1 -0
  150. package/dist/load-more.d.ts +64 -0
  151. package/dist/load-more.d.ts.map +1 -0
  152. package/dist/load-more.js +124 -0
  153. package/dist/load-more.js.map +1 -0
  154. package/dist/mentions.d.ts +67 -0
  155. package/dist/mentions.d.ts.map +1 -0
  156. package/dist/mentions.js +107 -0
  157. package/dist/mentions.js.map +1 -0
  158. package/dist/number-field.d.ts +208 -0
  159. package/dist/number-field.d.ts.map +1 -0
  160. package/dist/number-field.js +247 -0
  161. package/dist/number-field.js.map +1 -0
  162. package/dist/otp-field.d.ts +152 -0
  163. package/dist/otp-field.d.ts.map +1 -0
  164. package/dist/otp-field.js +117 -0
  165. package/dist/otp-field.js.map +1 -0
  166. package/dist/pagination.d.ts +181 -0
  167. package/dist/pagination.d.ts.map +1 -0
  168. package/dist/pagination.js +226 -0
  169. package/dist/pagination.js.map +1 -0
  170. package/dist/password-field.d.ts +187 -0
  171. package/dist/password-field.d.ts.map +1 -0
  172. package/dist/password-field.js +120 -0
  173. package/dist/password-field.js.map +1 -0
  174. package/dist/popover.d.ts +152 -0
  175. package/dist/popover.d.ts.map +1 -0
  176. package/dist/popover.js +137 -0
  177. package/dist/popover.js.map +1 -0
  178. package/dist/progress-recipe.d.ts +43 -0
  179. package/dist/progress-recipe.d.ts.map +1 -0
  180. package/dist/progress-recipe.js +16 -0
  181. package/dist/progress-recipe.js.map +1 -0
  182. package/dist/recipes.d.ts +31 -0
  183. package/dist/recipes.d.ts.map +1 -0
  184. package/dist/recipes.js +46 -0
  185. package/dist/recipes.js.map +1 -0
  186. package/dist/responsive.d.ts +27 -0
  187. package/dist/responsive.d.ts.map +1 -0
  188. package/dist/responsive.js +66 -0
  189. package/dist/responsive.js.map +1 -0
  190. package/dist/result.d.ts +111 -0
  191. package/dist/result.d.ts.map +1 -0
  192. package/dist/result.js +97 -0
  193. package/dist/result.js.map +1 -0
  194. package/dist/selection-helpers.d.ts +16 -0
  195. package/dist/selection-helpers.d.ts.map +1 -0
  196. package/dist/selection-helpers.js +52 -0
  197. package/dist/selection-helpers.js.map +1 -0
  198. package/dist/semantic-colors.d.ts +275 -0
  199. package/dist/semantic-colors.d.ts.map +1 -0
  200. package/dist/semantic-colors.js +84 -0
  201. package/dist/semantic-colors.js.map +1 -0
  202. package/dist/sheet.d.ts +51 -0
  203. package/dist/sheet.d.ts.map +1 -0
  204. package/dist/sheet.js +69 -0
  205. package/dist/sheet.js.map +1 -0
  206. package/dist/showcase.d.ts +153 -0
  207. package/dist/showcase.d.ts.map +1 -0
  208. package/dist/showcase.js +210 -0
  209. package/dist/showcase.js.map +1 -0
  210. package/dist/side-panel.d.ts +199 -0
  211. package/dist/side-panel.d.ts.map +1 -0
  212. package/dist/side-panel.js +111 -0
  213. package/dist/side-panel.js.map +1 -0
  214. package/dist/slider.d.ts +138 -0
  215. package/dist/slider.d.ts.map +1 -0
  216. package/dist/slider.js +150 -0
  217. package/dist/slider.js.map +1 -0
  218. package/dist/splitter.d.ts +113 -0
  219. package/dist/splitter.d.ts.map +1 -0
  220. package/dist/splitter.js +99 -0
  221. package/dist/splitter.js.map +1 -0
  222. package/dist/statistic.d.ts +41 -0
  223. package/dist/statistic.d.ts.map +1 -0
  224. package/dist/statistic.js +76 -0
  225. package/dist/statistic.js.map +1 -0
  226. package/dist/steps.d.ts +196 -0
  227. package/dist/steps.d.ts.map +1 -0
  228. package/dist/steps.js +160 -0
  229. package/dist/steps.js.map +1 -0
  230. package/dist/tag.d.ts +128 -0
  231. package/dist/tag.d.ts.map +1 -0
  232. package/dist/tag.js +93 -0
  233. package/dist/tag.js.map +1 -0
  234. package/dist/timeline.d.ts +147 -0
  235. package/dist/timeline.d.ts.map +1 -0
  236. package/dist/timeline.js +127 -0
  237. package/dist/timeline.js.map +1 -0
  238. package/dist/toast.d.ts +164 -0
  239. package/dist/toast.d.ts.map +1 -0
  240. package/dist/toast.js +529 -0
  241. package/dist/toast.js.map +1 -0
  242. package/dist/tokens.d.ts +9 -0
  243. package/dist/tokens.d.ts.map +1 -0
  244. package/dist/tokens.js +9 -0
  245. package/dist/tokens.js.map +1 -0
  246. package/dist/tooltip.d.ts +44 -0
  247. package/dist/tooltip.d.ts.map +1 -0
  248. package/dist/tooltip.js +88 -0
  249. package/dist/tooltip.js.map +1 -0
  250. package/dist/tour.d.ts +218 -0
  251. package/dist/tour.d.ts.map +1 -0
  252. package/dist/tour.js +211 -0
  253. package/dist/tour.js.map +1 -0
  254. package/dist/transfer-list.d.ts +207 -0
  255. package/dist/transfer-list.d.ts.map +1 -0
  256. package/dist/transfer-list.js +193 -0
  257. package/dist/transfer-list.js.map +1 -0
  258. package/dist/tree-select.d.ts +78 -0
  259. package/dist/tree-select.d.ts.map +1 -0
  260. package/dist/tree-select.js +132 -0
  261. package/dist/tree-select.js.map +1 -0
  262. package/dist/tree.d.ts +206 -0
  263. package/dist/tree.d.ts.map +1 -0
  264. package/dist/tree.js +223 -0
  265. package/dist/tree.js.map +1 -0
  266. package/dist/upload-item.d.ts +173 -0
  267. package/dist/upload-item.d.ts.map +1 -0
  268. package/dist/upload-item.js +156 -0
  269. package/dist/upload-item.js.map +1 -0
  270. package/dist/version.d.ts +3 -0
  271. package/dist/version.d.ts.map +1 -0
  272. package/dist/version.js +3 -0
  273. package/dist/version.js.map +1 -0
  274. package/docs/affix.md +63 -0
  275. package/docs/anchor.md +59 -0
  276. package/docs/ant-design-coverage.md +119 -0
  277. package/docs/app-provider.md +50 -0
  278. package/docs/app-rn-adoption.md +227 -0
  279. package/docs/architecture.md +320 -0
  280. package/docs/authoring-brief.md +87 -0
  281. package/docs/border-beam.md +66 -0
  282. package/docs/bottom-navigation.md +127 -0
  283. package/docs/breadcrumb.md +82 -0
  284. package/docs/calendar.md +154 -0
  285. package/docs/carousel.md +130 -0
  286. package/docs/cascader.md +93 -0
  287. package/docs/catalog-decision-status.md +306 -0
  288. package/docs/color-picker.md +72 -0
  289. package/docs/command-palette.md +116 -0
  290. package/docs/confirm-popover.md +93 -0
  291. package/docs/consistency-audit.md +383 -0
  292. package/docs/consumer-release-gate.md +90 -0
  293. package/docs/content-state.md +156 -0
  294. package/docs/context-panel.md +88 -0
  295. package/docs/cross-platform-core-normalization.md +118 -0
  296. package/docs/data-table.md +79 -0
  297. package/docs/date-picker.md +84 -0
  298. package/docs/description-list.md +75 -0
  299. package/docs/design-system-provider.md +139 -0
  300. package/docs/dropdown.md +78 -0
  301. package/docs/expansion-roadmap.md +285 -0
  302. package/docs/file-picker.md +59 -0
  303. package/docs/floating-action-button.md +111 -0
  304. package/docs/form.md +134 -0
  305. package/docs/generated/component-maturity.md +103 -0
  306. package/docs/generated/renderer-evidence.json +5597 -0
  307. package/docs/generated/renderer-evidence.md +132 -0
  308. package/docs/generated/showcase-manifest.json +3732 -0
  309. package/docs/icon.md +22 -0
  310. package/docs/identity.md +121 -0
  311. package/docs/image.md +84 -0
  312. package/docs/implementation-0.5.md +85 -0
  313. package/docs/layout-primitives.md +107 -0
  314. package/docs/layout.md +83 -0
  315. package/docs/library-reference-decisions.md +123 -0
  316. package/docs/link.md +67 -0
  317. package/docs/load-more.md +31 -0
  318. package/docs/mentions.md +82 -0
  319. package/docs/migration-0.2.md +108 -0
  320. package/docs/migration-0.3.md +72 -0
  321. package/docs/migration-0.5.md +82 -0
  322. package/docs/migration-0.6.md +197 -0
  323. package/docs/notification.md +54 -0
  324. package/docs/number-field.md +82 -0
  325. package/docs/otp-field.md +103 -0
  326. package/docs/pagination.md +139 -0
  327. package/docs/password-field.md +98 -0
  328. package/docs/popover.md +124 -0
  329. package/docs/promotion-candidates.md +169 -0
  330. package/docs/qr-code.md +69 -0
  331. package/docs/rating.md +58 -0
  332. package/docs/responsive-grid.md +90 -0
  333. package/docs/result.md +96 -0
  334. package/docs/showcase.md +97 -0
  335. package/docs/side-panel.md +71 -0
  336. package/docs/slider.md +79 -0
  337. package/docs/splitter.md +55 -0
  338. package/docs/statistic.md +16 -0
  339. package/docs/steps.md +115 -0
  340. package/docs/tag.md +57 -0
  341. package/docs/time-picker.md +88 -0
  342. package/docs/timeline.md +146 -0
  343. package/docs/toast.md +127 -0
  344. package/docs/tooltip.md +58 -0
  345. package/docs/tour.md +136 -0
  346. package/docs/transfer-list.md +94 -0
  347. package/docs/tree-select.md +101 -0
  348. package/docs/tree.md +123 -0
  349. package/docs/upload-item.md +68 -0
  350. package/docs/utility.md +53 -0
  351. package/docs/virtual-list.md +70 -0
  352. package/docs/watermark.md +58 -0
  353. package/package.json +402 -0
@@ -0,0 +1,169 @@
1
+ # Beta 승격 후보 실측 — 2026-08-19
2
+
3
+ `componentCatalog`에서 `status: "planned"`이고 `recipe`가 이미 연결된 항목 전부를
4
+ 대상으로, Yajalal RN(`/Users/jimin/Desktop/yajalal/modules/app-rn`)과
5
+ BurnTok(`/Users/jimin/Desktop/BurnTok`) 양쪽에서 **같은 문제를 이미 손으로 풀고 있는
6
+ 화면**을 찾았다. import 흔적이 아니라(렌더러가 없으니 당연히 없다) 자체 구현을 찾는
7
+ 조사다. 두 저장소 모두 읽기 전용으로만 확인했고, `catalog.ts` 등 공유 파일과 두 제품
8
+ 저장소는 한 줄도 고치지 않았다. `docs/promotion-candidates.md` 이 파일 하나만 새로
9
+ 만든다.
10
+
11
+ **승격은 여기서 하지 않는다.** 아래는 리드가 `catalog.ts`를 고칠 때 참고할 실측
12
+ 자료다.
13
+
14
+ > 2026-08-27 후속: 이 문서는 제품 adoption 실측 기록으로 보존한다. 이후 first-party
15
+ > renderer와 canonical evidence만으로 beta가 된 항목이 있어 현재 catalog status와 표의
16
+ > “실제품 승격 판단”은 의도적으로 다를 수 있다. 실제 제품 증거 없음은 stable gate의 debt다.
17
+
18
+ ## 요약
19
+
20
+ | 판단 | 개수 | 컴포넌트 |
21
+ | --- | --- | --- |
22
+ | 승격 가능 | **1** | Tag |
23
+ | 계약 보완 필요 (실제 요구가 계약 밖에 있음) | 2 | Timeline, Form(사실상 아직 없음에 가까움) |
24
+ | 전제 재검토 필요 (문제는 있으나 계약의 핵심 축을 다른 컴포넌트가 이미 풀고 있음) | 1 | DescriptionList |
25
+ | 보류 — 화면은 있으나 계약의 **필수** 축이 전혀 구현돼 있지 않음 | 1 | Carousel |
26
+ | 아직 없음 | 21 | 나머지 전부 |
27
+
28
+ 승격 가능은 **하나뿐이다.** 나머지는 후하게 매기지 않았다 — 특히 Carousel은 다른
29
+ 조사자가 "승격 가능"으로 보고했으나 직접 코드를 읽고 계약 문서(`docs/carousel.md`)의
30
+ "공개(필수 계약)" 표를 대조한 결과 하향 조정했다(아래 상세 참고).
31
+
32
+ ## 전체 표
33
+
34
+ | 컴포넌트 | 실사용처(파일:줄) | 자체 구현이 하는 일 | 계약이 덮는가 | 승격 판단 |
35
+ | --- | --- | --- | --- | --- |
36
+ | Stack | 없음 | — | — | 아직 없음 |
37
+ | Layout | 없음(BurnTok 각 페이지가 헤더+본문을 화면마다 개별 조립, skip link 없음) | — | — | 아직 없음 |
38
+ | Splitter | 없음 | — | — | 아직 없음 |
39
+ | DescriptionList | `PlayerScreen.tsx:181-258`(프로필 라벨→값), `PlayerScreen.tsx:507-525`(`PlainRows`) | 라벨→값 사실 나열은 실재하지만 `AppList`+`AppListRow`(단일 열, 이미 beta)로 풀려 있다 | 계약의 핵심 축(`columns:1\|2` 그리드 + `resolveDescriptionListColumnCount` 폰트스케일 reflow)을 쓰는 화면이 없다 | **전제 재검토 필요** — 아래 상세 |
40
+ | PasswordField | 없음(Yajalal 로그인 자체 없음, BurnTok은 OAuth-only) | — | — | 아직 없음 |
41
+ | OtpField | 없음(두 제품 다 인증번호 단계 없음) | — | — | 아직 없음 |
42
+ | Slider | 없음 | — | — | 아직 없음 |
43
+ | NumberField | 없음(BurnTok `sharedIncrement`는 서버 카운터이지 바운드 입력 UI가 아님) | — | — | 아직 없음 |
44
+ | Form | BurnTok `CommentsSheet.tsx:54-77,156,262` | `draft`/`submitError` + `isPending` + 실패 시 로컬라이즈 오류 — submit-session의 절반과 닮았다 | 단일 필드 컴포저라 계약의 핵심(다중 필드 중 첫 invalid 포커스 라우팅)은 전혀 쓰이지 않는다 | **계약 보완 필요, 사실상 아직 없음** |
45
+ | DatePicker | `ScheduleExplorerScreen.tsx:87-102` | 월그리드→날짜 레일 교체가 **의도된 설계 결정**(코드 주석에 명시) — 압축 트리거+팝업/시트라는 계약의 모양 자체를 쓰지 않기로 함 | — | 아직 없음 |
46
+ | FilePicker | 없음(BurnTok `create/page.tsx`는 텍스트→AI 생성이지 파일 선택 아님) | — | — | 아직 없음 |
47
+ | UploadItem | 없음 | — | — | 아직 없음 |
48
+ | Breadcrumb | 없음(`docs/breadcrumb.md`가 예시로 든 "구단→선수단→선수"는 **Native** 화면인데 Breadcrumb는 `platform:"web"` 전용이라 애초에 근거가 될 수 없음) | — | — | 아직 없음 — **문서 정정 필요** |
49
+ | Pagination | 없음(BurnTok `hooks.ts:68,96`의 `FEED_PAGE_SIZE`는 LoadMore용 상수) | — | — | 아직 없음 — LoadMore가 이미 이 자리를 가짐 |
50
+ | Steps | `OnboardingScreen.tsx:182-189` | `AppProgress`(연속 막대) + 현재 단계 라벨 하나만 — 개별 단계 마커 배열이 아니다. `docs/steps.md`가 "유력 후보"로 든 것은 **아직 실현되지 않은 계획**이다 | — | 아직 없음 — **문서 정정 필요**(예정 vs 실재 혼동) |
51
+ | Timeline | `LiveScreen.tsx:797-821`(`PlayLogRowView`) | 플레이 로그를 `AppListRow`+`badge={<AppBadge label={outLabel}/>}`로 나열 — "일어난 일, 순서, 커서 없음"이라는 Timeline 경계와 정확히 일치 | 각 항목의 짧은 배지(`1사`/`2사`)를 `TimelineItemDescriptor`가 담을 슬롯이 없다 | **계약 보완 필요** — `badge`/`count` 축 추가 검토 |
52
+ | DataTable | `StatTable.tsx`, `StandingsTable.tsx` | 고정 열 통계 표시, 첫 열만 고정 — 정렬·선택·페이지네이션 전혀 없음 | 계약의 핵심(정렬 `aria-sort`, 행 선택)을 쓰는 화면이 없다 | 아직 없음 — List/ListRow-고정열 영역이지 그리드가 아니다 |
53
+ | Tree | 없음 | — | — | 아직 없음 |
54
+ | Calendar | `ScheduleExplorerScreen.tsx:31,84` | `buildCalendarCells`는 남아 있지만 7열 그리드로 렌더된 적이 없다(레일만 소비) — 로드맵의 기존 판정 그대로 | — | 아직 없음(재확인 완료) |
55
+ | Carousel | `HomeMatchHero.tsx`(`HomeGameCarousel`) | 순서(`orderCarouselGames`)·시작 카드(`resolveCarouselStartIndex`)·클램프(`snapToOffsets`)·자동재생 없음은 계약과 정확히 일치 | **`docs/carousel.md`가 "공개(필수 계약)"으로 못 박은 축 — previous/next/dot 컨트롤의 tab 순서, 비활성 슬라이드 `inert`, Native `accessibilityRole="adjustable"`+`increment`/`decrement` — 가 이 화면에 전혀 없다.** 스와이프 전용 `ScrollView`뿐, 키보드/스크린리더로 넘길 방법이 없다 | **보류** — 아래 상세 |
56
+ | Image | 없음(`docs/image.md`가 든 "선수 프로필 사진·FA 등급 차트"는 코드에 없다) | `TeamMark.tsx:96`의 RN `Image`는 있지만 고정 정사각 엠블럼 + 로드 실패 시 대체 라벨 없음 — Avatar 영역이지 Image 계약의 대상이 아니다 | — | 아직 없음 — **문서 정정 필요** |
57
+ | Tag | `FaCenterScreen.tsx:270,369` | `AppBadge label={item.grade} tone="neutral"` / `AppBadge label={`${player.grade}등급`} tone="neutral"` — 정적 라벨, 상호작용 전혀 없음 | `label`+`tone` 뿐인 계약이 정확히 이 요구를 덮는다. `docs/tag.md`가 미리 지목한 후보와 일치 | **승격 가능** |
58
+ | Result | `_layout.tsx:142-166`(`ErrorFallback`), `PlayerScreen.tsx:44,85-99` | 둘 다 `AppStateView`를 거쳐 `AppEmptyState`의 아이콘만 바꿔 재사용 — 후속 조사 결과 **흡수가 아니라 둘 다 vertical slice가 없어서 같은 화면에 동원된 것**(아래, `docs/result.md`에 반영 완료) | Result의 실제 문제(행동 뒤 flow terminus)는 두 제품에 없다. 화면이 실제로 쓰는 건 `content` 축(로드 상태)이지 Result도 EmptyState의 tone도 아니다 | 아직 없음 — **흡수 아님, 둘 다 검증할 화면 없음**(`docs/result.md` 참고) |
59
+ | SidePanel | 없음 | — | — | 아직 없음 |
60
+ | Popover | 없음(BurnTok `AppSelect.tsx`의 팝업은 Select 자체 계약) | — | — | 아직 없음 |
61
+ | CommandPalette | 없음 | — | — | 아직 없음 |
62
+
63
+ ## 승격 가능 — Tag
64
+
65
+ `docs/tag.md`가 이미 "야잘알의 `AppBadge`가 지금 이 역할(포지션·등급·시즌 라벨)을
66
+ 수행하고 있습니다"라고 적어 둔 예측이 그대로 들어맞는다. `FaCenterScreen.tsx:270`
67
+ (`<AppBadge label={item.grade} tone="neutral" />`, FA 등급 시트)과 `:369`
68
+ (`<AppBadge label={`${player.grade}등급`} tone="neutral" />`, FA 명단 행)가 실제 코드다.
69
+ 둘 다:
70
+
71
+ - 정적 메타데이터 하나(등급)만 표시하고 누를 수 없다 — `TagDescriptor`가 가정하는 정확히
72
+ 그 모양.
73
+ - `tone="neutral"` 하나만 쓴다 — `tagRecipe`의 다섯 톤(`neutral|info|success|attention|
74
+ brand`) 중 이미 지원 범위 안.
75
+ - `selected`/`closable`/포커스 등 Tag가 명시적으로 배제한 축을 요구하지 않는다.
76
+
77
+ 계약이 못 덮는 부분이 없다. **승격 가능.**
78
+
79
+ ## 보류 — Carousel
80
+
81
+ 다른 조사자는 `HomeGameCarousel`(`HomeMatchHero.tsx`)을 "승격 가능"으로 보고했다. 직접
82
+ `src/carousel.ts`와 `docs/carousel.md`를 읽고 재검증한 결과 하향 조정한다.
83
+
84
+ `docs/carousel.md` "공개한 축 / 배제한 축" 표는 "previous/next/dot 컨트롤 tab 순서,
85
+ 비활성 슬라이드 `inert`"를 **"공개(필수 계약)"**로 명시한다 — 선택 사항이 아니다.
86
+ Native 번역 절도 `accessibilityRole="adjustable"` + `accessibilityValue` +
87
+ `increment`/`decrement` action을 요구한다. 그런데 실제 `HomeGameCarousel`은:
88
+
89
+ - previous/next 버튼이 **없다**.
90
+ - dot indicator가 **없다**.
91
+ - 화면 밖 카드를 `inert` 처리하는 코드가 **없다**.
92
+ - `ScrollView`에 `accessibilityRole`/`accessibilityValue`/`accessibilityActions`가
93
+ **전혀 설정돼 있지 않다** — 순수 스와이프 제스처 하나뿐이다.
94
+
95
+ 즉 이 화면이 증명하는 것은 계약의 "무엇을 보여주는가"(순서, 현재 카드, 클램프, 자동재생
96
+ 끔, 카드 내용은 제품 소유) 절반뿐이고, 계약이 **필수**로 못 박은 다른 절반(키보드·
97
+ 스크린리더로 넘길 수 있는 경로)은 이 화면에 실증된 적이 없다. `docs/carousel.md` 자신도
98
+ "검증 화면: 아직 없음... 유력 후보: 야잘알 홈의 내 구단 경기 스트립"이라고 이미 적어
99
+ 뒀다 — 즉 이 문서를 쓴 사람도 이 화면을 후보로만 남기고 검증 완료로 적지 않았다.
100
+
101
+ `architecture.md`의 surface gate 구분(`planned → beta`는 first-party public renderer와
102
+ canonical default proof, 제품 채택·접근성 실기기 검증은 `beta → stable`)을 감안해도,
103
+ "필수 계약"이라고 못 박힌 축이 화면에
104
+ **하나도** 없는 상태를 "vertical slice가 있다"고 보기는 무리라고 판단했다. 승격하려면
105
+ 최소한 previous/next 컨트롤(시각적으로 숨기더라도 키보드/VoiceOver 경로) 하나는 먼저
106
+ 붙어야 근거가 된다. 지금은 **보류**를 권한다.
107
+
108
+ ## 계약 보완 필요 — Timeline
109
+
110
+ `LiveScreen.tsx:797-821`의 `PlayLogRowView`는 "일어난 일을 순서대로, 커서 없이" 보여주는
111
+ 정확히 Timeline의 문제를 푼다. 그런데 아웃 카운트(`1사`/`2사`, 3아웃이면 배지 생략)를
112
+ `badge={<AppBadge label={outLabel} />}`로 각 행에 붙인다. `TimelineItemDescriptor`
113
+ (`id/label/timestamp?/description?/tone`)에는 이 짧은 인라인 배지를 담을 자리가 없다.
114
+ **리드에게 제안**: `badge?: string` 같은 짧은 보조 표시 축을 계약에 추가할지 검토.
115
+ (참고: 렌더링은 dot-connector가 아니라 flat `AppListRow`라 시각 recipe 쪽도 실제 화면과
116
+ 다르다 — 이건 승격 논의와 별개로 recipe 저작자가 참고할 사실.)
117
+
118
+ ## 전제 재검토 필요 — DescriptionList
119
+
120
+ 실제 라벨→값 사실 화면은 있다(`PlayerScreen.tsx:181-258` 프로필 섹션). 그런데 이
121
+ 화면은 `columns:1|2` 그리드가 아니라 이미 beta인 `AppList`/`AppListRow`(단일 열)로
122
+ 풀려 있다. `docs/description-list.md`가 든 두 후보(FA 등급 시트, 통산 화면)는 재확인
123
+ 결과 둘 다 안 맞는다 — FA 등급 시트는 짧은 라벨→값이 아니라 등급별 긴 설명 문장이고,
124
+ 통산 화면은 카드가 아니라 표(`StatTable`)라고 코드 주석에 명시돼 있다. 그리고
125
+ `resolveDescriptionListColumnCount`가 참고한 실제 폰트스케일 reflow 버그 수정은
126
+ `resolveStatisticColumnCount`(Statistic, 이미 beta)의 것이지 DescriptionList의 것이
127
+ 아니다. **제안**: 승격 근거를 찾기 전에 "이 계약이 정말 그리드 reflow를 중심에 둬야
128
+ 하는가, 아니면 실제로는 List/ListRow가 이미 푼 문제의 재포장인가"부터 재확인.
129
+
130
+ ## 후속 판정 완료 — Result는 EmptyState에 흡수되지 않았다
131
+
132
+ 위에서 유보했던 질문("Result가 EmptyState에 흡수된 것 아닌가")을 리드 지시로 끝까지
133
+ 따라갔다. 결론은 `docs/result.md`의 "실측" 절에 반영했다 — 요약하면:
134
+
135
+ 1. Result가 실제로 가리키는 문제(결제 성공, 제출 실패 같은 **행동 뒤** flow terminus)는
136
+ 두 제품 어디에도 없다. `ErrorFallback`/`PlayerScreen`의 에러 화면은 사용자 행동의
137
+ 결과가 아니라 **데이터 로드 상태**(`content` 축: loading/error/empty/success)다.
138
+ BurnTok의 유일한 후보(`create/page.tsx`의 `Phase`)도 재확인해 보니 성공은 실제
139
+ 생성물 미리보기, 실패는 인라인 문장 하나일 뿐 추상적 Result 화면이 아니다.
140
+ 2. "EmptyState는 tone이 없다"는 `docs/result.md`의 기존 근거는 실물과 어긋난다 —
141
+ `AppStateView`/`AppStateRegion`이 이미 error에 danger 아이콘, empty에 중립 아이콘을
142
+ 고정 배정하고 있다. 하지만 이게 "그러니 흡수하라"로 이어지지는 않는다 — 실제 제품이
143
+ 가르는 축은 success/failure/info가 아니라 **"실패가 화면 전체를 막는가 구역만
144
+ 막는가"**(`AppStateView` vs `AppStateRegion`)이고, 이건 Result의 `status`에도
145
+ EmptyState의 (없는) tone에도 없는 **제3의 축**이다.
146
+
147
+ **판정: 흡수 아님.** 둘 다 `docs/ant-design-coverage.md`의 「검증할 화면이 없음」
148
+ 범주에 남는다 — catalog 행 변경 없음. 대신 실측에서 나온 "전체 화면 vs 구역"이라는
149
+ 새 관찰을 다음 Result 저작 배치를 위해 `docs/result.md`에 남겨 뒀다.
150
+
151
+ ## 문서 정정이 필요한 자리
152
+
153
+ 이번 조사에서 기존 `docs/*.md`가 든 "실사용처"/"유력 후보"가 실물과 어긋난 경우를
154
+ 넷 발견했다. 리드가 원 저작자와 함께 정정 여부를 판단해야 한다.
155
+
156
+ 1. **`docs/steps.md`** — 유력 후보가 실현되지 않았다. `OnboardingScreen.tsx`는 여전히
157
+ `AppProgress`(연속 막대)를 쓰고, 개별 단계 마커로 교체된 적이 없다.
158
+ 2. **`docs/breadcrumb.md`** — 예시로 든 "구단 상세→선수단→선수 상세"는 **Native** 화면인데
159
+ Breadcrumb는 `platform: "web"` 전용이라 애초에 근거가 될 수 없는 조합이다.
160
+ 3. **`docs/description-list.md`** — 든 두 후보(FA 등급 시트, 통산 화면) 모두 재확인 결과
161
+ 맞지 않는다(위 상세 참고).
162
+ 4. **`docs/image.md`** — "선수 프로필 사진, FA 등급 차트 이미지"가 vertical slice 후보로
163
+ 적혀 있지만 코드에 해당 화면이 없다(`PlayerScreen.tsx`/`FaCenterScreen.tsx`에 `Image`
164
+ 사용 자체가 없음).
165
+
166
+ ## 게이트
167
+
168
+ `pnpm typecheck && pnpm test` — 변경한 파일이 이 문서 하나뿐이라 회귀 없음(hjm에서
169
+ `build`는 실행하지 않음). 두 제품 저장소는 읽기만 했다.
@@ -0,0 +1,69 @@
1
+ # QRCode — 계약을 만들지 않는다
2
+
3
+ ## 문제로 제기된 것
4
+
5
+ Ant Design `QRCode`는 문자열을 스캔 가능한 격자로 그리고, 오류 정정 레벨(`level`)과
6
+ 만료/로딩 상태(`status: active | loading | expired`)를 함께 다룹니다. 이 컴포넌트를
7
+ 검토하며 나온 관찰은 다음과 같았습니다 — 오류 정정 레벨, **스캔이 가능한 최소 렌더
8
+ 크기**, 대비 요구(밝은 배경 위 어두운 모듈), 그리고 **QR을 읽을 수 없는 사용자를 위한
9
+ 대체 경로**(같은 목적지의 링크·텍스트)가 계약할 거리로 보인다는 것이었습니다. 다만
10
+ 인코딩 자체(문자열 → 격자 픽셀)는 이 패키지가 하지 않고 제품이 라이브러리로 그린다는
11
+ 전제도 함께 있었습니다.
12
+
13
+ ## 판정: 만들지 않는다
14
+
15
+ ### 1. 두 제품 중 어디에도 실사용처가 없다
16
+
17
+ `Yajalal RN`(`modules/app-rn/src`)과 `BurnTok`(`apps/web/src`, `packages/design-system/src`)
18
+ 양쪽을 `qr`/`QRCode`로 전수 검색했지만 실제 화면 코드에서 QR을 그리거나 스캔하는 곳은
19
+ 없었습니다. 유일한 매치는 웹팩 빌드 산출물의 우연한 해시 문자열(`0qr3`, `1qr_kgj`)뿐으로
20
+ 제품 코드가 아닙니다. 티켓·프로필 공유·앱 다운로드 유도 등 QR이 흔히 쓰이는 흐름 자체가
21
+ 두 제품 어디에도 아직 없습니다.
22
+
23
+ ### 2. 진짜 계약처럼 보이는 부분은 이미 다른 컴포넌트가 소유한다
24
+
25
+ 관찰에서 유일하게 "새 상태 축"처럼 보였던 것 — **QR만으로는 도달할 수 없는 사용자를 위한
26
+ 대체 경로가 함께 있어야 한다** — 는 사실 새로운 개념이 아닙니다. `src/image.ts`의
27
+ `ImageDescriptor`가 이미 정확히 같은 모양(`Icon`과 동일한 decorative/informative
28
+ discriminated union)으로 이 문제를 풀어 두었습니다: 사진 자체가 유일한 정보 전달
29
+ 수단이면 `decorative: false` + 현지화된 `accessibilityLabel`이 함께 필요합니다
30
+ (`docs/image.md` "대체 텍스트의 의무 — Icon과 같은 모양"). QR 코드를 `decorative: false`
31
+ `Image`로 취급하고 그 옆에 같은 목적지를 가리키는 `Link`를 나란히 두면, "대체 경로 없이
32
+ QR만 보여주지 않는다"는 계약은 새 컴포넌트 없이 이미 성립합니다. antd가 갖는
33
+ `status: active | loading | expired`도 `Image`의 `idle | loading | loaded | error` 콘텐츠
34
+ 상태를 그대로 쓰면 되는 문제입니다.
35
+
36
+ 남는 것 — 최소 렌더 크기, 모듈 대비 — 은 상태 축이 아니라 **인코딩 라이브러리가 실제
37
+ 모듈 크기를 알아야 계산할 수 있는 렌더링 파라미터**입니다. 이 패키지는 인코딩을 하지
38
+ 않기로 이미 전제했으므로, 이 값을 검증할 입력 자체가 이 계층에 없습니다. Statistic이
39
+ 포맷된 문자열만 받듯, QR 크기·대비도 그 값을 실제로 계산하는 제품/렌더러의 몫입니다.
40
+
41
+ ### 3. 결론
42
+
43
+ 새 상태 축도, 새 접근성 개념도 남지 않습니다 — 유일하게 유효했던 부분(대체 경로 의무)은
44
+ `Image` + `Link` 조합으로 이미 표현 가능하고, 나머지는 렌더러가 소유하는 인코딩
45
+ 파라미터입니다. 측정된 제품 요구도 없습니다. `docs/authoring-brief.md`와
46
+ `docs/expansion-roadmap.md`가 명시한 대로 이 저장소는 측정되지 않은 표면을 미리 만들지
47
+ 않습니다.
48
+
49
+ ## 만들지 않은 것
50
+
51
+ `src/qrcode.ts`, `test/qrcode.test.ts`는 없습니다. `componentCatalog`의
52
+ `{ name: "QRCode", category: "data-display", platform: "shared", status: "planned" }` 행과
53
+ crosswalk의 `QRCode → QRCode` direct 관계(`src/component-references.ts:103`)는 건드리지
54
+ 않습니다 — 이름 자리를 지우자는 것이 아니라, 지금 채울 새 계약이 없다는 것입니다.
55
+
56
+ ## 뒤집힐 조건
57
+
58
+ 다음 중 하나가 실제로 측정되면 이 판정을 다시 엽니다.
59
+
60
+ 1. Yajalal 또는 BurnTok에 QR이 실제로 필요한 vertical slice(예: 오프라인 티켓, 프로필
61
+ 공유 QR, 앱 설치 유도)가 나온다.
62
+ 2. 그 슬라이스에서 `Image` + `Link` 조합으로는 표현할 수 없는 새 요구(예: 코드 자체의
63
+ 유효 기간을 컴포넌트가 알아야 하고, 만료를 `Image`의 `error` 상태와 다르게 발표해야
64
+ 하는 경우)가 나온다.
65
+
66
+ ## catalog 배선 명세 제안 (리드 적용, 참고용)
67
+
68
+ 지금은 변경할 것이 없습니다. 실제로 QR이 필요해지면 새 recipe를 만들기보다
69
+ `imageRecipe` + `linkRecipe` 조합으로 시작할 것을 권합니다.
package/docs/rating.md ADDED
@@ -0,0 +1,58 @@
1
+ # Rating — 계약을 만들지 않는다
2
+
3
+ ## 먼저 판정: 필요한가
4
+
5
+ 야잘알(KBO 정보)과 BurnTok 어디에도 별점(리뷰 점수, 만족도 별표) 화면이 없다. 야잘알의
6
+ "Overall 레이팅"(선수 능력치를 0–99 척도로 보여주는 기존 기능)은 antd `Rate`가 푸는
7
+ 문제(사용자가 1~N개의 아이콘 중 몇 개를 채우는가)와 다르다 — 이미 `Statistic`이나
8
+ 전용 배지로 표시되는 **숫자**이지 별을 세는 입력/표시가 아니다. 측정된 실제 요구가
9
+ 없다.
10
+
11
+ ## 그래도 "다른 문제인지" 판정한다
12
+
13
+ 측정된 요구가 없어도, antd `Rate`가 실제로 새 semantic을 필요로 하는지는 나눠서 봐야
14
+ 한다. 두 가지 실사용 형태로 나뉜다.
15
+
16
+ ### 1. 입력으로 쓰일 때 — Slider의 재구성이다
17
+
18
+ `Rate`를 사용자가 클릭/드래그해 값을 정하는 입력으로 쓰면, 필요한 값 계산은 이미 전부
19
+ 있다: `min`(보통 0), `max`(아이콘 개수), `step`(정수 1, half-star면 0.5) — `Slider`가
20
+ `src/number-field.ts`의 `validateNumericRangeConfig`/`snapToStep`/`clampToRange`를 이미
21
+ 가져다 쓰고 있고, 이 함수들은 소수 step(0.5)도 이미 지원한다(`stepPrecision`/
22
+ `roundToStepPrecision`이 일반화돼 있다) — half-star를 위한 새 수학이 필요 없다는
23
+ 뜻이다. `Rate`와 `Slider`가 다른 것은 **외형**(연속 트랙+손잡이 vs 아이콘 N개를 줄지어
24
+ 채움)일 뿐, 값이 어디 있는지·어디까지 갈 수 있는지·어떻게 스냅하는지는 완전히 같은
25
+ 문제다. Web `role="slider"` + `aria-valuenow`(정수/반정수 step)도 그대로 성립한다 —
26
+ 새 ARIA 패턴이 필요하지 않다.
27
+
28
+ ### 2. 표시로 쓰일 때 — Statistic의 영역이다
29
+
30
+ 실제로 더 흔한 쓰임은 입력이 아니라 **평균 점수를 보여주기만 하는 것**("4.3점, 별
31
+ 다섯 개 중 채워진 정도")이다. 이건 `Statistic`이 이미 "제품이 포맷한 값을 받아 보여주기만
32
+ 한다"는 계약으로 푸는 문제와 같다 — 새 controlled 상태도, 새 접근성 개념도 필요 없다.
33
+ 채워진 비율을 아이콘 줄로 그리는 것은 순수 시각 표현이라 `resolveSliderFillFraction`과
34
+ 같은 종류의(이미 존재하는) 0..1 비율 계산이면 충분하다.
35
+
36
+ ## 결론
37
+
38
+ 두 형태 모두 **새 semantic이 없다** — 있다면 기존 계약(Slider의 값 수학, Statistic의
39
+ 표시 계약) 위에 아이콘 줄이라는 새 **recipe**(외형)만 있으면 된다. 측정된 제품 요구도
40
+ 없다. `Notification`(→ Toast 설정)·`Dropdown`(→ Menu alias)과 같은 자리다 — 이름이
41
+ 다른 컴포넌트가 필요한 게 아니라, 필요해지는 순간 기존 계약에 recipe만 얹으면 된다.
42
+
43
+ `src/rating.ts`, `test/rating.test.ts`는 만들지 않는다. crosswalk의 `Rate → Rating`
44
+ (`relationship: "adapted"`)은 범위 추적용으로 그대로 둔다 — target `ComponentId`를 바꿀
45
+ 필요가 없다(`docs/dropdown.md`의 Dropdown crosswalk 처리와 같다). catalog row(`{ name:
46
+ "Rating", category: "input", platform: "shared", status: "planned", aliases: ["Rate"] }`)도
47
+ 건드리지 않는다 — 지금 채울 계약이 없다는 뜻일 뿐, 이름 자리를 지우자는 게 아니다.
48
+
49
+ ## 뒤집힐 조건
50
+
51
+ 1. 실제 화면에서 별점 **입력**이 필요해지면: `Slider`에 아이콘 기반 recipe 변형(연속
52
+ 트랙 대신 N개의 아이콘, 클릭 시 가장 가까운 정수/반정수로 스냅)을 추가한다 — 새
53
+ 컴포넌트가 아니라 `sliderRecipe`의 새 variant다.
54
+ 2. 실제 화면에서 별점 **표시**가 필요해지면: `Statistic`에 아이콘 fill 표현을 추가한
55
+ recipe 변형을 고려한다.
56
+ 3. 두 형태 중 하나가 기존 값 수학이나 접근성 계약으로 표현할 수 없는 요구(예: 정수도
57
+ 반정수도 아닌 자유 분수 표시, 별 모양이 아닌 클릭 불가 장식과 클릭 가능 입력이
58
+ 레이아웃까지 완전히 달라야 하는 경우)를 드러내면 이 판정을 다시 연다.
@@ -0,0 +1,90 @@
1
+ # Responsive values and Grid contract
2
+
3
+ ## 한 축만 공유한다
4
+
5
+ Web의 CSS media query와 React Native의 `useWindowDimensions`를 각각 제품에서
6
+ 임의로 해석하지 않는다. 두 renderer가 측정한 **창 너비**를 같은 네 class로
7
+ 번역한다.
8
+
9
+ | WindowClass | 최소 너비 | Web | Native |
10
+ | --- | ---: | --- | --- |
11
+ | `compact` | 0 | CSS px | density-independent point |
12
+ | `medium` | 600 | CSS px | density-independent point |
13
+ | `expanded` | 960 | CSS px | density-independent point |
14
+ | `wide` | 1280 | CSS px | density-independent point |
15
+
16
+ `resolveWindowClass(windowWidth)`의 경계는 inclusive다. 예를 들어 599.9는
17
+ `compact`, 600은 `medium`이다. `phone`/`tablet`/`desktop` 같은 기기명은 같은
18
+ 크기의 split view나 foldable 창을 잘못 분류하므로 계약에 넣지 않는다.
19
+
20
+ ## ResponsiveValue의 total fallback
21
+
22
+ `ResponsiveValue<T>`는 `compact`를 반드시 요구하고, 나머지는 sparse override다.
23
+
24
+ ```ts
25
+ const columns: ResponsiveValue<number> = {
26
+ compact: 1,
27
+ medium: 2,
28
+ wide: 4,
29
+ };
30
+
31
+ resolveResponsiveValue(columns, "expanded"); // 2
32
+ resolveResponsiveValue(columns, "wide"); // 4
33
+ ```
34
+
35
+ 값이 없는 class는 가장 가까운 좁은 class로 내려가며, 넓은 class 값을 좁은
36
+ 화면으로 역전파하지 않는다. `compact`가 필수라 모든 WindowClass에서 결과가
37
+ 존재한다. 객체 payload도 scalar와 구분하려는 런타임 추측 없이 그대로 쓸 수 있다.
38
+ 오타 난 class는 무시하지 않고 validator가 거부한다.
39
+
40
+ ## Grid가 소유하는 것
41
+
42
+ `GridDescriptor`는 다음의 renderer-neutral 의미만 소유한다.
43
+
44
+ - class별 **요청 열 수** `columns`
45
+ - spacing token만 받는 `gap`; 단일 token 또는 `{ row, column }`
46
+ - 여러 열이 목표 폭보다 좁아지기 전에 열 수를 줄이는 선택적 `minColumnWidth`
47
+ - 자식 source order를 바꾸지 않는 고정 `row-major` flow
48
+
49
+ ```ts
50
+ const cards: GridDescriptor = {
51
+ columns: { compact: 1, medium: 2, expanded: 3, wide: 4 },
52
+ gap: {
53
+ compact: "sm",
54
+ expanded: { row: "lg", column: "md" },
55
+ },
56
+ minColumnWidth: { compact: 160, wide: 220 },
57
+ };
58
+
59
+ const layout = resolveGridLayout(cards, {
60
+ windowWidth: 1440,
61
+ availableWidth: 920,
62
+ });
63
+ ```
64
+
65
+ 여기서 `windowWidth`는 WindowClass 선택에만 쓰고 `availableWidth`는 page padding,
66
+ sidebar 등을 뺀 실제 Grid 내부 폭과 열 너비 계산에만 쓴다. 둘을 하나로 합치면
67
+ wide 창의 좁은 사이드 패널이 wide 열 수를 받은 뒤 overflow하는 문제가 생긴다.
68
+
69
+ `columns`는 목표이자 최댓값이다. `minColumnWidth` 때문에 모두 들어가지 않으면
70
+ resolver가 열 수를 줄이되 요청보다 늘리지는 않는다. 단일 열은 작은 split view
71
+ 자체를 overflow시키지 않도록 컨테이너 폭까지 줄어들 수 있다. `minColumnWidth`가
72
+ 없으면 요청 열 수를 그대로 유지하고, gap을 제외한 열 폭이 0 이하인 구성은
73
+ 조용히 깨뜨리지 않고 오류로 보고한다.
74
+
75
+ ## Renderer translation
76
+
77
+ - Web: `repeat(columns, minmax(0, 1fr))`, numeric row/column gap으로 번역한다.
78
+ - Native: 계산된 `columnWidth`, row/column gap을 `flexWrap` item에 번역한다.
79
+ - 양쪽 모두 자식 배열을 재정렬하지 않는다. RTL은 logical start의 시각 방향만
80
+ 바꾸며 source/read/focus order는 그대로 둔다.
81
+
82
+ 이 계약은 DataTable의 행/열 semantic이나 Masonry packing을 포함하지 않는다.
83
+ Grid child 자체의 role과 접근성 이름도 각 child component가 소유한다.
84
+
85
+ ## 검증 경계
86
+
87
+ 단위 테스트는 네 breakpoint 경계, sparse fallback, 잘못된 class/token/열 수,
88
+ 축별 gap, 좁은 컨테이너 collapse, 불가능한 geometry를 검증한다. 실제 renderer는
89
+ 별도로 compact/medium/expanded/wide viewport와 RTL, 200% text 환경에서 overflow와
90
+ source/focus order를 evidence로 남겨야 한다.
package/docs/result.md ADDED
@@ -0,0 +1,96 @@
1
+ # Result contract
2
+
3
+ **문제.** 흐름이 끝나는 화면 — 결제 성공, 제출 실패, 존재하지 않는 페이지 — 을 하나의
4
+ 상태와 최대 두 개의 다음 행동으로 보여줍니다.
5
+
6
+ **일반화한 계약.**
7
+
8
+ ```ts
9
+ const saved = {
10
+ status: "success",
11
+ title: "저장했어요",
12
+ description: "변경 사항을 반영했습니다.",
13
+ actions: [{ label: "홈으로", onAction: goHome }],
14
+ } satisfies ResultDescriptor;
15
+ ```
16
+
17
+ - `status`는 `success | failure | info` 세 가지로 좁힙니다. Ant Design `Result`의
18
+ 403/404/500은 Web 페이지 개념이지 플랫폼 중립 상태가 아닙니다 — adapter가 이런 HTTP
19
+ 의미를 `failure`로 번역하고, 실제 문구("페이지를 찾을 수 없어요")는 제품이 `title`/
20
+ `description`에 직접 채웁니다.
21
+ - `actions`는 최대 두 개입니다. 첫 번째가 primary, 두 번째가 secondary입니다.
22
+ **셋 이상은 validator가 거부합니다** — 조용히 세 번째를 잘라내면 화면 작성자가 잘렸다는
23
+ 사실을 못 보고 넘어가기 때문입니다.
24
+ - 각 action은 `label`과 `onAction`이 필수입니다. `accessibilityLabel`이 없으면 resolver가
25
+ `label`로 채웁니다.
26
+
27
+ ## EmptyState와의 경계
28
+
29
+ 이 시스템에는 이미 `EmptyState`(beta)가 있습니다. 둘 다 "콘텐츠 대신 보여주는 화면"이라는
30
+ 점은 같지만 의미가 정반대입니다.
31
+
32
+ - `EmptyState`는 **아직 없음**입니다 — 검색 결과가 없거나 목록이 비었을 뿐, 조건이
33
+ 바뀌면 다시 채워질 수 있는 자리입니다. 사용자는 계속 그 화면에 머무르며 필터를
34
+ 바꾸거나 새로고침합니다.
35
+ - `Result`는 **끝남**입니다 — 이 흐름은 여기서 종료되었고, 다음 행동은 이 화면에
36
+ 머무르는 것이 아니라 다른 곳으로 이동하거나 다시 시도하는 것입니다.
37
+
38
+ 그래서 `Result`만 `status`(success/failure/info) tone 축을 가지고 `resultRecipe.tones`가
39
+ 성공/실패/정보를 시각적으로 구분합니다. `EmptyState`는 상태 tone이 없고 아이콘이 항상
40
+ `semanticColors.content.decorative`로 중립인 채입니다 — "아직 없음"은 성공도 실패도
41
+ 아니기 때문입니다. 반대로 `Result`는 항상 하나의 명확한 tone을 요구합니다(`status`가
42
+ 필수 필드입니다).
43
+
44
+ ### 실측 — 흡수는 아니지만, 위 근거 중 하나는 실물과 어긋난다
45
+
46
+ `planned → beta` 실측 과정에서 "Result가 EmptyState에 흡수된 것 아닌가"라는 질문이
47
+ 나왔다. 야잘알의 `ErrorFallback`(`app/_layout.tsx:142-166`, 크래시 바운더리)과
48
+ `PlayerScreen`의 로드 실패 화면(`features/player/PlayerScreen.tsx:44-99`) 둘 다 별도
49
+ 컴포넌트 없이 `AppStateView`가 `AppEmptyState`의 아이콘만 바꿔 렌더링하기 때문이다.
50
+ 직접 코드를 읽고 두 가지를 확인했다.
51
+
52
+ 1. **Result가 실제로 가리키는 문제(결제 성공, 제출 실패처럼 사용자 행동 뒤에 오는
53
+ flow terminus)는 두 제품 어디에도 없다.** 위 두 화면은 사용자 행동의 결과가 아니라
54
+ **데이터를 못 불러왔다**는 상태다 — `PlayerScreenProps.status`가
55
+ `'loading'|'error'|'empty'|'success'`인 것에서 보이듯, 이건 `docs/expansion-roadmap.md`
56
+ 「공통 상태 축」의 `content` 축(idle/loading/loadingMore/empty/error)을 화면 전체
57
+ 또는 구역 단위로 렌더링한 것이다. BurnTok의 유일한 "생성 성공/실패" 후보
58
+ (`apps/web/src/app/create/page.tsx`의 `Phase`)도 재확인해 보니 성공은 실제 생성물
59
+ 미리보기를, 실패는 인라인 문장 하나를 보여줄 뿐 — 추상적 아이콘+제목+행동 화면이
60
+ 아니다. 즉 Result의 정의가 가리키는 화면 자체가 아직 어느 제품에도 없다.
61
+ 2. **"EmptyState는 tone이 없다"는 근거는 실물과 다르다.** `AppStateView`/
62
+ `AppStateRegion`(`components/ui/AppStateView.tsx`, `AppStateRegion.tsx`)은 이미
63
+ `error`에 `colors.danger`(또는 `tone="danger"`) 아이콘을, `empty`에 중립 아이콘을
64
+ 각각 고정 배정한다 — "아이콘이 항상 decorative로 중립"은 지금 이 계약 초안
65
+ (`emptyStateRecipe`, tone 필드 없음)에는 맞지만 실제 제품 구현에는 맞지 않는다.
66
+
67
+ 그런데 이 사실이 "그러니 EmptyState에 tone을 추가하고 Result를 흡수시켜라"로
68
+ 이어지지는 않는다. 실제 제품이 **진짜로** 가르는 축은 success/failure/info라는
69
+ 결과의 종류가 아니라 **"이 실패가 화면 전체를 막는가, 일부만 막는가"**
70
+ (`AppStateView`=전체, 유일한 출구, 채움 버튼 / `AppStateRegion`=구역, 복구는 선택지
71
+ 중 하나, ghost 버튼 — 코드 주석이 "§6 오류 복구 행동"을 직접 인용해 이 구분을
72
+ 설명한다)다. 이 축은 `Result`의 `status`와도, `EmptyState`의 (없는) tone과도
73
+ 무관하다 — 둘 중 어느 계약도 지금 이 축을 갖고 있지 않다.
74
+
75
+ **판정: 흡수됨이 아니다.** Result가 푸는 문제(사용자 행동 뒤의 flow terminus)와
76
+ EmptyState가 지금 실제로 쓰이는 문제(전체/구역 단위 load-state 렌더링)는 서로 다르다 —
77
+ 겹쳐 보인 이유는 두 계약 다 vertical slice가 아직 없어서 같은 화면(데이터 로드 실패)에
78
+ 동원됐을 뿐이다. `docs/ant-design-coverage.md`의 「만들지 않는다 세 종류」 표 기준으로는
79
+ Result·EmptyState 둘 다 **"검증할 화면이 없음"**(계약은 유효하나 확인할 화면이 없음)
80
+ 범주에 남아야 하고, catalog 행도 그대로 둔다. 다만 이 조사에서 나온 "전체 화면 vs
81
+ 구역" 축은 실제로 관측된 요구라 다음에 Result를 다시 다룰 저작자에게 남겨 둔다 —
82
+ Result 자체의 축으로 넣을지, 별도 계약(예: 로드 상태 렌더러의 scope)으로 둘지는 이
83
+ 문서가 결정하지 않는다.
84
+
85
+ ## 플랫폼 번역
86
+
87
+ Web/Native 모두 아이콘 + 제목 + 설명 + action 슬롯을 세로로 쌓습니다. `primaryAction`/
88
+ `secondaryAction`은 이 계약이 스타일을 소유하지 않고 기존 `Button`/`Link` 컴포넌트를
89
+ 그대로 조합합니다 — Result 자체의 recipe는 아이콘 tone, 타이포그래피 위계, 슬롯 사이
90
+ gap만 제공합니다.
91
+
92
+ ## 검증 화면 (예정)
93
+
94
+ 야잘알 결제/제출 흐름의 성공·실패 화면을 첫 vertical slice 후보로 남깁니다.
95
+ `planned → beta` 승격에는 primary-only, primary+secondary, action-없음 세 조합의 실제
96
+ 화면 검증이 포함되어야 합니다.
@@ -0,0 +1,97 @@
1
+ # HJM Showcase
2
+
3
+ HJM Showcase는 정적인 화면 모음이 아니라 공통 계약의 실행 가능한 문서입니다.
4
+ `componentCatalog`가 범위와 계약 성숙도(`status`) 및 Web/Native renderer 성숙도
5
+ (`surfaceStatus`)를, `showcaseManifest`가 각 컴포넌트에 필요한 시각·동작·접근성
6
+ 증거를 소유합니다. 두 성숙도는 독립적입니다. 계약이 stable이어도 renderer 증거가
7
+ 부족하면 해당 surface는 beta 또는 planned일 수 있습니다.
8
+
9
+ ## 실행
10
+
11
+ ```bash
12
+ pnpm install
13
+ pnpm showcase:web
14
+ ```
15
+
16
+ 정적 빌드와 타입 검사는 다음 명령으로 실행합니다.
17
+
18
+ ```bash
19
+ pnpm showcase:web:check
20
+ pnpm showcase:web:build
21
+ ```
22
+
23
+ ## 환경 도구
24
+
25
+ 모든 Web story는 toolbar에서 다음 환경을 즉시 바꿀 수 있어야 합니다.
26
+
27
+ - light / dark
28
+ - LTR / RTL
29
+ - 100% / 150% / 200% text
30
+ - full / reduced motion
31
+
32
+ RN on-device story도 같은 `showcaseEnvironmentMatrix`와 story identifier를 사용합니다.
33
+ Web과 Native가 같은 DOM/view tree를 만드는 것이 아니라, 같은 의미·상태 전환·접근성
34
+ 결과를 제공하는 것이 parity 기준입니다.
35
+
36
+ ## story 범위
37
+
38
+ `surfaceStatus`가 `planned`인 surface는 renderer evidence를 요구하지 않고 contract
39
+ 문서만 유지합니다. `beta`는 first-party renderer export와 최소 default evidence가 있어야
40
+ 하며, 아직 통과하지 못한 dark·긴 문구·큰 글자·RTL·reduced motion·접근성 scenario를
41
+ generated evidence debt로 공개합니다. `stable`은 이 required scenario를 모두 통과해야
42
+ 합니다. behavior가 있는 컴포넌트는 keyboard interaction, adaptive 컴포넌트는 두 surface가
43
+ 모두 beta 이상일 때 Web/Native parity가 승격 debt에 추가됩니다.
44
+
45
+ `beta`의 구현 source of truth는 `@hjmds/react/evidence`와
46
+ `@hjmds/react-native/evidence`입니다. 제품 story는 채택 evidence이지 first-party 구현을
47
+ 대신하지 않습니다. renderer claim이 없으면 `planned`, 플랫폼이 지원되지 않으면
48
+ `unsupported`입니다. `stable` 승격은 story 파일의 존재만으로 하지 않으며 위 required
49
+ scenario의 자동 evidence가 모두 통과해야 합니다.
50
+
51
+ 제품 전용 구단 마크, 경기 상태, 피드 콘텐츠 같은 의미는 HJM story에 올리지 않습니다.
52
+ BurnTok과 Yajalal의 실제 채택 story에서 공통 의미로 매핑된 결과만 보여줍니다.
53
+
54
+ ## 세 개의 증거 층
55
+
56
+ - HJM Web Storybook(6006): 모든 Stable/Beta 계약을 개별 페이지로 제공하고 모든 환경 축을 전환합니다.
57
+ - BurnTok Web Storybook(6007): 실제 제품 Web renderer를 Tailwind·Next.js 환경에서 실행합니다.
58
+ - Yajalal RN Storybook(8082): 실제 Native renderer를 iOS/Android 기기에서 실행합니다.
59
+
60
+ HJM의 정적 Storybook은 `main` 갱신 시 GitHub Pages artifact로 배포됩니다. 빌드 후 검증은
61
+ Stable/Beta 컴포넌트 페이지가 하나라도 누락되면 실패합니다. 제품 Storybook은 각 제품 저장소의
62
+ CI에서 별도로 빌드·번들 검증하며, HJM 카탈로그의 story identifier로 연결합니다.
63
+
64
+ ## 자동 동기화
65
+
66
+ 수동 expected 목록은 두지 않습니다. 다음 산출물은 모두 같은 `componentCatalog`와
67
+ `showcaseManifest`에서 계산됩니다.
68
+
69
+ - `pnpm contracts:sync`: `docs/generated/component-maturity.md`와
70
+ `docs/generated/showcase-manifest.json` 갱신
71
+ - `pnpm contracts:check`: 생성 파일이 source와 다르면 CI 실패
72
+ - `pnpm evidence:sync`: first-party renderer claim과 required scenario를 결합한
73
+ `renderer-evidence.json`/`.md` 및 명시적 beta debt 갱신
74
+ - `pnpm evidence:check`: renderer projection이 source와 다르면 CI 실패
75
+ - first-party renderer의 `default` claim은 `proofs[]`에서 canonical render table의 stable
76
+ case id와 연결되고, 그 table은 evidence ID와 exact equality를 검증한 뒤 모든 case를
77
+ 실행한다. 현재 gate는 구조화된 test-result registry가 없는 non-default scenario claim을
78
+ 전부 거부한다. keyboard·accessibility·device 같은 축은 test case ID와 실행 결과 artifact를
79
+ exact join하는 registry를 먼저 추가한 뒤에만 열 수 있으며, 파일 안의 주석·문자열이나
80
+ export 존재만으로 scenario를 claim할 수 없다.
81
+ - HJM Storybook: `surfaceStatus.web`으로 renderer/contract-only/unsupported 분류
82
+ - 제품 Storybook verifier: 일반 앱 CI에서는 설치된 `@hjmds/design-contracts/showcase`, release
83
+ candidate gate에서는 payload full SHA의 generated manifest를 읽어 해당 surface의 active ID와
84
+ exported CSF registration을 비교하고 missing/unknown/duplicate를 실패 처리
85
+ - 제품 evidence artifact: 검증된 story ID와 실제 실행된 scenario만 schema v1 JSON으로 출력
86
+ - canonical tag gate: 최소 권한 token으로 릴리스 시작 시 두 private 제품의 default-branch
87
+ HEAD를 full SHA로 캡처해 `repository_dispatch`하고, 같은 canonical release SHA·consumer
88
+ SHA·correlation ID가 run과 artifact JSON 내부까지 exact-join된 두 검증이 모두 성공하기
89
+ 전에는 tag를 생성하지 않음
90
+
91
+ `compareShowcaseStoryIds`와 `assertShowcaseStoryIds`는 모든 Stable/Beta 컴포넌트를 제공하는
92
+ first-party/full-coverage Storybook의 inventory gate입니다. 부분 채택 소비 앱이나 개별 화면은
93
+ 전체 active ID를 가장하지 않고, 실제 실행한 항목만 versioned evidence artifact로 제출합니다.
94
+
95
+ Story 파일이 존재한다는 이유만으로 dark, keyboard, accessibility 등을 통과했다고 기록하지
96
+ 않습니다. 환경 toolbar는 수동 확인 기능이고, scenario evidence는 interaction test, axe,
97
+ visual regression 또는 on-device test가 실제 실행된 경우에만 별도로 제출합니다.