@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,116 @@
1
+ # CommandPalette contract
2
+
3
+ ## 문제
4
+
5
+ 키보드로 전체 앱의 행동을 검색해 실행한다(⌘K 스타일). antd에는 이 문제에 직접
6
+ 대응하는 컴포넌트가 없다 — `antDesignReferenceComponents`에 `CommandPalette`
7
+ crosswalk가 없다. 가장 가까운 antd 표면인 `AutoComplete`/`showSearch` Select는
8
+ 정확히 아래 판정이 다루는 질문이지, 별도 커버리지 공백이 아니다.
9
+
10
+ ## Combobox와의 경계 (판정)
11
+
12
+ CommandPalette는 "Combobox + 모달 표면 + 전역 단축키"로 보일 수 있지만, 결정적으로
13
+ 다른 지점이 하나 있고 거기서 나머지 판단이 갈린다.
14
+
15
+ **결과가 값이 아니라 행동이다.** Combobox의 commit은 `selectedKey`를 필드의
16
+ 지속되는 값으로 만들고 `inputValue`가 그것을 계속 반영한다. "새 트윗 작성"을
17
+ 실행하는 데는 기억할 지속 값이 없다 — 팔레트는 닫히고 다음에 열 때 리셋된다.
18
+ 여기에 `selectedKey`/`onCommit`을 억지로 씌우면 열 때마다 `null`로 되돌아가는
19
+ 유령 값을 만들게 된다 — `docs/dropdown.md`가 Menu의 문제를 다른 이름으로 다시
20
+ 계약하지 않은 것과 같은 종류의 실수다. 그래서 항목 타입은 `SelectItemDescriptor`
21
+ (shortcut/tone을 의도적으로 뺀)가 아니라 `MenuItemDescriptor`(shortcut/tone을
22
+ 가진, 위험한 명령에 danger tone을 줄 수 있는) 모양을 그대로 쓴다
23
+ (`CommandPaletteItemDescriptor<Key> = MenuItemDescriptor<Key>`).
24
+
25
+ 이 한 가지를 빼면 나머지는 전부 기존 계약의 조합이다.
26
+
27
+ | 조각 | 판정 | 근거 |
28
+ | --- | --- | --- |
29
+ | 여러 출처가 섞인 목록(최근/명령어/검색 결과) | **Collection의 `sections`로 이미 된다** | `CollectionSource`가 이미 그룹 items를 표현한다. 새 데이터 모델 불필요 |
30
+ | 검색어 입력·필터링 | **Combobox의 `ComboboxInput`/`ComboboxCollectionState` 그대로 재사용** | local vs external filtering, `queryValue`/`resultQuery` staleness guard는 이미 완결된 계약이고, 명령 검색도 같은 비동기 검색 문제다 |
31
+ | 항목 간 키보드 탐색·typeahead | **`getCollectionNavigationTarget`/`getCollectionTypeaheadMatch` 그대로 재사용** | 어떤 `CollectionSource`에도 이미 일반화돼 있다 |
32
+ | 결과 실행(activate) | **새로 계약** | Select/Combobox의 `selectedKey` 모델이 맞지 않는 자리 — 위 판정 |
33
+ | 표면(모달, 포커스 트랩, Escape/outside) | **새로 계약(자급자족)**, Dialog 모양을 그대로 복사 | Dialog는 `src/dialog.ts`가 없어 import할 타입이 없다. SidePanel이 Sheet 모양을 복사해 자급자족한 것과 같은 이유 |
34
+ | 전역 단축키(⌘K) | **배제 — 제품 몫** | 어떤 키 조합인지, 전역인지 범위 한정인지는 앱의 결정이다. Link가 navigation을 소유하지 않는 것과 같은 경계 |
35
+
36
+ ## 일반화한 계약
37
+
38
+ ### 항목·출처
39
+
40
+ `CommandPaletteItemDescriptor`/`CommandPaletteSource`는 각각 `MenuItemDescriptor`/
41
+ `CollectionSource`의 별칭이다 — 새 필드를 만들지 않았다.
42
+
43
+ ### 검색·필터
44
+
45
+ `CommandPaletteInput`/`CommandPaletteQueryState`는 각각 `ComboboxInput`/
46
+ `ComboboxCollectionState`의 별칭이다.
47
+
48
+ ### 실행(activate)은 Menu의 onAction 모양을 따르되 자급자족한다
49
+
50
+ `onActivate`(즉시 실행)와 `onActivateAfterDismiss`(퇴장 전환이 끝난 뒤 실행)로
51
+ 나눈다 — `behaviorRegistry.menu`의 `onAction`/`onActionAfterDismiss` 분리와 같은
52
+ 이유다. Menu는 `src/menu.ts`가 없어 import할 타입이 없으므로 이 모듈이 같은 모양을
53
+ 독자적으로 선언한다. 실행한 명령이 다른 오버레이(예: Dialog)를 열어야 한다면
54
+ `docs/architecture.md`의 오버레이 stacking 규칙("Sheet를 먼저 닫고 exit 완료 뒤 후속
55
+ surface를 연다")과 같은 순서가 필요하고, `onActivateAfterDismiss`가 그 시점을
56
+ 제공한다.
57
+
58
+ ### 열림·닫힘
59
+
60
+ `CommandPaletteOpenState`는 Tooltip/Popover와 같은 `open`/`defaultOpen`/
61
+ `onOpenChange` discriminated union이다. `CommandPaletteDismissReason`은
62
+ `close-action | outside | escape | activation | programmatic`이다.
63
+
64
+ - `back`/`swipe`가 없다 — web 전용, 항상 모달이라 SidePanel의 `modal` 축도 없다.
65
+ - **`activation`은 `programmatic`과 똑같이 항상 허용된다.** 명령 실행은 무엇을
66
+ 하든 팔레트를 닫아야 한다 — `dismissible: false`인 정책이라도 막을 수 없다.
67
+ 이것은 Menu의 항목 선택이 항상 표면을 닫는 것(다중 선택 모드 제외)과 같은 종류의
68
+ "행동 완료는 표면 종료를 함의한다"는 규칙이다.
69
+ - `busy` 축은 없다. 팔레트 자신은 fire-and-forget이다 — `onActivate`가 실행되고
70
+ 팔레트는 닫힌다. 명령의 실제 효과가 비동기라면 그건 팔레트가 이미 사라진 뒤
71
+ 진행된다(`onActivateAfterDismiss`가 그 순서를 보장). AlertDialog의
72
+ `idle→busy→error` session을 여기 복제하는 것은 "명령이 끝날 때까지 팔레트가
73
+ 열려 있어야 한다"는, 측정되지 않은 요구를 추측하는 일이다.
74
+
75
+ ### 설명
76
+
77
+ `CommandPaletteDescriptor`는 `accessibilityLabel`과 `searchPlaceholder` 둘 다
78
+ **필수**다. Popover의 `accessibilityLabel`은 선택 사항이었다(콘텐츠가 보통 자체
79
+ heading을 가지므로) — CommandPalette는 다르다: `role="dialog"` 표면에 보이는 제목이
80
+ 없고 검색 입력 하나뿐이라, 검색창 placeholder만으로 렌더러마다 다른 접근 가능한
81
+ 이름을 만들 위험이 있다. 그래서 명시적으로 요구한다.
82
+
83
+ ## HJM 기본값
84
+
85
+ - `commandPaletteRecipe`는 새 색이나 형태를 만들지 않는다. 모달 chrome은
86
+ `floatingSurfaceContract`(배경/테두리/그림자), backdrop은 기존 `backdrop.modal`,
87
+ 검색창은 `fieldFrameContract`, 결과 행과 section label은 `collectionItemContract`를
88
+ 그대로 쓴다 — Menu/Select/Tree가 이미 쓰는 행 chrome과 시각적으로 같다.
89
+ - `maxWidth: 560`/`maxHeight: 420`만 새로 정했다 — 검색 결과 목록이 화면을 다 덮지
90
+ 않도록 하는 palette 특유의 크기 제약이다.
91
+
92
+ ## 플랫폼 번역
93
+
94
+ - Web: `role="dialog"`(모달), `aria-label`은 `accessibilityLabel`. 초기 focus는
95
+ 검색 입력으로 이동한다. Escape·backdrop 클릭은 `outside`/`escape`로 닫는다.
96
+ 결과 목록은 `getCollectionNavigationTarget`/`getCollectionTypeaheadMatch`가 이미
97
+ 제공하는 키보드 모델을 그대로 쓴다 — 새 keyboard table을 정의하지 않는다.
98
+ - Native: 이 컴포넌트는 `platform: web`이다. Native의 대응 검토는 측정된 요구가
99
+ 나온 뒤로 미룬다.
100
+ - Reduce Motion: Dialog/Popover와 같은 `motionPreset.enter/exit`을 재사용한다.
101
+
102
+ ## 공개한 축 / 배제한 축
103
+
104
+ | 축 | 상태 |
105
+ | --- | --- |
106
+ | 실행(`onActivate`/`onActivateAfterDismiss`) | 공개 |
107
+ | 검색/필터(`ComboboxInput`/`ComboboxCollectionState` 재사용) | 공개 |
108
+ | 여러 section 혼합 | 공개(Collection 기본 계약 그대로) |
109
+ | dismiss reason(`close-action`/`outside`/`escape`/`activation`/`programmatic`) | 공개 |
110
+ | `busy`(명령 실행 중 팔레트 유지) | **배제** — fire-and-forget, 측정된 요구 없음 |
111
+ | 전역 단축키 바인딩 | **배제** — 제품 몫 |
112
+ | 값 커밋(`selectedKey`) | **배제** — 결과는 값이 아니라 행동(위 판정) |
113
+
114
+ ## 검증 화면
115
+
116
+ 아직 없음. `planned → beta` 승격은 실제 제품 vertical slice 이후 리드가 진행한다.
@@ -0,0 +1,93 @@
1
+ # ConfirmPopover — 별도 컴포넌트를 만들지 않는다
2
+
3
+ ## 문제로 제기된 것
4
+
5
+ Ant Design `Popconfirm`은 "삭제하시겠습니까?" 같은 확인을 Modal보다 가벼운
6
+ inline popover로 보여준다 — 트리거 옆에 바로 뜨고, 배경을 막지 않고, 확인/취소
7
+ 버튼만 있다. `docs/popover.md`가 Popover를 계약하면서 이미 이 자리를 예약해
8
+ 뒀다("ConfirmPopover는 Popover 위의 조합이지 셋째 독립 primitive가 아니다").
9
+ 이 문서는 그 한 줄 판정을 정식으로 완결한다.
10
+
11
+ ## 판정: 만들지 않는다
12
+
13
+ 핵심 질문은 브리프가 준 그대로다 — **"덜 무거운 확인이 파괴적 동작에 적절한가"**.
14
+ 답은 "아니다"이고, 그 답은 이 저장소가 이미 내린 다른 결정들에서 곧바로 따라
15
+ 나온다.
16
+
17
+ ### 파괴적 동작의 확인은 이미 AlertDialog가 완결한 문제다
18
+
19
+ `docs/architecture.md`의 "위험 확인의 생명주기"는 AlertDialog가 삭제·결제처럼
20
+ 되돌릴 수 없는 side effect를 다루는 유일한 계약이라고 이미 못박는다.
21
+ `idle → confirm → busy → success/error → closing → closed`와 `outsideDismiss:
22
+ false`, `busy 동안 모든 dismiss 무시`가 그 계약의 본체다.
23
+
24
+ Popover는 그 반대 축 위에 설계돼 있다(`popoverBehaviorDefaults`):
25
+ `outsideDismiss: true`, `escapeDismiss: true`, `focusOutDismiss: true`, 그리고
26
+ **`busy` 축 자체가 없다**(`docs/popover.md`: "Popover는 모달이 아니라 '모든
27
+ dismiss를 막는 전역 상태'가 성립하지 않는다"). 즉 Popover 안에 확인 버튼을
28
+ 넣으면:
29
+
30
+ - 사용자가 확인 버튼을 누른 순간에도 바깥 포인터·Tab 이탈·Escape가 여전히
31
+ surface를 닫을 수 있다 — 삭제 요청이 서버로 나가는 도중에 표면이 사라지는
32
+ 경쟁을 막을 방법이 계약에 없다.
33
+ - 이 경쟁을 막으려면 Popover에 `busy` 축을 추가해야 하는데, 그건 Popover를
34
+ 사실상 모달로 바꾸는 것이다 — 그 순간 AlertDialog가 이미 소유한 문제(모달
35
+ confirm 세션)를 두 번째 이름으로 다시 계약하게 된다.
36
+
37
+ `docs/dropdown.md`가 "Dropdown을 임의 콘텐츠용으로 다시 정의하면 이미 Popover가
38
+ 점유한 문제와 구분되지 않는다"고 판정한 것과 정확히 같은 형태의 충돌이다 — 여기서는
39
+ ConfirmPopover가 AlertDialog가 이미 점유한 문제(파괴적 동작의 안전한 확인)와
40
+ 구분되지 않는다.
41
+
42
+ ### 되돌릴 수 있는 동작의 "가벼운 확인"은 새 계약이 필요 없다
43
+
44
+ 그럼 "되돌릴 수 있는 동작이면 Popover 확인이 맞지 않냐"는 질문이 남는다 —
45
+ 브리프가 정확히 이 경계("되돌릴 수 있으면 Popover, 없으면 AlertDialog")를
46
+ 제안했다. 하지만 이 갈래를 따라가 봐도 **새로 계약할 축이 없다.**
47
+
48
+ 되돌릴 수 있는 동작에 필요한 전부는 "확인/취소 버튼 두 개가 있는 콘텐츠"이고,
49
+ 그건 이미 Popover의 정의 그 자체다 — `PopoverDescriptor`는 "임의의 interactive
50
+ 콘텐츠"를 담는 자리로 계약돼 있고(`docs/popover.md`), 확인/취소 버튼 두 개는
51
+ 그 임의 콘텐츠의 특수 사례일 뿐이다. 제품이 Popover의 `content` 슬롯에 버튼
52
+ 두 개를 넣고 하나가 `onOpenChange(false, ...)`를 호출하는 데에는 새로운 상태
53
+ 축도, 새로운 접근성 개념도 필요하지 않다 — Notification이 "Toast의 설정 값
54
+ 조합일 뿐 새 계약이 아니다"로 판정된 것과 같은 자리다.
55
+
56
+ 즉 "ConfirmPopover"라는 이름표가 실제로 가리킬 수 있는 두 갈래는 다음과 같다.
57
+
58
+ | 갈래 | 실제로 필요한 것 | 이미 있는가 |
59
+ | --- | --- | --- |
60
+ | 파괴적 동작의 확인 | `busy` 축을 가진 모달 confirm 세션 | **있다 — AlertDialog** |
61
+ | 되돌릴 수 있는 동작의 가벼운 확인 | 버튼 두 개가 든 non-modal popover 콘텐츠 | **있다 — 그냥 Popover의 콘텐츠 슬롯** |
62
+
63
+ 어느 쪽으로 가도 `src/confirm-popover.ts`가 소유할 독립적인 상태 축이나
64
+ 접근성 개념이 남지 않는다.
65
+
66
+ ## 만들지 않은 것
67
+
68
+ `src/confirm-popover.ts`, `test/confirm-popover.test.ts`는 없다. 제품이
69
+ "가벼운 확인"을 원하면 Popover 콘텐츠에 두 버튼을 직접 구성하고, "파괴적
70
+ 동작"이면 AlertDialog를 쓴다 — 둘 사이의 선택 기준은 이 문서와 `docs/popover.md`가
71
+ 이미 명시한 그대로다: **되돌릴 수 있으면 Popover, 없으면 AlertDialog.**
72
+
73
+ ## catalog 배선 명세 (리드 적용)
74
+
75
+ crosswalk(`component-references.ts`, `Popconfirm → ConfirmPopover`,
76
+ `relationship: "adapted"`)는 antd 범위 추적용 그대로 둔다. catalog의
77
+ `ConfirmPopover` planned row(`category: "overlay", platform: "web"`)도
78
+ 지우지 않는다 — 이름 자리를 없애자는 것이 아니라, 지금 채울 독립 계약이 없다는
79
+ 것이다. `Dropdown`/`Notification`과 같은 처리를 권한다: catalog row는 남기고,
80
+ Component Explorer에서 이 문서로 연결한다.
81
+
82
+ ## 뒤집힐 조건
83
+
84
+ 다음 중 하나가 실제로 측정되면 이 판정을 다시 연다.
85
+
86
+ 1. Popover의 `outsideDismiss`/`escapeDismiss`를 끄지 않고도 안전하게 처리할 수
87
+ 있는, 진짜로 되돌릴 수 없지만 AlertDialog의 전체 모달 무게(배경 완전 차단,
88
+ 별도 트랜지션 레이어)는 과한 액션이 실제 제품에서 발견된다 — 즉 "가볍지만
89
+ busy를 반드시 막아야 하는" 제3의 자리가 vertical slice로 증명된다.
90
+ 2. 여러 제품 화면에서 "Popover 콘텐츠에 확인/취소 버튼 두 개"라는 조합이
91
+ 반복되어, 그 조합 자체(콜백 규약, 포커스 초기값)를 매번 다시 구현하는 비용이
92
+ 측정 가능하게 커진다 — 그때는 새 상태 축이 아니라 **Popover 위의 얇은
93
+ 합성 헬퍼**(recipe 없이 콜백 배선만 감싸는 유틸리티) 형태를 먼저 검토한다.
@@ -0,0 +1,383 @@
1
+ # 교차 중복 감사 (2026-08-19)
2
+
3
+ 병렬 저작 다섯 팀이 만든 `src/*.ts`·`test/*.ts`·`docs/*.md` 전체를 대상으로, 이미
4
+ 배선된 것까지 포함해 서로 겹치거나 어긋난 자리를 찾았다. **코드는 고치지 않았다** —
5
+ 이 문서 하나만 새로 만들었다.
6
+
7
+ > **최신 상태(2026-08-20):** 아래 최초 감사의 1번(Popover), 2번(Slider), 2차 감사의
8
+ > 10번(Tour)은 모두 해결됐다. 각 항목과 오버레이 비교표는 현재 구현을 기준으로
9
+ > 갱신했으며, 최초 발견 맥락은 회귀 이유를 남기기 위해 보존한다.
10
+
11
+ ## 무엇을 어떻게 대조했는가
12
+
13
+ 1. `git status --short`로 이번 배치의 변경/신규 파일 전체 목록을 뽑았다(`src/*.ts`
14
+ 30여 개, `docs/*.md` 30여 개).
15
+ 2. **오버레이 dismiss 어휘 4벌**을 나란히 읽었다 — `src/sheet.ts`(원본, beta),
16
+ `src/side-panel.ts`, `src/command-palette.ts`, `src/popover.ts`. 각각의
17
+ `XxxDismissReason` union, `XxxOpenChangeDetails`, `XxxDismissPolicy`,
18
+ `canDismissXxx` 시그니처를 필드 단위로 비교했다.
19
+ 3. **숫자 범위 판정 3벌**을 대조했다 — `src/number-field.ts`(원본),
20
+ `src/slider.ts`, `src/splitter.ts`가 실제로 같은 함수를 import해서 쓰는지,
21
+ 아니면 각자 다시 구현했는지 import 목록과 함수 본문을 직접 읽었다.
22
+ 4. **tri-state 체크박스 집계**를 대조했다 — `src/data-table.ts`,
23
+ `src/tree-select.ts`, `src/tree.ts`, 그리고 그 바탕이 되는
24
+ `behaviors.ts`의 `CheckboxState`/`getCheckboxNextState`.
25
+ 5. **`behaviorRegistry`에 실제로 배선된 값**(`src/behaviors.ts`)을 각 모듈
26
+ 자신의 타입과 대조해, 배선 과정에서 조용히 뜻이 바뀐 자리가 있는지 확인했다.
27
+ 6. **"만들지 않는다" 판정 문서 7개**(`notification`·`dropdown`·`virtual-list`·
28
+ `context-panel`·`rating`·`time-picker`·`layout-primitives`)를 다시 읽고, 각
29
+ 문서가 근거로 든 다른 컴포넌트의 상태(`status`, 존재 여부)가 지금도 사실인지
30
+ `src/catalog.ts` 현재 값과 대조했다.
31
+
32
+ 이 여섯 갈래 밖의 파일(예: Calendar/Carousel/Timeline/Steps/Description List 등)은
33
+ 이번 감사에서 깊이 읽지 않았다 — 시간 안에 검증 가능한 근거를 남길 수 있는 범위로
34
+ 좁혔다. 이 문서가 다루지 않은 조합은 "확인 안 함"이지 "문제 없음"이 아니다.
35
+
36
+ ## 발견
37
+
38
+ ### 1. [해결 확인] Popover 트리거-오픈 사유가 공통 `"trigger"`로 통합됐다
39
+
40
+ - 현재 `PopoverOpenChangeReason`은 `"trigger" | PopoverDismissReason`이다.
41
+ `"trigger-activation"`은 Tooltip의 **재클릭 닫힘** 사유로만 남아 같은 문자열이
42
+ 열림과 닫힘을 동시에 뜻하던 충돌이 사라졌다.
43
+ - Popover의 `outside-pointer`/`outside-focus` 분리는 비모달 Tab 이탈과 포인터 이탈을
44
+ 구분하기 위한 별도 축이므로 그대로 유지한다(3번 항목).
45
+ - 남은 조치: 없음. `test/popover.test.ts`와 2차 감사 9번이 현재 어휘를 고정한다.
46
+
47
+ ### 2. [해결 확인] Slider 단일 스텝이 NumberField 판정을 직접 재사용한다
48
+
49
+ - `getSliderStepTarget`의 `"increment"`/`"decrement"` 경로는 이제
50
+ `stepNumericValue(descriptor.value, config, intent)`를 직접 호출한다.
51
+ - `"increment-page"`/`"decrement-page"`만 Slider 고유 page multiplier를 적용한 뒤
52
+ 공유 `snapToStep`으로 스냅하며, `"first"`/`"last"`는 경계값을 그대로 반환한다.
53
+ - `test/slider.test.ts`는 min offset, 소수 step, 양쪽 경계에서 Slider 단일 스텝과
54
+ `stepNumericValue` 결과가 계속 같아야 한다는 회귀표를 고정한다.
55
+
56
+ ### 3. [INFO] Popover의 `outside-pointer`/`outside-focus` 분리는 근거가 있다 — 다음 저작자를 위한 이정표만 필요
57
+
58
+ - 파일·심볼: `src/popover.ts:27-32` `PopoverDismissReason`
59
+ - 관찰: Sheet/SidePanel/CommandPalette는 전부 바깥 클릭을 `"outside"` 하나로
60
+ 부르는데 Popover만 `"outside-pointer"`/`"outside-focus"` 둘로 나눈다.
61
+ `docs/popover.md`가 이유를 명시적으로 적어 뒀다 — Popover만 비모달이라 Tab이
62
+ surface 밖으로 정당하게 나갈 수 있고, 그 키보드 이탈이 포인터 이탈과 다른
63
+ 입력 양식이라서다. **이건 어긋난 게 아니라 정당한 분화다.**
64
+ - 왜 그래도 기록하나: 다음에 비모달 오버레이(예: 향후 실제 `ContextPanel` 같은
65
+ 것)를 저작하는 사람이 이 분리를 보고 "Popover가 왜 유별난가"를 처음부터 다시
66
+ 묻지 않도록, 그리고 반대로 "Sheet의 `outside`가 표준이니 Popover를 거기 맞춰
67
+ 고쳐야 하나"라는 잘못된 통합 시도를 막기 위해서다.
68
+ - 권고: **그대로 둠.** `docs/architecture.md`의 공통 상태 축 표 근처(또는
69
+ `behaviors.ts`의 `dismiss` 필드 주석)에 "모달 오버레이는 `outside` 하나,
70
+ 비모달은 `outside-pointer`/`outside-focus`로 분리한다"는 한 줄 규칙을
71
+ 추가하면 다음 판정이 더 빨라진다 — 지금 당장 필요한 코드 변경은 없다.
72
+ - 적용 시 건드릴 파일: (선택) `docs/architecture.md` 한 줄 추가뿐.
73
+
74
+ ### 4. [INFO] CommandPalette의 `canDismiss` 시그니처가 `busy`를 받지 않는다 — 의도된 축소, 위험 낮음
75
+
76
+ - 파일·심볼: `src/command-palette.ts:133` `canDismissCommandPalette(reason, policy)`
77
+ vs `src/sheet.ts` `canDismissSheet(reason, busy, policy)`,
78
+ `src/side-panel.ts` `canDismissSidePanel(reason, busy, policy)`.
79
+ - 관찰: CommandPalette만 `busy` 인자가 없다. 모듈 자체 주석이 이유를 설명한다 —
80
+ 팔레트는 fire-and-forget이라 전역 busy 상태가 성립하지 않는다.
81
+ - 왜 심각하지 않은가: 세 함수를 인터페이스 하나로 묶어 다형적으로 호출하는
82
+ 코드는 지금 없고, 그런 코드를 짜도 TypeScript가 인자 개수 불일치를 **컴파일
83
+ 타임에** 잡는다 — "렌더러가 조용히 틀리는" 카테고리가 아니다.
84
+ - 권고: **그대로 둠.** 근거가 이미 모듈 주석에 있다.
85
+ - 적용 시 건드릴 파일: 없음.
86
+
87
+ ### 5. [INFO/양호] 공유 `BehaviorContract.dismiss` enum은 일부러 더 거칠다 — 버그 아님
88
+
89
+ - 파일·심볼: `src/behaviors.ts:1197-1202`(`popover.web.dismiss: ["escape",
90
+ "outside"]`), CommandPalette 배선(리드가 `"activation"`을 기존 `"action"`으로
91
+ 연결).
92
+ - 관찰: 각 모듈 자신의 `XxxDismissReason`은 세분화돼 있는데(`outside-pointer`/
93
+ `outside-focus`, `activation`), `behaviorRegistry`에 실제로 꽂히는
94
+ `web.dismiss`/`native.dismiss` 값은 더 거친 공용 enum(`escape|outside|
95
+ selection|blur|timeout|action|close-action|swipe|programmatic`)으로
96
+ 수렴한다.
97
+ - 왜 기록하나: 처음 보면 "배선 과정에서 세부 정보가 유실됐다"는 버그처럼 보일 수
98
+ 있다. 그런데 이건 이미 **두 번 같은 방식으로 일어난 의도된 패턴**이다 — 공용
99
+ registry 필드는 "이 컴포넌트가 대략 어떤 이벤트로 닫히는가"를 문서화하는
100
+ 요약이고, 정밀한 계약은 각 모듈 자신의 타입(`canDismissXxx`,
101
+ `XxxDismissReason`)이 유일한 source of truth이기 때문이다. 다음 사람이 이걸
102
+ 버그로 착각해 세분화된 리터럴을 공용 enum에 억지로 추가하지 않도록 남긴다.
103
+ - 권고: **그대로 둠.** 다만 이 요약↔정밀 계약의 관계 자체가 `docs/architecture.md`
104
+ 어디에도 명문화돼 있지 않다 — 문서 한 줄이 있으면 다음 감사가 이 항목을 다시
105
+ 조사할 필요가 없어진다.
106
+ - 적용 시 건드릴 파일: (선택) `docs/architecture.md` 한 줄.
107
+
108
+ ### 6. [확인함, 문제 없음] tri-state 체크박스 집계 — DataTable → TreeSelect 재사용 사슬
109
+
110
+ - 대상: `src/data-table.ts` `resolveDataTableSelectAllState`, `src/tree-select.ts`
111
+ `coverageToState`/`resolveTreeSelectAncestorStates`류.
112
+ - 확인 내용: 둘 다 새 `"none"|"some"|"all"` enum을 만들지 않고 기존
113
+ `CheckboxState`(`boolean | "mixed"`, `src/behaviors.ts`)를 그대로 반환한다.
114
+ TreeSelect는 자기 주석에서 `resolveDataTableSelectAllState`를 명시적으로
115
+ 인용하며 "disabled 행 제외" 규칙을 조상 집계로 일반화한다고 밝힌다. 활성화
116
+ 방향(mixed→checked 기본값) 컨벤션도 기존 `getCheckboxNextState`를 그대로
117
+ 따른다고 주석에 남겨 뒀다.
118
+ - 결론: 어긋남 없음. 오히려 이 저장소가 원하는 재사용 방식의 모범 사례다.
119
+
120
+ ### 7. [확인함, 문제 없음] Tree/DataTable의 선택 모델
121
+
122
+ - 대상: `src/tree.ts` `TreeSelectionModel<Id> = CollectionSelectionModel<Id>`,
123
+ `src/data-table.ts` `DataTableSelection<Key> = CollectionSelectionModel<Key>`.
124
+ - 확인 내용: 둘 다 `behaviors.ts`의 `CollectionSelectionModel`을 별칭으로만
125
+ 다시 내보낼 뿐 새 선택 타입을 만들지 않는다.
126
+ - 결론: 어긋남 없음.
127
+
128
+ ### 8. [확인함, 문제 없음] "만들지 않는다" 판정 7개의 전제 재검증
129
+
130
+ - 대상: `docs/notification.md`, `docs/dropdown.md`, `docs/virtual-list.md`,
131
+ `docs/context-panel.md`, `docs/rating.md`, `docs/time-picker.md`,
132
+ `docs/layout-primitives.md`.
133
+ - 확인 내용: 각 문서가 근거로 인용한 다른 컴포넌트의 상태(`Select`/`Menu`/
134
+ `Statistic`/`Slider`는 beta, `SidePanel`/`Sheet`는 계약 완료, `Popover`는
135
+ `planned`+recipe/behavior 배선됨)를 `src/catalog.ts` 현재 값과 대조했다. 전부
136
+ 일치한다 — 예를 들어 `context-panel.md`가 "방금 완성된 SidePanel"을 근거로
137
+ 드는데 SidePanel은 실제로 이번 배치에서 완성·배선됐고, `rating.md`가 인용하는
138
+ Slider의 `validateNumericRangeConfig`/`snapToStep` 소수 step 지원도 실제
139
+ `src/number-field.ts` 구현과 일치한다.
140
+ - 결론: 이번 감사 시점 기준으로 낡은 전제를 찾지 못했다. (리드가 언급한, 브리핑
141
+ 전제가 커밋 하나로 낡았던 사례는 이 일곱 문서 중에는 없었다 — 다른 곳에서
142
+ 일어난 것으로 보인다.)
143
+
144
+ ## 감사하지 않은 것 (범위 고백)
145
+
146
+ - Calendar, Carousel, Timeline, Steps, DescriptionList, DatePicker, OtpField,
147
+ PasswordField, Mentions, Image, DesignSystemProvider, Form, Watermark,
148
+ QRCode, Anchor, Affix — 이번 감사에서 다른 모듈과의 필드 단위 대조를 하지
149
+ 않았다. 이름이 겹치는 자리(예: Calendar/DatePicker의 날짜 값 표현이 서로
150
+ 같은 문자열 포맷을 쓰는지)는 다음 감사 대상으로 남긴다.
151
+ - `catalog.ts`/`behaviors.ts`/`recipes.ts`/`index.ts`의 배선 자체가 각 모듈의
152
+ 타입과 100% 일치하는지는 typecheck가 이미 담보한다(현재 0 errors) — 이 문서는
153
+ 타입은 맞지만 **의미가 갈리는** 자리만 따로 찾은 것이다.
154
+
155
+ ## 1차 게이트
156
+
157
+ 코드를 고치지 않았으므로 `pnpm typecheck && pnpm test`는 감사 시작 전과 동일한
158
+ 통과 상태(0 errors / 531 passed)다. 이 문서(`docs/consistency-audit.md`) 한 파일만
159
+ 추가했다.
160
+
161
+ ---
162
+
163
+ # 2차 감사 — 1차가 고백한 범위 (2026-08-19)
164
+
165
+ 리드가 1차 MEDIUM(Popover `trigger-activation`)을 적용했고, 적용 과정에서 **1차보다
166
+ 나쁜 사례**를 확인했다 — `src/tooltip.ts:10`의 `"trigger-activation"`은 **닫는**
167
+ 사유(트리거를 다시 눌러 이미 열린 tooltip을 닫는다)인데 Popover는 **여는** 사유로
168
+ 같은 문자열을 썼다. 1차가 오버레이 4벌(Sheet/SidePanel/CommandPalette/Popover)로
169
+ 대조 범위를 좁혔던 탓에 Tooltip이 빠졌다. 이번엔 그 교훈을 반영해 **의심 문자열은
170
+ 파일 몇 개가 아니라 `src/` 전체를 grep**했다.
171
+
172
+ ## 방법 (1차와 달라진 점)
173
+
174
+ 1. `git status --short`로 디스크를 다시 확인했다 — 1차 이후 `floating-action-button.ts`,
175
+ `transfer-list.ts`, `tour.ts`가 새로 들어와 있었다(1차 시점엔 없었다).
176
+ 2. `OpenState|DismissReason|OpenChangeDetail`를 **`src/*.ts` 전체**에서 grep해
177
+ 오버레이류 모듈을 다시 전수 조사했다 — 1차가 놓쳤던 `alert-dialog.ts`,
178
+ `tooltip.ts`, `toast.ts`, `date-picker.ts`, `collection.ts`(Select 원본)까지
179
+ 포함해 총 11개 모듈의 "여는/닫는 사유" 어휘를 한 표로 모았다.
180
+ 3. `"ltr"` 리터럴을 **`src/` 전체**에서 grep해 `docs/design-system-provider.md`가
181
+ 주장한 6곳(및 이번에 새로 생긴 파일들)과 대조했다.
182
+ 4. `fontScale`/`textScale`을 전체 grep해 세 번째 갈래가 더 있는지 확인했다.
183
+ 5. `Compose.*AccessibleName` 타입 선언을 전체 grep해 패턴이 실제로 같은 모양인지
184
+ (info 객체 하나 → string) 시그니처까지 읽어서 확인했다.
185
+ 6. `date-picker.ts`가 `calendar.ts`의 ISO 날짜 검증기를 실제로 import하는지
186
+ import 목록을 직접 읽었다.
187
+ 7. `transfer-list.ts`/`mentions.ts`의 실패했던 테스트를 다시 돌려 지금 상태를
188
+ 확인했다(감사 중 다른 저작자가 고쳐 지금은 전체 617건 통과).
189
+
190
+ ## 발견
191
+
192
+ ### 9. [해결 확인] Tooltip↔Popover의 `"trigger-activation"` 충돌은 리드의 수정으로 완전히 사라졌다
193
+
194
+ - 파일·심볼: `src/tooltip.ts:10` `TooltipOpenChangeReason`의 `"trigger-activation"`
195
+ (트리거 재클릭으로 **닫힘**), 수정 전 `src/popover.ts`의 `"trigger-activation"`
196
+ (트리거로 **열림**).
197
+ - 확인 내용: 리드가 1차 MEDIUM 권고대로 Popover를 `"trigger"`로 바꾼 뒤,
198
+ `"trigger-activation"` 문자열을 쓰는 곳은 `src/tooltip.ts` **하나만** 남았다 —
199
+ `grep -rn '"trigger-activation"' src/*.ts`로 재확인했다. 즉 1차가 "이름은 같은데
200
+ 뜻이 다른" 위험을 지적했고 실제로는 **그 위험이 이미 실현돼 있던 것**이었는데,
201
+ 권고한 수정이 부작용 없이 둘 다 해소했다.
202
+ - 남은 위험: 없음. Tooltip 자신의 `"trigger-activation"`(닫힘)은 이제 유일한
203
+ 용례이고, `docs/tooltip.md`가 이미 이 의미를 문서화하고 있다.
204
+ - 권고: 없음(참고용 기록).
205
+
206
+ ### 10. [해결 확인] Tour 열림 사유도 공통 `"trigger"`를 사용한다
207
+
208
+ - `TourOpenReason = "trigger" | TourCloseReason`이 추가됐고
209
+ `TourOpenChangeDetail.reason`은 이 전체 union을 사용한다. 타입 이름과 실제 값 목록이
210
+ 다시 일치한다.
211
+ - `docs/tour.md`와 `test/tour.test.ts`가 트리거로 여는 경로를 기록한다. Tour도 Sheet,
212
+ SidePanel, CommandPalette, Popover, Select, DatePicker, AlertDialog와 같은 열림 어휘를
213
+ 사용하므로 renderer 공용 로깅에서 예외 분기가 필요 없다.
214
+ - 남은 조치: 없음.
215
+
216
+ ### 11. [확인함, 정확함] DesignSystemProvider의 direction 이관 목록 — 6곳 전부 맞고 빠진 곳 없음
217
+
218
+ - 대상: `docs/design-system-provider.md`가 주장한 `BottomNavigationDirection`,
219
+ `TabsDirection`, `SelectionDirection`, `IconDirection`, `ShowcaseDirection`,
220
+ `calendar.ts` 인라인 파라미터.
221
+ - 확인 내용: `grep -rn '"ltr"' src/*.ts`로 전체를 다시 훑었다. 정확히 이 6곳뿐이고
222
+ 일곱 번째 자리는 없다. `SidePanelEdge`/`FilePicker`/`Tour`/`Splitter`/`Layout`
223
+ 등이 쓰는 `"start"|"end"`는 **다른(보완적인) 축**이라 겹치지 않는다 — 그건
224
+ 물리적 좌우가 아니라 논리적 시작/끝이고, `ltr`/`rtl`이 있어야 좌우로 해소되는
225
+ 하류 값이다.
226
+ - 결론: 이관 대상 목록이 정확하다. 이번 저작자 다섯이 만든 새 파일 중에도
227
+ `"ltr"`/`"rtl"`을 새로 선언한 곳은 없었다(1차 이후 늘어난 `tour.ts`/
228
+ `transfer-list.ts`/`floating-action-button.ts` 포함, 셋 다 방향 축이 없다).
229
+
230
+ ### 12. [확인함, 정확함] textScale/fontScale 두 갈래도 정확히 두 곳뿐
231
+
232
+ - 대상: `docs/design-system-provider.md`가 주장한 `description-list.ts`의
233
+ `fontScale`(연속값)과 `showcase.ts`의 `ShowcaseTextScale`(닫힌 `1|1.5|2`).
234
+ - 확인 내용: `grep -rn "Scale\b|fontScale|textScale" src/*.ts`로 전체를 훑었다.
235
+ 세 번째 갈래는 없었다.
236
+ - 결론: 정확함.
237
+
238
+ ### 13. [확인함, 문제없음] 오버레이류 11개의 열림/닫힘 사유 전수 비교표
239
+
240
+ | 모듈 | 열림 사유 | 닫힘/dismiss 사유 |
241
+ | --- | --- | --- |
242
+ | Sheet | `trigger` | `close-action`·`escape`·`back`·`outside`·`swipe`·`programmatic` |
243
+ | SidePanel | `trigger` | `close-action`·`escape`·`outside`·`programmatic` |
244
+ | CommandPalette | `trigger` | `close-action`·`outside`·`escape`·`activation`·`programmatic` |
245
+ | Popover(수정 후) | `trigger` | `close-action`·`outside-pointer`·`outside-focus`·`escape`·`programmatic` |
246
+ | Select | `trigger` | `keyboard`·`selection`·`escape`·`outside`·`blur`·`programmatic` |
247
+ | DatePicker | `trigger` | (Select와 같은 계열, `collection.ts` 재사용) |
248
+ | AlertDialog | `trigger` | `confirm`·`cancel`류(`AlertDialogCancelReason`) |
249
+ | Tooltip | `pointer`·`focus` | `pointer-leave`·`blur`·`escape`·`trigger-activation`(재클릭 닫힘)·`another-tooltip` |
250
+ | Toast | 없음(트리거 앵커가 없는 큐 모델) | `ToastDismissReason`(별도 체계, 오버레이 anchor 개념 자체가 없어 비교 대상 아님) |
251
+ | Tour | `trigger` | `skip`·`escape`·`complete`·`programmatic`·`interrupted` |
252
+ | Menu | (behaviorRegistry 문자열, 자체 파일 없음) | `escape`·`outside`·`selection` |
253
+
254
+ 결론: `programmatic`(controlled owner의 강제 닫힘은 항상 허용)과 `escape`는 전
255
+ 모듈이 같은 뜻으로 일관되게 쓴다. `close-action`(명시적 닫기 버튼)도 일관된다.
256
+ Tooltip/Toast는 애초에 다른 문제(hover 트리거 없음/큐 모델)라 다른 어휘를 쓰는
257
+ 것이 맞다 — 강제로 맞추면 오히려 `docs/tooltip.md`/`docs/toast.md`가 이미 세운
258
+ 경계를 허문다.
259
+
260
+ ### 14. [확인함, 문제없음] ComposeXAccessibleName 패턴 — 7개 모듈이 독립적으로 같은 모양에 수렴
261
+
262
+ - 대상: `Calendar`, `Carousel`, `Pagination`, `PasswordField`, `Timeline`, `Steps`,
263
+ `Tree`의 `Compose*AccessibleName` 타입 7개.
264
+ - 확인 내용: 전부 `(info: <X>AccessibleNameInfo) => string` 모양이다 — 인자
265
+ 개수·반환 타입까지 정확히 일치한다. 서로 다른 저작자가 각자 만들었는데도
266
+ "제품이 포맷한 문자열을 받는다"는 원칙이 같은 함수 시그니처로 수렴했다.
267
+ - 결론: 어긋남 없음 — 오히려 이 저장소의 관례가 잘 전파된 증거.
268
+
269
+ ### 15. [확인함, 문제없음] DatePicker는 Calendar의 ISO 날짜 검증을 재사용한다
270
+
271
+ - 대상: `src/date-picker.ts`의 import 목록.
272
+ - 확인 내용: `assertIsoCalendarDate`, `validateCalendarGridDescriptor`,
273
+ `resolveCalendarGridDescriptor` 등을 `./calendar.js`에서 직접 가져와 쓴다 — 날짜
274
+ 문자열 검증을 다시 짜지 않았다.
275
+ - 결론: 어긋남 없음.
276
+
277
+ ### 16. [확인함, 문제없음] TransferList의 tri-state/재조정 재사용
278
+
279
+ - 대상: `src/transfer-list.ts`.
280
+ - 확인 내용: `getCheckboxNextState`, `toggleCheckboxSelection`,
281
+ `reconcileCheckboxSelection`(전부 `behaviors.ts` 기존 함수)을 그대로 가져다
282
+ 쓰고, `resolveTransferListSelectAllState`는 DataTable의
283
+ `resolveDataTableSelectAllState` 패턴(disabled 제외, `CheckboxState` 반환)을
284
+ 주석으로 직접 인용하며 패널 단위로 일반화한다.
285
+ - 결론: 어긋남 없음 — 1차의 6번 항목과 같은 계열의 모범 사례.
286
+
287
+ ## 감사하지 않은 것 (2차 범위 고백)
288
+
289
+ 1차가 고백한 16개 중 이번에 **직접 읽은 것**: `Calendar`(날짜 검증만),
290
+ `DatePicker`, `DesignSystemProvider`, `Timeline`/`Steps`/`PasswordField`(accessible
291
+ name 시그니처만), `TransferList`(전체). **여전히 필드 단위로 못 본 것**:
292
+ `Carousel`(accessible name 시그니처만 확인, 나머지 로직 미확인), `OtpField`,
293
+ `Mentions`, `Image`, `Form`, `Watermark`, `QRCode`, `Anchor`, `Affix`, `Cascader`
294
+ (문서만 존재), `ColorPicker`(문서만 존재). 이번에 새로 생긴
295
+ `FloatingActionButton`도 `docs/tour.md`의 "이 담당분의 다른 둘(FloatingActionButton,
296
+ ConfirmPopover)과 달리"라는 문구가 FAB를 불필요하다고 판정한 것처럼 읽힐 수도
297
+ 있어 대조해 봤지만, `floating-action-button.md`가 BottomCTA와의 경계를 별도로
298
+ 분명히 근거를 들어 계약했고 그 판정이 서로 모순되는지는 문구가 같은 저작 배치를
299
+ 가리키는 것인지 실제 판정 충돌인지 불확실해 **확정 짓지 못했다** — 이건 발견이
300
+ 아니라 미해결로 남긴다.
301
+
302
+ ## 2차 게이트
303
+
304
+ 코드는 여전히 고치지 않았다 — `docs/consistency-audit.md`에 이 절만 추가했다.
305
+ `pnpm typecheck && pnpm test`는 617 passed / 0 errors로, 감사 시작 전과 동일하다
306
+ (감사 중 다른 저작자가 `mentions`/`transfer-list`의 진행 중 실패를 스스로
307
+ 고쳤다 — 이 문서가 그걸 고친 것은 아니다).
308
+
309
+ ---
310
+
311
+ # 3차 — 문서가 적어 둔 실사용처가 실물과 다른 문제 (2026-08-19)
312
+
313
+ 리드가 다른 저작자의 승격 실측 과정에서 문서 넷(`steps.md`, `breadcrumb.md`,
314
+ `description-list.md`, `image.md`)이 존재하지 않는 실사용처를 근거로 들고 있다는
315
+ 보고를 받아 전달했다. `/Users/jimin/Desktop/yajalal`과 `/Users/jimin/Desktop/BurnTok`을
316
+ 직접 읽어 하나씩 검증하고, 어긋난 넷을 고쳤다(`src`/`test`는 여전히 손대지 않았다).
317
+
318
+ ## 검증 결과
319
+
320
+ - **`steps.md`**: `OnboardingScreen.tsx`를 직접 읽었다 — 지금도 `AppProgress`(연속
321
+ 진행바, `value={currentStepIndex + 1}` + `valueText="N / M"`)를 그대로 쓰고
322
+ 있다. "AppProgress 대체"라는 문구가 이미 결정된 교체 계획처럼 읽혔지만 그런 계획은
323
+ 코드 어디에도 없다 — 화면 모양 자체는 Steps 후보로 여전히 타당하지만(이름 있는
324
+ 단계·뒤로 가기 버튼), "대체 예정"이 아니라 "지금은 진행바로 풀려 있다"고 정정했다.
325
+ - **`breadcrumb.md`**: Breadcrumb는 `platform: "web"`인데 인용된 "야잘알 구단 상세 →
326
+ 선수단 → 선수 상세"는 애초에 Web 화면이 아니다 — 야잘알은 Flutter(`modules/app`)와
327
+ React Native(`modules/app-rn`) 모바일 앱뿐이고 Web 모듈 자체가 없다. BurnTok
328
+ Web(`apps/web/src/app`)의 실제 라우트 트리도 확인했지만 지금은 대부분 2단
329
+ 이하(`/c/[id]`, `/ideas/[id]`, `/messages/[peerId]`, `/u/[id]`)라 3단 이상 후보를
330
+ 찾지 못했다 — "아직 없음"으로 정정하고 확인한 근거(라우트 목록)를 남겼다.
331
+ - **`description-list.md`**: `FaCenterScreen.tsx`(FA 등급)와 `PlayerScreen.tsx`(선수
332
+ 프로필) 둘 다 직접 읽었다 — 실제로 `AppList`/`AppListRow`(이미 beta)로 풀려 있고
333
+ `columns: 1|2` grid 모양이 아니다. 두 후보 모두 DescriptionList의 계약과 안 맞아
334
+ "아직 없음"으로 정정했다.
335
+ - **`image.md`**: "선수 프로필 사진"과 "FA 등급 차트 이미지"를 코드베이스 전체에서
336
+ 검색했지만 존재하지 않는다. 가장 가까운 실사용(`TeamMark.tsx`의 팀 엠블럼)은 이니셜
337
+ 폴백이 붙은 Avatar 성격의 조합이라 Image가 계약하는 "네트워크 로드 실패/404 자산"
338
+ 문제와 결이 다르다. BurnTok Web의 아이콘도 확인했지만 data URI SVG라 네트워크 로드
339
+ 실패 케이스 자체가 없다. "아직 없음"으로 정정했다.
340
+
341
+ `docs/carousel.md`의 "야잘알 홈의 내 구단 경기 스트립"도 대조군으로 재확인했다 —
342
+ `HomeMatchHero.tsx`가 실제로 `CAROUSEL_GAP`/`CAROUSEL_PEEK` 상수와 함께 여러 경기
343
+ 카드를 가로로 스냅 스크롤하는 코드를 갖고 있어, 이름은 다르지만 내용은 정확했다.
344
+ 리드가 "올바른 형식"으로 지목한 문서가 실제로도 맞다는 것을 확인했다.
345
+
346
+ ## 왜 이런 일이 생기는가
347
+
348
+ 네 문서 모두 로드맵의 기록 형식(`문제 → 일반화한 계약 → HJM 기본값 → 플랫폼 번역 →
349
+ 검증 화면`)에서 **"검증 화면"란 자체는 비워 두지 않았다** — `steps.md`/
350
+ `breadcrumb.md`/`description-list.md`/`image.md` 넷 다 "아직 없음"이라는 정직한
351
+ 문장으로 시작했다. 문제는 그 뒤에 붙는 **"유력 후보"** 한 줄이다. `docs/carousel.md`
352
+ 같은 좋은 선례가 이미 "아직 없음 + 구체적 후보 하나"라는 형식을 보여주고 있어서,
353
+ 후속 저작자들이 그 형식을 그대로 따라 쓰되 후보를 **상상해서 채운 뒤 실제 코드로
354
+ 확인하지 않았다** — `pnpm typecheck && pnpm test` 게이트는 이 문장이 가리키는
355
+ 파일/화면이 실제로 존재하는지 검사할 방법이 전혀 없어서, 잘못 채운 후보가 그대로 다음
356
+ 저작자의 승격 실측 단계까지 통과했다.
357
+
358
+ 즉 구조적 압력은 "검증 화면 항목을 비워 두면 안 될 것 같다"가 아니라, **"아직
359
+ 없음"이라는 정직한 한 줄 뒤에 구체적인 후보 이름까지 붙이는 것이 이 저장소의 좋은
360
+ 스타일로 이미 자리 잡았는데, 그 구체성 자체를 검증할 게이트가 없다**는 것이다.
361
+ `docs/notification.md`/`docs/dropdown.md`/`docs/virtual-list.md`처럼 "만들지 않는다"
362
+ 판정 문서는 판정 근거가 전부 **이 저장소 안의 다른 코드**(catalog, crosswalk, 다른
363
+ recipe)라서 typecheck/grep으로 재검증이 쉽지만, "검증 화면 후보"는 근거가 **이 저장소
364
+ 밖의 제품 코드**(yajalal, BurnTok)라서 이 저장소의 게이트가 원천적으로 닿지 못한다 —
365
+ 같은 실수(근거를 확인하지 않고 적음)라도 위치에 따라 걸리는 그물이 다르다.
366
+
367
+ ## 고칠 자리 제안
368
+
369
+ 로드맵의 기록 형식에 "검증 화면"란을 채울 때의 규칙을 한 줄 추가하는 것을 제안한다 —
370
+ 예: "구체적인 화면/컴포넌트 이름을 후보로 들 때는 실제 코드에서 확인한 뒤에만 적는다.
371
+ 확인하지 못했다면 '아직 없음'에서 멈추고 후보를 지어내지 않는다." 이건 이 저장소
372
+ 자체(`docs/`)에서 고칠 수 있는 문구라 `src`/`test` 금지 규칙과 충돌하지 않는다 — 다만
373
+ 로드맵 문서 자체를 고칠지는 리드 판단으로 남긴다.
374
+
375
+ ## 3차 산출물
376
+
377
+ `docs/steps.md`, `docs/breadcrumb.md`, `docs/description-list.md`, `docs/image.md`의
378
+ "검증 화면" 절을 직접 수정했다(문서 파일은 이번 담당에서 허용됨). `src`/`test`는
379
+ 손대지 않았다.
380
+
381
+ ## 3차 게이트
382
+
383
+ 문서만 고쳤으므로 `pnpm typecheck && pnpm test`는 그대로다.