@hjmds/design-contracts 1.26.1 → 1.27.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 (278) hide show
  1. package/dist/agreement.d.ts +1 -0
  2. package/dist/agreement.d.ts.map +1 -1
  3. package/dist/agreement.js +2 -1
  4. package/dist/agreement.js.map +1 -1
  5. package/dist/auth-screen.d.ts +1 -1
  6. package/dist/behaviors.d.ts +1 -1
  7. package/dist/calendar.d.ts +1 -0
  8. package/dist/calendar.d.ts.map +1 -1
  9. package/dist/calendar.js +4 -1
  10. package/dist/calendar.js.map +1 -1
  11. package/dist/catalog.d.ts +78 -30
  12. package/dist/catalog.d.ts.map +1 -1
  13. package/dist/catalog.js +10 -5
  14. package/dist/catalog.js.map +1 -1
  15. package/dist/chart.d.ts +2 -0
  16. package/dist/chart.d.ts.map +1 -1
  17. package/dist/chart.js +1 -1
  18. package/dist/chart.js.map +1 -1
  19. package/dist/component-definitions.d.ts +1 -0
  20. package/dist/component-definitions.d.ts.map +1 -1
  21. package/dist/component-definitions.js +1 -0
  22. package/dist/component-definitions.js.map +1 -1
  23. package/dist/component-recipes.d.ts +53 -6
  24. package/dist/component-recipes.d.ts.map +1 -1
  25. package/dist/component-recipes.js +64 -10
  26. package/dist/component-recipes.js.map +1 -1
  27. package/dist/data-table.d.ts +11 -0
  28. package/dist/data-table.d.ts.map +1 -1
  29. package/dist/data-table.js +5 -0
  30. package/dist/data-table.js.map +1 -1
  31. package/dist/description-list.d.ts +3 -0
  32. package/dist/description-list.d.ts.map +1 -1
  33. package/dist/description-list.js +5 -1
  34. package/dist/description-list.js.map +1 -1
  35. package/dist/design-profile-layout.d.ts +28 -0
  36. package/dist/design-profile-layout.d.ts.map +1 -1
  37. package/dist/design-profile-layout.js +27 -3
  38. package/dist/design-profile-layout.js.map +1 -1
  39. package/dist/design-profile.d.ts.map +1 -1
  40. package/dist/design-profile.js +40 -8
  41. package/dist/design-profile.js.map +1 -1
  42. package/dist/field-group.d.ts +13 -0
  43. package/dist/field-group.d.ts.map +1 -1
  44. package/dist/field-group.js +17 -2
  45. package/dist/field-group.js.map +1 -1
  46. package/dist/foundations.d.ts +24 -11
  47. package/dist/foundations.d.ts.map +1 -1
  48. package/dist/foundations.js +40 -7
  49. package/dist/foundations.js.map +1 -1
  50. package/dist/heading.d.ts +5 -5
  51. package/dist/image-choice.d.ts +23 -0
  52. package/dist/image-choice.d.ts.map +1 -0
  53. package/dist/image-choice.js +26 -0
  54. package/dist/image-choice.js.map +1 -0
  55. package/dist/index.d.ts +1 -1
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +1 -1
  58. package/dist/index.js.map +1 -1
  59. package/dist/masonry.d.ts +8 -0
  60. package/dist/masonry.d.ts.map +1 -1
  61. package/dist/masonry.js +12 -1
  62. package/dist/masonry.js.map +1 -1
  63. package/dist/number-field.d.ts +2 -2
  64. package/dist/number-field.d.ts.map +1 -1
  65. package/dist/number-field.js +4 -2
  66. package/dist/number-field.js.map +1 -1
  67. package/dist/qr-code-recipe.d.ts +1 -0
  68. package/dist/qr-code-recipe.d.ts.map +1 -1
  69. package/dist/qr-code-recipe.js +3 -1
  70. package/dist/qr-code-recipe.js.map +1 -1
  71. package/dist/recipes.d.ts +1 -1
  72. package/dist/recipes.d.ts.map +1 -1
  73. package/dist/recipes.js +1 -1
  74. package/dist/recipes.js.map +1 -1
  75. package/dist/result.d.ts +9 -0
  76. package/dist/result.d.ts.map +1 -1
  77. package/dist/result.js +5 -0
  78. package/dist/result.js.map +1 -1
  79. package/dist/screen-patterns.d.ts +139 -2
  80. package/dist/screen-patterns.d.ts.map +1 -1
  81. package/dist/screen-patterns.js +140 -1
  82. package/dist/screen-patterns.js.map +1 -1
  83. package/dist/sheet.d.ts +15 -1
  84. package/dist/sheet.d.ts.map +1 -1
  85. package/dist/sheet.js +20 -1
  86. package/dist/sheet.js.map +1 -1
  87. package/dist/showcase.d.ts +27 -27
  88. package/dist/showcase.js +27 -27
  89. package/dist/showcase.js.map +1 -1
  90. package/dist/tag.d.ts +10 -2
  91. package/dist/tag.d.ts.map +1 -1
  92. package/dist/tag.js +3 -2
  93. package/dist/tag.js.map +1 -1
  94. package/dist/tags-input.d.ts +6 -0
  95. package/dist/tags-input.d.ts.map +1 -1
  96. package/dist/tags-input.js +3 -1
  97. package/dist/tags-input.js.map +1 -1
  98. package/dist/text-formats.d.ts +2 -2
  99. package/dist/top.d.ts +2 -2
  100. package/dist/transfer-list.d.ts +1 -1
  101. package/dist/transfer-list.d.ts.map +1 -1
  102. package/dist/transfer-list.js +2 -1
  103. package/dist/transfer-list.js.map +1 -1
  104. package/dist/upload-item.d.ts +1 -10
  105. package/dist/upload-item.d.ts.map +1 -1
  106. package/dist/upload-item.js +1 -2
  107. package/dist/upload-item.js.map +1 -1
  108. package/dist/version.d.ts +1 -1
  109. package/dist/version.js +1 -1
  110. package/dist/version.js.map +1 -1
  111. package/docs/activity-chip.md +4 -3
  112. package/docs/catalog-freeze.json +5 -2
  113. package/docs/chart.md +1 -1
  114. package/docs/consumer-policy.md +13 -6
  115. package/docs/description-list.md +4 -0
  116. package/docs/design-profile.md +28 -2
  117. package/docs/font-roles.md +1 -1
  118. package/docs/generated/component-maturity.md +5 -4
  119. package/docs/generated/renderer-evidence.json +92 -6
  120. package/docs/generated/renderer-evidence.md +5 -3
  121. package/docs/generated/showcase-manifest.json +49 -1
  122. package/docs/gravity-letters.md +7 -2
  123. package/docs/masonry.md +4 -2
  124. package/docs/screen-patterns.md +5 -2
  125. package/docs/showcase.md +7 -9
  126. package/docs/stable-promotion.md +13 -1
  127. package/docs/usage/README.md +40 -10
  128. package/docs/usage/SELECTION.md +30 -4
  129. package/docs/usage/STANDARD.md +11 -2
  130. package/docs/usage/components/account-switcher.md +98 -0
  131. package/docs/usage/components/activity-chip.md +4 -4
  132. package/docs/usage/components/badge.md +1 -1
  133. package/docs/usage/components/bezier-editor.md +99 -0
  134. package/docs/usage/components/bottom-cta.md +2 -1
  135. package/docs/usage/components/button.md +26 -12
  136. package/docs/usage/components/carousel.md +4 -4
  137. package/docs/usage/components/chart.md +1 -0
  138. package/docs/usage/components/chat-message.md +18 -12
  139. package/docs/usage/components/chat-screen.md +6 -3
  140. package/docs/usage/components/cohort-retention-table.md +83 -0
  141. package/docs/usage/components/collapsible.md +7 -2
  142. package/docs/usage/components/comment-thread-row.md +102 -0
  143. package/docs/usage/components/comment-thread-screen.md +27 -13
  144. package/docs/usage/components/composition-layout.md +1 -0
  145. package/docs/usage/components/context-menu.md +32 -15
  146. package/docs/usage/components/conversation-row.md +11 -4
  147. package/docs/usage/components/conversion-funnel.md +85 -0
  148. package/docs/usage/components/correlation-matrix.md +80 -0
  149. package/docs/usage/components/data-table.md +4 -0
  150. package/docs/usage/components/description-list.md +2 -1
  151. package/docs/usage/components/editor-screen.md +9 -5
  152. package/docs/usage/components/effect-surface.md +4 -2
  153. package/docs/usage/components/empty-state.md +2 -2
  154. package/docs/usage/components/event-attendance-card.md +104 -0
  155. package/docs/usage/components/expandable-search.md +105 -0
  156. package/docs/usage/components/file-picker.md +5 -0
  157. package/docs/usage/components/filter-recovery.md +97 -0
  158. package/docs/usage/components/follow-profile-card.md +100 -0
  159. package/docs/usage/components/heading.md +6 -2
  160. package/docs/usage/components/histogram-explorer.md +89 -0
  161. package/docs/usage/components/icon-button.md +18 -2
  162. package/docs/usage/components/icon-tile.md +12 -4
  163. package/docs/usage/components/image-choice-group.md +81 -0
  164. package/docs/usage/components/import-field-mapping.md +94 -0
  165. package/docs/usage/components/inline-edit.md +105 -0
  166. package/docs/usage/components/keyboard-avoidance-context.md +60 -0
  167. package/docs/usage/components/keyboard-avoiding.md +5 -1
  168. package/docs/usage/components/list-detail-screen.md +4 -4
  169. package/docs/usage/components/list.md +1 -1
  170. package/docs/usage/components/masonry.md +6 -7
  171. package/docs/usage/components/media-selection-screen.md +7 -5
  172. package/docs/usage/components/media-transport.md +104 -0
  173. package/docs/usage/components/message-composer.md +13 -3
  174. package/docs/usage/components/moderation-screen.md +5 -2
  175. package/docs/usage/components/notification-inbox-screen.md +3 -3
  176. package/docs/usage/components/notification-row.md +64 -8
  177. package/docs/usage/components/onboarding-screen.md +1 -1
  178. package/docs/usage/components/overview-screen.md +9 -3
  179. package/docs/usage/components/pagination.md +18 -3
  180. package/docs/usage/components/password-rules-field.md +90 -0
  181. package/docs/usage/components/permission-screen.md +1 -1
  182. package/docs/usage/components/photo-source-sheet.md +1 -1
  183. package/docs/usage/components/popover.md +6 -3
  184. package/docs/usage/components/pricing-choice.md +85 -0
  185. package/docs/usage/components/profile-summary.md +56 -8
  186. package/docs/usage/components/progress.md +3 -3
  187. package/docs/usage/components/qr-code.md +5 -9
  188. package/docs/usage/components/repeatable-field-group.md +92 -0
  189. package/docs/usage/components/reply-composer.md +83 -0
  190. package/docs/usage/components/reply-message.md +85 -0
  191. package/docs/usage/components/result.md +3 -3
  192. package/docs/usage/components/saved-items-screen.md +7 -5
  193. package/docs/usage/components/screen-layout.md +21 -10
  194. package/docs/usage/components/search-field.md +1 -1
  195. package/docs/usage/components/section.md +2 -2
  196. package/docs/usage/components/selection-action-bar.md +89 -0
  197. package/docs/usage/components/settings-screen.md +13 -11
  198. package/docs/usage/components/sheet.md +19 -7
  199. package/docs/usage/components/shortcut-recorder.md +77 -0
  200. package/docs/usage/components/split-action.md +94 -0
  201. package/docs/usage/components/streaming-answer.md +94 -0
  202. package/docs/usage/components/switch.md +6 -2
  203. package/docs/usage/components/table-column-settings.md +88 -0
  204. package/docs/usage/components/target-summary.md +79 -0
  205. package/docs/usage/components/text.md +11 -5
  206. package/docs/usage/components/toggle-feedback-button.md +94 -0
  207. package/docs/usage/components/top-bar.md +8 -12
  208. package/docs/usage/components/transfer-task.md +87 -0
  209. package/docs/usage/compositions/action-recovery-optimistic.md +18 -14
  210. package/docs/usage/compositions/action-recovery-save.md +18 -14
  211. package/docs/usage/compositions/action-recovery-undo.md +18 -14
  212. package/docs/usage/compositions/activity-status.md +1 -1
  213. package/docs/usage/compositions/adaptive-content.md +2 -1
  214. package/docs/usage/compositions/collection-detail.md +6 -5
  215. package/docs/usage/compositions/collection-editing.md +13 -6
  216. package/docs/usage/compositions/command-records.md +5 -4
  217. package/docs/usage/compositions/communication.md +7 -5
  218. package/docs/usage/compositions/compound-controls.md +7 -3
  219. package/docs/usage/compositions/content-transition-comparison.md +10 -5
  220. package/docs/usage/compositions/data-layouts.md +12 -16
  221. package/docs/usage/compositions/data-workflows.md +7 -4
  222. package/docs/usage/compositions/date-time-selection.md +5 -5
  223. package/docs/usage/compositions/design-profile-comparison.md +22 -6
  224. package/docs/usage/compositions/disclosure.md +14 -9
  225. package/docs/usage/compositions/document-resource.md +1 -1
  226. package/docs/usage/compositions/environment-matrix.md +16 -12
  227. package/docs/usage/compositions/expo-interactions.md +9 -8
  228. package/docs/usage/compositions/family-drawer.md +9 -5
  229. package/docs/usage/compositions/field-group.md +7 -3
  230. package/docs/usage/compositions/interaction-adapters.md +11 -9
  231. package/docs/usage/compositions/interaction-flow-apply.md +17 -15
  232. package/docs/usage/compositions/interaction-flow-search.md +11 -7
  233. package/docs/usage/compositions/live-list.md +12 -8
  234. package/docs/usage/compositions/navigation-bar-collection.md +8 -3
  235. package/docs/usage/compositions/optional-motion.md +2 -2
  236. package/docs/usage/compositions/paper-surface.md +11 -6
  237. package/docs/usage/compositions/purpose-input-comment.md +10 -4
  238. package/docs/usage/compositions/purpose-input-message.md +10 -4
  239. package/docs/usage/compositions/reference-first.md +4 -4
  240. package/docs/usage/compositions/reference-review.md +4 -4
  241. package/docs/usage/compositions/reference-settings.md +4 -4
  242. package/docs/usage/compositions/selection-motion.md +2 -1
  243. package/docs/usage/compositions/social-compositions.md +7 -6
  244. package/docs/usage/compositions/stea-event-ticket.md +2 -4
  245. package/docs/usage/compositions/stea-order-progress.md +19 -21
  246. package/docs/usage/compositions/stea-pixel-empty.md +26 -26
  247. package/docs/usage/compositions/task-controls.md +7 -2
  248. package/docs/usage/compositions/texture-comparison.md +5 -5
  249. package/docs/usage/compositions/toast-layout.md +7 -3
  250. package/docs/usage/compositions/toggle-feedback.md +1 -1
  251. package/docs/usage/compositions/visual-foundations.md +10 -5
  252. package/docs/usage/compositions/web-additions.md +5 -1
  253. package/docs/usage/screens/common-chat.md +5 -3
  254. package/docs/usage/screens/common-comments.md +14 -9
  255. package/docs/usage/screens/common-conversation-list.md +109 -0
  256. package/docs/usage/screens/common-inbox.md +5 -3
  257. package/docs/usage/screens/common-login.md +7 -4
  258. package/docs/usage/screens/common-profile.md +16 -12
  259. package/docs/usage/screens/common-saved.md +12 -8
  260. package/docs/usage/screens/common-search.md +16 -8
  261. package/docs/usage/screens/common-settings.md +12 -11
  262. package/docs/usage/screens/common-shell.md +12 -10
  263. package/docs/usage/screens/dashboard.md +59 -56
  264. package/docs/usage/screens/discovery-gallery.md +18 -9
  265. package/docs/usage/screens/flow-collection.md +10 -6
  266. package/docs/usage/screens/flow-editor.md +9 -6
  267. package/docs/usage/screens/flow-media.md +12 -7
  268. package/docs/usage/screens/flow-moderation.md +9 -3
  269. package/docs/usage/screens/flow-onboarding.md +13 -8
  270. package/docs/usage/screens/flow-permission.md +5 -3
  271. package/docs/usage/screens/landing.md +20 -14
  272. package/docs/usage/screens/mockup-studio.md +100 -88
  273. package/docs/usage/screens/notification-settings.md +9 -7
  274. package/docs/usage/screens/product-bento.md +22 -10
  275. package/docs/usage/screens/reference-comparison.md +2 -2
  276. package/docs/usage/tokens/font-roles.md +18 -2
  277. package/docs/usage/tokens/typography.md +22 -11
  278. package/package.json +7 -1
@@ -4,7 +4,7 @@
4
4
  - 상태: 배포
5
5
  - 지원: Web · Native
6
6
  - 적용: 구성 API 지침 참조
7
- - 검토일: 2026-10-15
7
+ - 검토일: 2026-10-17
8
8
  - 근거: [반복 화면 조합](../../screen-patterns.md); 공통 API와 실제 Web·Native 예제의 슬롯·상태를 대조해 중복 조립 방지. 2026-10-06 사용자 승인으로 스토리북 배포(이전 `실험/화면/공통 화면/설정`, [승인 기록](../../../../../docs/STORYBOOK_NAVIGATION.md#21-2026-10-06-전체-승격과-규격-확정))
9
9
  - 스토리북: `배포/화면/설정/앱 설정`
10
10
 
@@ -46,8 +46,6 @@ host: 남은 높이·safe area·키보드
46
46
 
47
47
  ### 즉시 저장과 일괄 저장 선택
48
48
 
49
- 2026-10-13 전수 점검에서 최초 조회와 변경 요청의 상태를 같은 골격으로 설명하던 부분을 구체화했다.
50
-
51
49
  | 상황 | 연결 | 유지·복구 |
52
50
  | --- | --- | --- |
53
51
  | 즉시 저장 | 각 Switch 행의 pending·오류 | 실패하면 마지막 확인값으로 복구하고 다른 설정은 유지한다 |
@@ -69,13 +67,11 @@ host: 남은 높이·safe area·키보드
69
67
  아래 preview의 메모리 상태를 서버 저장으로 취급하지 않는다. 예제는 읽기용이며 제품에서 showcase를 import하지 않는다.
70
68
 
71
69
  <!-- usage-example: Web ../../../../../showcase/web/src/patterns/settings-page-preview.tsx -->
72
- - [Web 실행 예제](../../../../../showcase/web/src/patterns/settings-page-preview.tsx): 공개 SettingsScreen·행·Sheet 조합과 로컬 설정 상태.
70
+ - [Web 실행 예제](https://github.com/jim1286/hjm-design-system/blob/9fccb921216a06bac2568ca8252bd0b4d7c48d95/showcase/web/src/patterns/settings-page-preview.tsx): 공개 SettingsScreen·행·Sheet 조합과 로컬 설정 상태.
73
71
  <!-- usage-example: Native ../../../../../showcase/native/src/settings-page-preview.tsx -->
74
- - [Native 실행 예제](../../../../../showcase/native/src/settings-page-preview.tsx): 같은 흐름의 Native 공개 API와 이벤트 연결.
72
+ - [Native 실행 예제](https://github.com/jim1286/hjm-design-system/blob/9fccb921216a06bac2568ca8252bd0b4d7c48d95/showcase/native/src/settings-page-preview.tsx): 같은 흐름의 Native 공개 API와 이벤트 연결.
75
73
 
76
- 설치 패키지에서 소스 링크를 열 수 없다면 [Web 원본](https://github.com/jim1286/hjm-design-system/blob/v1.17.1/showcase/web/src/patterns/settings-page-preview.tsx)과
77
- [Native 원본](https://github.com/jim1286/hjm-design-system/blob/v1.17.1/showcase/native/src/settings-page-preview.tsx)을 참고한다.
78
- 고정 버전 예제와 현재 checkout의 후속 변경은 구분하며 설치된 타입을 기준으로 사용한다.
74
+ 고정 SHA의 예제는 설치 패키지에서도 열 수 있다. 해당 예제와 현재 checkout의 후속 변경을 구분하고 설치된 공개 타입을 기준으로 사용한다.
79
75
 
80
76
  ## 큰 글자·다크·좁은 폭
81
77
 
@@ -96,11 +92,16 @@ host: 남은 높이·safe area·키보드
96
92
  ## 함정
97
93
 
98
94
  - 저장 실패는 notice로 알리고 마지막 확인값으로 복구.
99
- - 2026-10-06 인자 없이 기본과 같은 화면이던 `새 계정 기본 설정`·`선택과 프로필 수정` 스토리를 뺐다. 선택·프로필 수정은 `기본`에서 직접 조작한다.
95
+ - 선택·프로필 수정은 `기본`에서 직접 조작한다.
100
96
  - Storybook은 실제 서버·OS 권한·라우터 연동 증거가 아니다. 기본·다크·큰 글자와 실패/복구를 각각 확인한다.
101
97
 
102
- 2026-10-08 Spint 실화면 검토에서 일반 Section의 큰 제목과 상단 구분선이 설정 행보다 강하게
103
- 보였다. SettingsScreen은 화면 제목 아래의 그룹 이름을 `Text label / strong / muted`로 표시하고,
98
+ SettingsScreen은 화면 제목 아래의 그룹 이름을 `Text label / strong / muted`로 표시하고,
104
99
  그룹 간격은 `md`, 제목과 내용 간격은 `xs`로 둔다. 그룹마다 추가하던 진한 선은 제거하며,
105
100
  행 자체의 경계·터치 영역은 유지한다. 제품에서 내부 스타일을 덮거나 별도 설정 틀을 복제하지 않는다.
106
101
  Web의 의미 있는 section heading과 Native의 header 역할은 유지한다.
102
+
103
+ ## 근거
104
+
105
+ - 즉시 저장과 일괄 저장 선택 상황 표: 2026-10-13 전수 점검 — 최초 조회와 변경 요청의 상태를 같은 골격으로 설명하던 부분을 구체화했다.
106
+ - 선택·프로필 수정 별도 스토리 없음: 2026-10-06 인자 없이 기본과 같은 화면이던 `새 계정 기본 설정`·`선택과 프로필 수정` 스토리를 뺐다.
107
+ - 그룹 이름 `Text label / strong / muted`·진한 선 제거: 2026-10-08 Spint 실화면 검토 — 일반 Section의 큰 제목과 상단 구분선이 설정 행보다 강하게 보였다.
@@ -4,7 +4,7 @@
4
4
  - 상태: 배포
5
5
  - 지원: Web · Native
6
6
  - 적용: 구성 API 지침 참조
7
- - 검토일: 2026-10-15
7
+ - 검토일: 2026-10-17
8
8
  - 근거: [반복 화면 조합](../../screen-patterns.md); 공통 API와 실제 Web·Native 예제의 슬롯·상태를 대조해 중복 조립 방지. 2026-10-06 사용자 승인으로 스토리북 배포(이전 `실험/화면/공통 화면/화면 골격`, [승인 기록](../../../../../docs/STORYBOOK_NAVIGATION.md#21-2026-10-06-전체-승격과-규격-확정))
9
9
  - 스토리북: `배포/화면/화면 틀과 도구/화면 골격과 상태`
10
10
 
@@ -31,7 +31,7 @@ host: 남은 높이·safe area·키보드
31
31
 
32
32
  | 행동 | 컴포넌트·tone | 위치 | 개수·순서 |
33
33
  | --- | --- | --- | --- |
34
- | 작업 | ScreenLayout 공개 행동 슬롯 | 뒤로는 leading, 화면 도구는 actions, 주 행동은 footer | 같은 표면에 경쟁하는 primary 하나만 |
34
+ | 작업 | ScreenLayout 공개 행동 슬롯 | 뒤로는 `exit`(헤더 시작 쪽 아이콘), 화면 도구는 actions, 주 행동은 footer | 같은 표면에 경쟁하는 primary 하나만 |
35
35
  | 복구 | Button·secondary | 오류 근처 | 재시도할 대상과 범위를 표시 |
36
36
 
37
37
  ## 상태
@@ -46,8 +46,6 @@ host: 남은 높이·safe area·키보드
46
46
 
47
47
  ### 상태를 바꾸는 기준
48
48
 
49
- 2026-10-13 전수 점검에서 최초 조회와 변경 요청의 상태를 같은 골격으로 설명하던 부분을 구체화했다.
50
-
51
49
  | 상황 | 연결 | 유지·복구 |
52
50
  | --- | --- | --- |
53
51
  | 첫 조회 | 데이터가 아직 없으면 state=loading; 실패하면 state=error + stateAction | 본문의 실제 남은 영역에서 중앙 정렬한다 |
@@ -67,13 +65,12 @@ host: 남은 높이·safe area·키보드
67
65
 
68
66
  ```tsx
69
67
  // Web
70
- import { IconButton } from "@hjmds/react/actions";
71
68
  import { BottomCTA } from "@hjmds/react/bottom-cta";
72
69
  import { ScreenLayout } from "@hjmds/react/screens";
73
70
 
74
71
  <ScreenLayout
75
72
  title={t("saved.title")}
76
- leading={<IconButton label={t("common.back")} onClick={goBack}><BackGlyph /></IconButton>}
73
+ exit={{ kind: "back", label: t("common.back"), onClick: goBack }}
77
74
  state={isLoading ? { kind: "loading", title: t("saved.loading") } : { kind: "ready" }}
78
75
  footer={<BottomCTA primaryAction={{ label: t("saved.add"), onClick: add }} />}
79
76
  >
@@ -83,13 +80,12 @@ import { ScreenLayout } from "@hjmds/react/screens";
83
80
 
84
81
  ```tsx
85
82
  // Native — host가 safe area를 한 번 처리한다
86
- import { IconButton } from "@hjmds/react-native/actions";
87
83
  import { BottomCTA } from "@hjmds/react-native/bottom-cta";
88
84
  import { ScreenLayout } from "@hjmds/react-native/screens";
89
85
 
90
86
  <ScreenLayout
91
87
  title={t("saved.title")}
92
- leading={<IconButton label={t("common.back")} onPress={goBack}><BackGlyph /></IconButton>}
88
+ exit={{ kind: "back", label: t("common.back"), onPress: goBack }}
93
89
  state={isLoading ? { kind: "loading", title: t("saved.loading") } : { kind: "ready" }}
94
90
  footer={<BottomCTA primaryAction={{ label: t("saved.add"), onPress: add }} />}
95
91
  >
@@ -118,9 +114,15 @@ import { ScreenLayout } from "@hjmds/react-native/screens";
118
114
  ## 함정
119
115
 
120
116
  - 초기 로딩만 본문 교체; 갱신 실패는 notice로 본문 유지.
121
- - 이 화면에는 전송 행동이 없어 `실패와 초안 복구` 스토리를 2026-10-06 뺐다. 갱신 실패 복구는 `연결 복구`로 확인한다.
117
+ - 이 화면에는 전송 행동이 없어 `실패와 초안 복구` 스토리를 두지 않는다. 갱신 실패 복구는 `연결 복구`로 확인한다.
122
118
  - Storybook은 실제 서버·OS 권한·라우터 연동 증거가 아니다. 기본·다크·큰 글자와 실패/복구를 각각 확인한다.
123
119
 
124
120
  ### 숨긴 페이지 이름과 가운데 문맥
125
121
 
126
- 2026-10-14 야잘알에서 페이지 이름이 탭·필터와 중복되고 날짜가 왼쪽으로 밀린 사용자 피드백으로 `ScreenLayout`의 `titleVisibility="hidden"`과 `headerAlignment="center"`를 추가했다. 페이지 이름은 화면 식별값으로 유지하고 날짜·선수명 같은 실제 문맥만 표시할 수 있다. Native는 좌우 행동 영역의 큰 폭을 양쪽에 동일하게 확보해 중앙을 고정한다. `ListDetailScreen.detail`도 두 옵션을 전달한다. 기본값 visible/start는 기존 배치를 유지한다.
122
+ `ScreenLayout`의 `titleVisibility="hidden"`으로 페이지 이름을 숨기고 문맥을 가운데 둔다(헤더 기본값이 가운데). 페이지 이름은 화면 식별값으로 유지하고 날짜·선수명 같은 실제 문맥만 표시할 수 있다. Native는 좌우 행동 영역의 큰 폭을 양쪽에 동일하게 확보해 중앙을 고정한다. `ListDetailScreen.detail`도 `titleVisibility`·`headerAlignment`를 전달한다.
123
+
124
+ ## 근거
125
+
126
+ - 상태를 바꾸는 기준 상황 표: 2026-10-13 전수 점검 — 최초 조회와 변경 요청의 상태를 같은 골격으로 설명하던 부분을 구체화했다.
127
+ - `실패와 초안 복구` 스토리 없음: 2026-10-06 전송 행동이 없는 화면이라 뺐다.
128
+ - `titleVisibility="hidden"`·가운데 헤더: 2026-10-14 야잘알 사용자 피드백 — 페이지 이름이 탭·필터와 중복되고 날짜가 왼쪽으로 밀렸다.
@@ -4,8 +4,8 @@
4
4
  - 상태: 배포
5
5
  - 지원: Web · Native
6
6
  - 적용: 1.12.1
7
- - 검토일: 2026-10-15
8
- - 근거: `showcase/web/src/patterns/Dashboard.stories.tsx`, `showcase/native/src/Dashboard.stories.tsx`, `showcase/shared/dashboard-pattern.ts`, `showcase/native/src/pattern-status.tsx`, `packages/react/src/styles.css`(`.hjm-statistic`, `.hjm-text`)
7
+ - 검토일: 2026-10-17
8
+ - 근거: `showcase/web/src/patterns/Dashboard.stories.tsx`, `showcase/native/src/Dashboard.stories.tsx`, `showcase/shared/dashboard-pattern.ts`, `showcase/native/src/pattern-status.tsx`, `packages/react/src/screens.tsx`, `packages/react/src/styles.css`(`.hjm-screen`, `.hjm-statistic`)
9
9
  - 스토리북: `배포/화면/콘텐츠/대시보드`
10
10
 
11
11
  공통 구조·배치·상태·배지의 소유권은 [화면과 구성 선택](../SELECTION.md#소유권)을 따른다. 앱은 데이터·문구·브랜드·추가 기능을 공개 슬롯과 콜백으로 연결한다. 아래 코드 골격을 앱별 기본 구조 복제의 근거로 사용하지 않는다. 전용 화면 API가 없는 제품 콘텐츠는 `ScreenLayout`의 본문에 HJM 구성을 연결한다.
@@ -14,64 +14,60 @@
14
14
 
15
15
  한 기간의 개인 활동을 숫자 요약 → 날짜별 활동 → 기록 목록 순서로 돌아보는 화면이다.
16
16
  기간을 바꾸면 세 영역이 같은 데이터에서 함께 다시 계산된다. 차트·비교·목표 설정은 없다.
17
- 수치·기간·기록은 제품 데이터이며 스토리의 "예제 기록입니다" 안내는 showcase 전용이다.
17
+ 수치·기간·기록은 제품 데이터다. 스토리의 기록은 합성 예제이며 화면 본문에 예제 안내 문구를 두지 않는다.
18
18
 
19
19
  ## 영역 구조
20
20
 
21
21
  ```text
22
- 좁은 폭(Native·모바일 Web) — 전체가 하나의 세로 스크롤, 고정 영역 없음
23
- ┌ 상단 안전 영역 (화면 소유 아님: 헤더·TopBar safeAreaTop) ┐
24
- │ ScrollView 위아래 spacing.lg 20 │
25
- │ Container gutter: 폭<600 compact 16 · 이상 regular 20 │
26
- │ ① 머리 │
27
- │ 제목 Heading level3 · semanticLevel 1 │
28
- │ 소개 Text (Web as="p") │ 직계 요소 사이 spacing.xl 24
29
- │ [최근 7일] [9월] [8월] ← 기간 선택 │ SegmentedControl pills, 단일 선택
30
- │ ② "2026-09-01 — 2026-09-30" (상태 알림) │
31
- │ (오류면 여기 Notice danger + [다시 시도]) │
22
+ 좁은 폭(Native·모바일 Web) — ScreenLayout 하나, 본문만 세로 스크롤
23
+ ┌ ScreenLayout 헤더 (가운데 정렬, padding spacing.md 16) ──┐
24
+ │ 작은 순간이 쌓인 한 달 ← title │
25
+ │ 기록한 날과 머문 시간을 함께 돌아봐요. ← description │
26
+ ├ notice (좌우 16, 본문 상태가 바뀌어도 유지) ───────────────┤
27
+ │ [최근 7일] [9월] [8월] ← 기간 선택 │ SegmentedControl pills, 단일 선택
28
+ ├ 본문 (padding 16, 스크롤) ─────────────────────────────────┤
29
+ │ ② "2026-09-01 — 2026-09-30" (상태 알림) │ 직계 요소 사이 spacing.xl 24
30
+ │ (오류면 notice에 Notice danger + [다시 시도]) │
32
31
  │ ┌ ③ 요약 Surface padding lg 20 ─────────────────────────┐ │
33
- │ │ 남긴 기록 ← 라벨(muted) │ │ 라벨-값 spacing.xxs 4
34
- │ │ 7 ← 값(tabular-nums) │ │
35
- │ │ ↕ spacing.lg 20 │ │
32
+ │ │ 남긴 기록 / 7 │ │ 라벨-값 spacing.xxs 4
33
+ │ │ ↕ spacing.lg 20 │ │ 좁은 폭: Grid 1열
36
34
  │ │ 기록한 날 / 6 │ │
37
35
  │ │ ↕ 20 │ │
38
36
  │ │ 머문 시간 (분) / 125 │ │
39
37
  │ └────────────────────────────────────────────────────────┘ │
40
- │ ④ 활동 │
41
- │ "기록한 날" Text emphasis=strong │
42
- │ [날짜 목록으로 보기] ← 보기 전환(ghost) │
43
- │ ActivityHeatmap (grid ↔ list) │
38
+ │ ┌ ④ Section ─────────────────────────────────────────────┐ │
39
+ │ │ 기록한 날 [날짜 목록으로 보기] │ │ 머리 한 줄: title · action(ghost small)
40
+ │ │ ActivityHeatmap (grid ↔ list) │ │
41
+ │ └────────────────────────────────────────────────────────┘ │
44
42
  │ ⑤ 기록 List — 최신 날짜가 위 │
45
43
  │ ├ ListRow 제목 / "날짜 · N분" │
46
44
  │ └ … │
47
45
  │ (기록 0개면 ④⑤ 대신 EmptyState + [9월 보기]) │
48
- └ 아래: 고정 영역 없음, 내용이 스크롤된다 ───────────────────────┘
46
+ └ 아래: 고정 영역 없음 ─────────────────────────────────────────┘
49
47
  ```
50
48
 
51
- 넓은 폭 Web도 같은 한 열이다. [Container](../components/container.md) `size="content"`(`layout.contentMaxWidth` 1200)가
52
- 폭을 묶는다. 요약 수치를 가로로 놓는 배치는 스토리에서 확인되지 않았다.
49
+ 넓은 폭 Web도 같은 한 열이다. ScreenLayout이 `screenPatternRecipe.maxWidth`(`layout.readingMaxWidth` 720)로
50
+ 폭을 묶고 가운데에 둔다. ③ 요약은 [Grid](../components/grid.md) `columns={{ compact: 3 }}` `minColumnWidth={{ compact: 160 }}`(`dashboardStatMinWidth`)라
51
+ 세 수치가 각각 160 이상 들어가는 폭(최대 폭 720에서 Surface 안쪽 648)에서는 한 줄 3열, 좁은 폭에서는 1열로 쌓인다.
53
52
 
54
53
  | 영역 | 컴포넌트 | 위치 | 크기·간격 |
55
54
  | --- | --- | --- | --- |
56
- | 바깥 틀 | Web `main` > [Container](../components/container.md) `size="content"` `gutter` > Stack `gap="xl"`, 문서 스크롤 · Native `ScrollView` > Container `size="content"` `gutter` > Stack `gap="xl"` | 화면 전체, 세로 스크롤 하나 | 좌우 Container gutter: 폭 < 600 `compact` 16, 이상 `regular` 20([화면 여백](../tokens/layout.md)). Native ScrollView 위아래 `spacing.lg` 20(`paddingVertical`). 직계 요소 사이 `spacing.xl` 24. 위 안전 영역은 내비게이션 헤더·[TopBar](../components/top-bar.md) `safeAreaTop`이 맡고, 헤더 없이 띄울 때만 제품이 감싼다. 아래 고정 영역이 없어 inset은 스크롤로 지나간다. 입력이 없어 키보드 처리는 없다 |
57
- | ① 머리 | [Heading](../components/heading.md) `level="level3"` `semanticLevel={1}` · Text(소개) · SegmentedControl `presentation="pills"` | 맨 위 | 제목 24/32 heavy. 버튼 높이 44(`control.buttonHeight.medium`), 사이 `spacing.sm` 12 |
58
- | ② 기간 표시 | Text `role="status"`(Web) · `accessibilityLiveRegion="polite"` Text(Native) | ① 아래 | 위아래 24 |
59
- | ③ 요약 | Surface `padding="lg"` > Stack `gap="lg"` > AnimatedStatistic ×3 | ② 아래 | 안쪽 `spacing.lg` 20, 수치 사이 20, 라벨-값 `spacing.xxs` 4 |
60
- | ④ 활동 | Text `emphasis="strong"` · Button `ghost` · ActivityHeatmap `view` | ③ 아래, 세 요소가 바깥 Stack 직계 | 요소 사이 24 |
55
+ | 바깥 틀 | [ScreenLayout](../components/screen-layout.md) `title` `description` `notice` > Stack `gap="xl"` (Web `@hjmds/react/screens`, Native `@hjmds/react-native/screens`) | 화면 전체, 본문만 스크롤 | 헤더·notice·본문 좌우 `screenPatternRecipe.padding` 16, 최대 폭 720. 본문 직계 요소 사이 `spacing.xl` 24. 별도 ScrollView·Container로 감싸지 않는다. 아래 고정 영역이 없어 inset은 스크롤로 지나간다. 입력이 없어 키보드 처리는 없다 |
56
+ | ① 머리 | ScreenLayout `title`·`description` · `notice`의 SegmentedControl `presentation="pills"` | 맨 위, 헤더 아래 notice | 제목은 가운데 정렬 헤더(`screenHeaderRecipe.centeredTitleLevel`). 버튼 높이 44(`control.buttonHeight.medium`), 사이 `spacing.sm` 12. 기간 선택을 헤더 `actions`가 아닌 `notice`에 두는 이유: 가운데 헤더의 양옆 칸은 같은 폭이라 pills 세 개가 들어가면 제목 칸이 사라지고, `notice`는 본문이 loading·empty·error로 바뀌어도 남는다 |
57
+ | ② 기간 표시 | Text `role="status"`(Web) · 공개 StatusText(Native) | 본문 맨 위 | 아래 24 |
58
+ | ③ 요약 | Surface `padding="lg"` > Grid `columns={{ compact: 3 }}` `minColumnWidth={{ compact: 160 }}` `gap={{ compact: "lg" }}` > AnimatedStatistic ×3 | ② 아래 | 안쪽 `spacing.lg` 20, 수치 사이 20, 라벨-값 `spacing.xxs` 4 |
59
+ | ④ 활동 | [Section](../components/section.md) `title` · `action`(Button `ghost` `size="small"`) > ActivityHeatmap `view` | ③ 아래 | 머리 한 줄(제목 시작, 전환 버튼 끝, 사이 `spacing.sm` 12, Web 폭 600 미만·Native 큰 글자에서 세로로 쌓임). 머리-히트맵 `sectionRecipe.gap` |
61
60
  | ⑤ 기록 | List > ListRow(누름 없음) | ④ 아래 | 두 줄 행 최소 68(`layout.rowHeight.twoLine`). 기록은 최신 날짜가 위(내림차순)다. 요약·히트맵과 같은 기간 필터 결과를 뒤집어 쓴다(`dashboard-pattern.ts` `records.slice().reverse()`) |
62
61
  | 빈 상태 | EmptyState + `action` Button | ④⑤ 자리 | Web 위아래 `spacing.xxl` 32 · Native 위아래 `spacing.xxxl` 40·좌우 `spacing.xl` 24 |
63
62
 
64
- ④의 제목·토글·히트맵이 바깥 Stack 직계라 사이가 24로 벌어진다. 제목과 전환 버튼을 한 줄에 묶으려면
65
- [Section](../components/section.md)의 `title`·`action`(머리 한 줄, 사이 `spacing.sm` 12, Web 폭 600(`breakpoint.medium`) 미만에서 세로로 쌓임)을 쓴다.
66
-
67
63
  ## 버튼과 행동 위치
68
64
 
69
65
  | 행동 | 컴포넌트·tone | 위치 | 개수·순서 |
70
66
  | --- | --- | --- | --- |
71
- | 기간 선택 | SegmentedControl `presentation="pills"` | ① 소개문 아래 한 줄, 시작 정렬, wrap | 3(짧은 기간 → 긴 기간 → 과거). 기본 선택은 현재 달(스토리 `month`). 화면에 primary가 없다 — 이 화면의 주 행동은 읽기다 |
72
- | 보기 전환 | Button `ghost` | ④ 제목 아래 | 1. 라벨은 바뀔 보기를 말한다(`viewToggleKey`: grid → `dashboard.showList`, list → `dashboard.showGrid`) |
67
+ | 기간 선택 | SegmentedControl `presentation="pills"` | ScreenLayout `notice`, 시작 정렬, wrap | 3(짧은 기간 → 긴 기간 → 과거). 기본 선택은 현재 달(스토리 `month`). 화면에 primary가 없다 — 이 화면의 주 행동은 읽기다 |
68
+ | 보기 전환 | Button `ghost` `size="small"` | ④ Section `action`, 제목과 같은 줄 끝 | 1. 라벨은 바뀔 보기를 말한다(`viewToggleKey`: grid → `dashboard.showList`, list → `dashboard.showGrid`) |
73
69
  | 빈 상태 복구 | EmptyState `action` > Button(기본 primary) | EmptyState 맨 아래 가운데 | 1. 기록 있는 기간(스토리는 현재 달)으로 돌린다 |
74
- | 다시 시도 | [Notice](../components/notice.md) `action` > Button `tone="secondary" size="small"` | ② 아래 Notice 끝(Web 같은 줄 끝, Native 문구 아래) | 1. 오류일 때만 |
70
+ | 다시 시도 | [Notice](../components/notice.md) `action` > Button `tone="secondary" size="small"` | notice의 Notice 끝(Web 같은 줄 끝, Native 문구 아래) | 1. 오류일 때만 |
75
71
  | 기록 열기 | — | — | 스토리에서는 누를 수 없다. 상세가 있으면 ListRow에 `onClick`/`onPress`를 주고 [작품 탐색](discovery-gallery.md)처럼 Sheet로 연다 |
76
72
  | 파괴 행동 | — | — | 없음 |
77
73
 
@@ -81,8 +77,8 @@
81
77
  | --- | --- | --- |
82
78
  | 기본 | ①~⑤ 모두. AnimatedStatistic이 이전 값에서 새 값으로 굴러간다(모션 줄이기면 즉시) | 기간 선택·보기 전환 |
83
79
  | 로딩 | 동기 스토리에 없음. 제품 최초 조회는 ScreenLayout `state=loading`으로 본문 중앙 spinner 하나를 표시하고 라벨은 접근성 이름으로 전달한다. 재조회는 기존 본문을 유지한다 | 기간 선택 유지, 같은 요청 중복 실행 방지 |
84
- | 빈 | 동기 스토리는 수치 0과 인라인 EmptyState를 시연한다. 제품의 전체 빈 상태는 ScreenLayout `state=empty`로 본문 중앙에 두고 기간 선택은 actions에 유지한다 | 기록 있는 기간으로 |
85
- | 오류 | 스토리에 없음. ② 바로 아래 [Notice](../components/notice.md) `tone="danger"`(Native `announcement="assertive"` — 기본 `none`). 문구는 원인별 키(`errorKey`: 네트워크 `dashboard.error.network`, 서버 `dashboard.error.server`). 이전 데이터가 있으면 ③④⑤를 유지하고, 첫 로딩 실패면 ScreenLayout `state=error`와 `stateAction`으로 본문 중앙에 오류·재시도를 함께 둔다. 다시 시도가 또 실패하면 같은 Notice를 같은 자리에 두고 버튼 `loading`만 푼다(Notice를 쌓지 않는다) | Notice `action` 다시 시도 1개(`tone="secondary" size="small"`, 요청 중 `loading`) |
80
+ | 빈 | 동기 스토리는 수치 0과 인라인 EmptyState를 시연한다. 제품의 전체 빈 상태는 ScreenLayout `state=empty`로 본문 중앙에 두고 기간 선택은 `notice`에 유지한다 | 기록 있는 기간으로 |
81
+ | 오류 | 스토리에 없음. ScreenLayout `notice`에 기간 선택과 함께 [Notice](../components/notice.md) `tone="danger"`(Native `announcement="assertive"` — 기본 `none`). 문구는 원인별 키(`errorKey`: 네트워크 `dashboard.error.network`, 서버 `dashboard.error.server`). 이전 데이터가 있으면 ③④⑤를 유지하고, 첫 로딩 실패면 ScreenLayout `state=error`와 `stateAction`으로 본문 중앙에 오류·재시도를 함께 둔다. 다시 시도가 또 실패하면 같은 Notice를 같은 자리에 두고 버튼 `loading`만 푼다(Notice를 쌓지 않는다) | Notice `action` 다시 시도 1개(`tone="secondary" size="small"`, 요청 중 `loading`) |
86
82
  | 기간 변경 | ② 기간 문구가 바뀌어 읽히고 ③④⑤가 같이 바뀐다 | — |
87
83
  | 날짜 값 모름 | 히트맵 값 `null`은 `formatDay`로 "확인되지 않음"이라 읽힌다. 0과 구분해 넘긴다 | — |
88
84
 
@@ -90,10 +86,11 @@
90
86
 
91
87
  | 지침 | 쓰는 곳 |
92
88
  | --- | --- |
93
- | [Container](../components/container.md) | 바깥 틀 폭·좌우 여백 |
94
- | [Stack](../components/stack.md) | 바깥 세로 리듬, 기간 버튼 줄, 요약 안 |
95
- | [Heading](../components/heading.md) | ① 화면 제목 |
96
- | [Text](../components/text.md) | 소개·기간 표시·활동 제목 |
89
+ | [ScreenLayout](../components/screen-layout.md) | 바깥 틀·제목·소개·기간 선택 자리·최대 폭 |
90
+ | [Stack](../components/stack.md) | 본문 세로 리듬 |
91
+ | [Grid](../components/grid.md) | ③ 요약 수치 배치 |
92
+ | [Section](../components/section.md) | ④ 활동 머리(제목·보기 전환) |
93
+ | [Text](../components/text.md) | 기간 표시 |
97
94
  | [SegmentedControl](../components/segmented-control.md) | 기간 단일 선택 |
98
95
  | [Button](../components/button.md) | 보기 전환, 복구, 다시 시도 |
99
96
  | [Surface](../components/surface.md) | ③ 요약 카드 |
@@ -103,7 +100,7 @@
103
100
  | [EmptyState](../components/empty-state.md) | 기록 없음 |
104
101
  | [Skeleton](../components/skeleton.md) | 로딩 | 동기 스토리에 없음. 제품 최초 조회는 ScreenLayout `state=loading`으로 본문 중앙 spinner 하나를 표시하고 라벨은 접근성 이름으로 전달한다. 재조회는 기존 본문을 유지한다 | 기간 선택 유지, 같은 요청 중복 실행 방지 |
105
102
  | [Notice](../components/notice.md) | 오류 |
106
- | [화면 여백과 너비](../tokens/layout.md) | gutter·최대 폭 |
103
+ | [화면 여백과 너비](../tokens/layout.md) | 최대 폭 |
107
104
  | [간격](../tokens/spacing.md) | `spacing.xxs`·`sm`·`lg`·`xl` |
108
105
 
109
106
  ## 코드 골격
@@ -119,16 +116,17 @@
119
116
  import { ScreenLayout } from "@hjmds/react/screens";
120
117
  import { Button } from "@hjmds/react/actions";
121
118
  import { Notice } from "@hjmds/react/feedback";
119
+ import { Stack } from "@hjmds/react/layout";
122
120
 
123
121
  const initial = data === undefined;
124
- <ScreenLayout title={t("dashboard.title")} actions={periodControls}
122
+ <ScreenLayout title={t("dashboard.title")} description={t("dashboard.intro")}
125
123
  state={initial
126
124
  ? error ? { kind: "error", title: t(errorKey[error]) } : { kind: "loading", title: t("dashboard.loading") }
127
125
  : data.count === 0 ? { kind: "empty", title: t("dashboard.empty.title") } : { kind: "ready" }}
128
126
  stateAction={initial && error ? <Button loading={retrying} onClick={retry}>{t("common.retry")}</Button>
129
127
  : !initial && data.count === 0 ? <Button onClick={() => setPeriod("month")}>{t("dashboard.reset")}</Button> : null}
130
- notice={!initial && error ? <Notice tone="danger" title={t(errorKey[error])}
131
- action={<Button tone="secondary" loading={retrying} onClick={retry}>{t("common.retry")}</Button>} /> : null}>
128
+ notice={<Stack gap="sm">{periodControls}{!initial && error ? <Notice tone="danger" title={t(errorKey[error])}
129
+ action={<Button tone="secondary" loading={retrying} onClick={retry}>{t("common.retry")}</Button>} /> : null}</Stack>}>
132
130
  {summaryAndRecords}
133
131
  </ScreenLayout>
134
132
  ```
@@ -138,16 +136,17 @@ const initial = data === undefined;
138
136
  import { ScreenLayout } from "@hjmds/react-native/screens";
139
137
  import { Button } from "@hjmds/react-native/actions";
140
138
  import { Notice } from "@hjmds/react-native/feedback";
139
+ import { Stack } from "@hjmds/react-native/primitives";
141
140
 
142
141
  const initial = data === undefined;
143
- <ScreenLayout title={t("dashboard.title")} actions={periodControls}
142
+ <ScreenLayout title={t("dashboard.title")} description={t("dashboard.intro")}
144
143
  state={initial
145
144
  ? error ? { kind: "error", title: t(errorKey[error]) } : { kind: "loading", title: t("dashboard.loading") }
146
145
  : data.count === 0 ? { kind: "empty", title: t("dashboard.empty.title") } : { kind: "ready" }}
147
146
  stateAction={initial && error ? <Button loading={retrying} onPress={retry}>{t("common.retry")}</Button>
148
147
  : !initial && data.count === 0 ? <Button onPress={() => setPeriod("month")}>{t("dashboard.reset")}</Button> : null}
149
- notice={!initial && error ? <Notice tone="danger" announcement="assertive" title={t(errorKey[error])}
150
- action={<Button tone="secondary" loading={retrying} onPress={retry}>{t("common.retry")}</Button>} /> : null}>
148
+ notice={<Stack gap="sm">{periodControls}{!initial && error ? <Notice tone="danger" announcement="assertive" title={t(errorKey[error])}
149
+ action={<Button tone="secondary" loading={retrying} onPress={retry}>{t("common.retry")}</Button>} /> : null}</Stack>}>
151
150
  {summaryAndRecords}
152
151
  </ScreenLayout>
153
152
  ```
@@ -163,15 +162,15 @@ ScreenLayout을 감싸지 않는다. 재조회는 기존 데이터와 기간 설
163
162
  | --- | --- |
164
163
  | 큰 글자(`textScale` 2) | 기간 선택 pills는 가로 순서를 유지하며 공간이 부족하면 감긴다. Statistic 값은 Web에서 `type.title` 크기 × 글자 배율로 커지고 `overflow-wrap: anywhere`로 꺾인다. 긴 제목은 Heading이 줄바꿈한다 |
165
164
  | 다크 | 테마 토큰만 쓰므로 바꿀 것이 없다 |
166
- | 좁은 폭 | 구조 동일, gutter가 `compact` 16으로 준다. ActivityHeatmap 격자가 좁으면 보기 전환으로 목록을 쓴다 |
167
- | 넓은 폭 Web | 한 열 유지, Container `content`가 1200으로 폭을 묶고 gutter `regular` 20 |
165
+ | 좁은 폭 | 구조 동일, ③ 요약이 1열로 쌓인다. ActivityHeatmap 격자가 좁으면 보기 전환으로 목록을 쓴다 |
166
+ | 넓은 폭 Web | 한 열 유지, ScreenLayout이 720으로 폭을 묶고 ③ 요약은 3열 |
168
167
 
169
168
  ## 플랫폼 차이
170
169
 
171
170
  | 항목 | Web | Native |
172
171
  | --- | --- | --- |
173
- | 바깥 스크롤 | 문서 스크롤 | `ScrollView` 위아래 `spacing.lg` 20, 좌우는 안쪽 Container gutter |
174
- | 상단 안전 영역 | 브라우저 | 화면이 소유하지 않는다. 내비게이션 헤더 또는 [TopBar](../components/top-bar.md) `safeAreaTop`이 맡고, 헤더 없이 띄울 때만 제품이 감싼다 |
172
+ | 바깥 스크롤 | ScreenLayout 본문(`.hjm-screen__body`) | ScreenLayout 본문 ScrollView |
173
+ | 상단 안전 영역 | 브라우저 | ScreenLayout이 소유하지 않는다. host 내비게이션 또는 안전 영역 래퍼가 맡는다 |
175
174
  | 기간 표시 알림 | `role="status"` | 공개 StatusText(Android live region + iOS 변경 알림) |
176
175
  | 로딩 알림 | ScreenLayout 본문 busy와 Spinner 접근성 이름 | ScreenLayout 내부 Spinner 접근성 이름 |
177
176
  | 오류 Notice 알림 | `tone="danger"`가 알림 영역 | `announcement="assertive"`를 명시(기본 `none`) |
@@ -181,10 +180,14 @@ ScreenLayout을 감싸지 않는다. 재조회는 기존 데이터와 기간 설
181
180
 
182
181
  - ActivityHeatmap은 1~366일만 받는다(범위 밖이면 `RangeError`). 1년 넘는 기록은 제품이 기간을 나눈다.
183
182
  - 기간·결과 문구는 공개 [StatusText](../components/text.md#statustext)를 쓴다. iOS에서는 active 중 바뀐 비어 있지 않은 문구만 알리고, 최초 안내·동일 문구·배경 변경은 재생하지 않는다.
184
- - 구획 제목은 Heading으로 표시한다. 이전 Text heading 예제는 2026-10-06 제목 의미 구조를 맞추면서 수정했다.
185
- Text의 기본 `emphasis="regular"`가 굵기를 덮어 24px 보통 굵기로 나오고(`.hjm-text[data-emphasis="regular"]`), Native는 제목 단계가 없다.
186
- [Text](../components/text.md) 규칙대로 새 제품은 Heading `level3`·`semanticLevel={1}`(24/32 heavy)을 쓴다.
187
- - 예제는 양 플랫폼 모두 Container로 감싼다. 예제의 compact gutter 16과 제품이 폭에 따라 선택하는 regular gutter 20을 구분한다.
183
+ - 화면 제목은 ScreenLayout `title`, 구획 제목은 Section `title`로 둔다. 본문에 Heading·강조 Text로 제목을 다시 만들지 않는다.
184
+ - 요약 수치를 Surface 안 세로 Stack에 두면 넓은 폭에서 오른쪽이 비고, 보기 전환 Button을 바깥 Stack 직계로 두면 내용 폭 전체로 늘어난다.
185
+
186
+
187
+ 제거/복구 뒤 초점: Web 빈 기간 복구는 선택된 기간 컨트롤로 초점을 돌린다. 기간을 바꿔도 목록/격자 보기 선택은 유지한다.
188
188
 
189
+ ## 근거
189
190
 
190
- 2026-10-13 행동 검수에서 제거/복구 뒤 키보드 문맥 유실을 확인해 보강했다. Web 빈 기간 복구는 선택된 기간 컨트롤로 초점을 돌린다. 기간을 바꿔도 목록/격자 보기 선택은 유지한다.
191
+ - 구획 제목 Heading: 2026-10-06 제목 의미 구조를 맞추면서 이전 Text heading 예제를 수정했다.
192
+ - 제거/복구 뒤 초점 규칙: 2026-10-13 행동 검수에서 제거/복구 뒤 키보드 문맥 유실을 확인했다.
193
+ - ScreenLayout·Grid·Section 구조: 2026-10-17 화면 검토(S5)에서 헤더 없는 본문 Heading, Web 모바일 상단 여백 없음, 화면 맨 위 예제 안내 문구, 1170px로 늘어난 보기 전환 버튼과 세로로만 쌓인 요약을 확인해 ScreenLayout 본문·notice 기간 선택·Grid 요약·Section 머리로 바꿨다.
@@ -4,7 +4,7 @@
4
4
  - 상태: 배포
5
5
  - 지원: Web · Native
6
6
  - 적용: 1.12.1
7
- - 검토일: 2026-10-15
7
+ - 검토일: 2026-10-17
8
8
  - 근거: `showcase/web/src/patterns/DiscoveryGallery.stories.tsx`, `showcase/native/src/DiscoveryGallery.stories.tsx`, `showcase/shared/discovery-gallery.ts`, `src/base-recipes.ts`(`buttonRecipe.states.selected`), `src/grid.ts`
9
9
  - 스토리북: `배포/화면/검색/작품 탐색`
10
10
 
@@ -31,7 +31,7 @@
31
31
  │ [🔍 모바일, 작업 공간 ........... (x)] │ SearchField 최소 44
32
32
  │ ↕ spacing.xl 24 │
33
33
  │ ② [전체] [웹사이트] [모바일] [대시보드] │ SegmentedControl pills, 단일 선택
34
- │ ③ [정렬: 인기순] [저장한 작품 2] │ secondary(정렬) · ghost+selected(저장만 보기)
34
+ │ ③ [ 인기순 | 최신순 ] [✓ 저장한 작품 2] │ SegmentedControl(정렬) · Chip multiple(저장만 보기)
35
35
  │ ④ "N개의 작품" (상태 알림) │
36
36
  │ (오류면 여기 Notice danger + [다시 시도]) │
37
37
  │ ⑤ 카드 Grid │
@@ -67,7 +67,7 @@
67
67
  | 바깥 틀 | Web `main` > [Container](../components/container.md) `size="content"` `gutter` > Stack `gap="xl"`, 문서 스크롤 · Native `ScrollView` `automaticallyAdjustKeyboardInsets` `keyboardShouldPersistTaps="handled"` > Container `size="content"` `gutter` > Stack `gap="xl"` | 화면 전체, 세로 스크롤 하나 | 좌우 Container gutter: 폭 < 600 `compact` 16, 이상 `regular` 20([화면 여백](../tokens/layout.md)). Native ScrollView 위아래 `spacing.lg` 20(`paddingVertical`). 직계 요소 사이 `spacing.xl` 24. 위 안전 영역은 내비게이션 헤더·[TopBar](../components/top-bar.md) `safeAreaTop`이 맡는다. 아래 고정 영역이 없다. 키보드가 떠 있으면 Native ScrollView가 inset을 늘리고 첫 탭으로 필터·카드 버튼이 눌린다 |
68
68
  | ① 머리 | Stack `gap="md"` > Text(brand) · [Heading](../components/heading.md) `level="level3"` `semanticLevel={1}` · Text(소개) · SearchField | 맨 위(카드 배경 없음) | 요소 사이 `spacing.md` 16. SearchField는 두 플랫폼 모두 보이는 `label` |
69
69
  | ② 카테고리 | Stack `axis="inline" wrap gap="sm"` > SegmentedControl `presentation="pills"` | 머리 아래 | 높이 44, 사이 `spacing.sm` 12 |
70
- | ③ 정렬·저장 보기 | Stack `axis="inline" wrap gap="sm"` > Button `secondary` · `ghost` + `selected` | 카테고리 아래 | 사이 12 |
70
+ | ③ 정렬·저장 보기 | Stack `axis="inline" wrap gap="sm"` > SegmentedControl `size="small"`(정렬) · Chip `selectionMode="multiple"`(저장만 보기) | 카테고리 아래 | 사이 12. SegmentedControl 항목 최소 36(+hitSlop 4), Chip `small` 36 |
71
71
  | ④ 결과 수 | Text `role="status"`(Web) · `accessibilityLiveRegion="polite"` Text(Native) | 정렬 줄 아래 | — |
72
72
  | ⑤ 격자 | [Grid](../components/grid.md) `columns={{ compact: 1, medium: 2, expanded: 3 }}` `gap={{ compact: "xl" }}` `minColumnWidth={{ compact: 320 }}` | 결과 수 아래 | 열·행 사이 `spacing.xl` 24(두 플랫폼 공통). 열 수는 창 폭 class로 고르고, 본문이 좁으면 `minColumnWidth` 320이 열을 줄인다 |
73
73
  | ⑤ 카드 | `article`(Web) > Stack `gap="sm"` > 그림·Text·버튼 줄 | 격자 칸 | 그림 비율 1.6([AspectRatio](../components/aspect-ratio.md) `ratio={1.6}`), 모서리 `radius.lg` 16, 카드 안 `spacing.sm` 12 |
@@ -79,8 +79,8 @@
79
79
  | --- | --- | --- | --- |
80
80
  | 검색 | SearchField `onValueChange` | ① 맨 아래 | 1. 입력 즉시 거른다 |
81
81
  | 카테고리 | SegmentedControl `presentation="pills"` | ② | 카테고리 수, "전체"가 맨 앞 |
82
- | 정렬 전환 | Button `secondary`, 라벨에 현재 값(`t("gallery.sort", { value: t(sortKey[sort]) })`) | ③ 첫 자리 | 1. 누를 때마다 인기순 ↔ 최신순 |
83
- | 저장한 작품만 | Button `ghost` + `selected`, 라벨에 개수 | ③ 정렬 뒤 | 1 |
82
+ | 정렬 | SegmentedControl `size="small"`, `label={t("gallery.sort.label")}`, 항목 인기순·최신순(`sortKey`) | ③ 첫 자리 | 1. 하나만 고른다 |
83
+ | 저장한 작품만 | Chip `selectionMode="multiple"`(체크 역할), 라벨에 개수 | ③ 정렬 뒤 | 1. 결과를 거르는 필터라 행동 토글 Button이 아니다 |
84
84
  | 상세 보기 | Button `ghost` | 카드 맨 아래 버튼 줄 첫 자리 | 카드마다 1. `aria-label`/`accessibilityLabel`에 작품 제목 포함 |
85
85
  | 카드 저장 | Button `secondary` + `selected`(토글) | 카드 버튼 줄 둘째 자리 | 카드마다 1. 라벨 "저장" ↔ "저장됨", 접근성 이름에 제목과 저장/저장 취소 |
86
86
  | 시트 저장(시트의 주 행동) | Button 기본 primary + `selected` | ⑦ Sheet `footer`(Native 꽉 찬 폭, Web 끝 정렬) | 1. 라벨 "컬렉션에 저장" ↔ "저장 취소". 시트를 닫지 않는 토글이다 |
@@ -88,6 +88,8 @@
88
88
  | 다시 시도 | [Notice](../components/notice.md) `action` > Button `tone="secondary" size="small"` | ④ 아래 Notice 끝 | 1. 오류일 때만 |
89
89
  | 파괴 행동 | — | — | 없음(저장 취소는 같은 버튼의 토글) |
90
90
 
91
+ 정렬·저장만 보기는 선택이므로 [선택 조립 규칙](../SELECTION.md#조립-규칙필수)대로 SegmentedControl·Chip을 쓴다. 카드·시트 저장은 항목에 대한 행동이 눌림 상태를 갖는 토글이라 Button `selected`를 유지한다.
92
+
91
93
  primary는 화면에서 빈 결과 복구 하나, 시트 안에서 저장 하나다. 시트 안 행동은 그 표면 안에서 세고, `selected`를 준 버튼은
92
94
  tone과 관계없이 선택 모양(배경 `bg`, 글자 `contentBrand`, 별도 선택 테두리 없음)으로 칠해져 세지 않는다([Button](../components/button.md) "꼭 지킬 것").
93
95
  카드 안 버튼은 [보기][저장] 순서로 시작 정렬, `wrap`이다. 카드 전체를 누름 대상으로 만들지 않는다 — 카드 안에 버튼이 둘이라
@@ -111,7 +113,8 @@ tone과 관계없이 선택 모양(배경 `bg`, 글자 `contentBrand`, 별도
111
113
  | [Container](../components/container.md) | 바깥 틀 폭·좌우 여백 |
112
114
  | [Heading](../components/heading.md) | ① 화면 제목 |
113
115
  | [SearchField](../components/search-field.md) | ① 검색 |
114
- | [Button](../components/button.md) | 카테고리·정렬·저장 보기·카드 행동·시트 저장·다시 시도 |
116
+ | [SegmentedControl](../components/segmented-control.md) · [Chip](../components/chip.md) | ② 카테고리, ③ 정렬·저장만 보기 |
117
+ | [Button](../components/button.md) | 카드 행동·시트 저장·다시 시도 |
115
118
  | [Stack](../components/stack.md) | 세로 리듬, 버튼 줄, 카드 안 |
116
119
  | [Text](../components/text.md) | 머리 문구, 카드 제목·메타 |
117
120
  | [Grid](../components/grid.md) | ⑤ 카드 격자 |
@@ -204,14 +207,20 @@ ScreenLayout host는 남은 본문 높이를 확보하고 로딩·오류·빈
204
207
 
205
208
  - 카드 그림은 장식으로 숨기므로 그림에만 있는 정보(점수·제목)가 없어야 한다.
206
209
  - Grid `gap`은 단일 token 문자열을 받지 않는다. `gap={{ compact: "xl" }}`처럼 class별 값으로 준다(`ResponsiveValue<GridGap>`).
207
- - 구획 제목은 Heading으로 표시한다. 이전 Text heading 예제는 2026-10-06 제목 의미 구조를 맞추면서 수정했다.
210
+ - 구획 제목은 Heading으로 표시한다.
208
211
  새 제품은 Heading `level3`·`semanticLevel={1}`을 쓴다.
209
212
  - 예제의 바깥 틀은 양 플랫폼 모두 `Container gutter="compact"`다. 제품의 넓은 화면에서는 위 배치 표의 반응형 gutter를 선택한다.
210
213
  - 양 플랫폼 격자는 Grid를 사용한다. 창 폭의 열 수가 본문보다 넓으면 `minColumnWidth`가 실제 열 수를 줄인다.
211
- - 카테고리는 `SegmentedControl presentation="pills"`의 단일 선택 계약을 사용한다. 저장만 보기는 독립 토글이므로 Button `selected`를 유지한다.
214
+ - 카테고리는 `SegmentedControl presentation="pills"`, 정렬은 `SegmentedControl size="small"`의 단일 선택 계약을 사용한다. 저장만 보기는 결과 필터라 Chip `selectionMode="multiple"`을 쓴다. Button `selected`는 카드·시트의 저장 토글에만 남긴다.
212
215
  - 시트 확정 행동은 footer에 둔다. Native는 scrollable 본문과 분리해 큰 글자에서도 접근 가능하게 한다.
213
216
  - 현재 스토리의 카드 메타는 "작가 · 카테고리 · 좋아요 N"을 한국어 템플릿으로 조립한다. 제품은 `t("gallery.meta", { author, category })`처럼
214
217
  보간 키 하나로 둔다. 좋아요 수가 필요하면 같은 키에 값을 더한다.
215
218
 
216
219
 
217
- 2026-10-13 행동 검수에서 제거/복구 뒤 키보드 문맥 유실을 확인해 보강했다. Web 저장만 보기에서 저장 취소로 카드가 사라지면 저장 필터로 초점을 돌린다. 상세를 닫을 때는 원래 상세 버튼, 해당 카드가 제거됐으면 저장 필터를 양 플랫폼 Sheet returnFocusRef로 지정한다. Web 빈 결과 초기화는 검색 필드로 이어진다. Native ref 연결과 실제 OS 접근성 초점 이동 검증은 구분한다.
220
+ 제거/복구 뒤 초점: Web 저장만 보기에서 저장 취소로 카드가 사라지면 저장 필터로 초점을 돌린다. 상세를 닫을 때는 원래 상세 버튼, 해당 카드가 제거됐으면 저장 필터를 양 플랫폼 Sheet returnFocusRef로 지정한다. Web 빈 결과 초기화는 검색 필드로 이어진다. Native ref 연결과 실제 OS 접근성 초점 이동 검증은 구분한다.
221
+
222
+ ## 근거
223
+
224
+ - 정렬·저장만 보기에 SegmentedControl·Chip: 2026-10-16 사용자 지적 — `selected` 버튼 줄이 테두리 없는 글자처럼 보였다.
225
+ - 구획 제목 Heading: 2026-10-06 제목 의미 구조를 맞추면서 이전 Text heading 예제를 수정했다.
226
+ - 제거/복구 뒤 초점 규칙: 2026-10-13 행동 검수에서 제거/복구 뒤 키보드 문맥 유실을 확인했다.
@@ -4,7 +4,7 @@
4
4
  - 상태: 배포
5
5
  - 지원: Web · Native
6
6
  - 적용: 구성 API 지침 참조
7
- - 검토일: 2026-10-15
7
+ - 검토일: 2026-10-17
8
8
  - 근거: [반복 화면 조합](../../screen-patterns.md); 공통 API와 실제 Web·Native 예제의 슬롯·상태를 대조해 중복 조립 방지. 2026-10-06 사용자 승인으로 스토리북 배포(이전 `실험/화면/기본 흐름/목록과 상세`, [승인 기록](../../../../../docs/STORYBOOK_NAVIGATION.md#21-2026-10-06-전체-승격과-규격-확정))
9
9
  - 스토리북: `배포/화면/콘텐츠/목록과 상세`
10
10
 
@@ -31,7 +31,7 @@ host: 남은 높이·safe area·키보드
31
31
 
32
32
  | 행동 | 컴포넌트·tone | 위치 | 개수·순서 |
33
33
  | --- | --- | --- | --- |
34
- | 작업 | ListDetailScreen 공개 행동 슬롯 | 새로고침은 actions, 상세 뒤로는 leading | 같은 표면에 경쟁하는 primary 하나만 |
34
+ | 작업 | ListDetailScreen 공개 행동 슬롯 | 새로고침은 actions, 상세 뒤로는 `back` 콜백을 받아 헤더 `exit`로 표시 | 같은 표면에 경쟁하는 primary 하나만 |
35
35
  | 복구 | Button·secondary | 오류 근처 | 재시도할 대상과 범위를 표시 |
36
36
 
37
37
  ## 상태
@@ -45,7 +45,7 @@ host: 남은 높이·safe area·키보드
45
45
 
46
46
  ### 목록과 상세의 독립 상태
47
47
 
48
- `state`는 목록에, `detail.state`는 상세에만 적용한다. 새 상세 prop은 미게시(1.18.0 이후)이며
48
+ `state`는 목록에, `detail.state`는 상세에만 적용한다. 상세 복구 prop은 Web·Native 1.26.1 설치 타입에서 확인했으며
49
49
  기존 `detail: { title, content }` 사용법은 그대로 유지한다. 목록 입력·스크롤을 보존한 채
50
50
  상세의 최초 조회·오류·재시도를 중앙 상태로 표현하려고 추가했다.
51
51
 
@@ -102,9 +102,9 @@ import { ListDetailScreen } from "@hjmds/react-native/screen-flows";
102
102
  `detailRetry`는 해당 id만 다시 조회하는 Button, `detailNotice`는 기존 상세를 유지하는 Notice다.
103
103
 
104
104
  <!-- usage-example: Web ../../../../../showcase/web/src/patterns/list-detail-recovery-preview.tsx -->
105
- - [Web 상세 복구 실행 예제](../../../../../showcase/web/src/patterns/list-detail-recovery-preview.tsx)
105
+ - [Web 상세 복구 실행 예제](https://github.com/jim1286/hjm-design-system/blob/9fccb921216a06bac2568ca8252bd0b4d7c48d95/showcase/web/src/patterns/list-detail-recovery-preview.tsx)
106
106
  <!-- usage-example: Native ../../../../../showcase/native/src/list-detail-recovery-preview.tsx -->
107
- - [Native 상세 복구 실행 예제](../../../../../showcase/native/src/list-detail-recovery-preview.tsx)
107
+ - [Native 상세 복구 실행 예제](https://github.com/jim1286/hjm-design-system/blob/9fccb921216a06bac2568ca8252bd0b4d7c48d95/showcase/native/src/list-detail-recovery-preview.tsx)
108
108
 
109
109
  스토리 `상세 조회 실패와 복구`: 목록 메모 입력 → 다음 상세 조회 실패시키기 → 항목 열기 →
110
110
  상세만 다시 불러오기 → 목록으로 복귀해 메모 유지 확인. 예제의 지연·서버 응답은 메모리 모의다.
@@ -124,4 +124,8 @@ import { ListDetailScreen } from "@hjmds/react-native/screen-flows";
124
124
  - 목록 pane 유지; 상세 실패는 detail.state·detail.stateAction으로 복구.
125
125
  - Storybook은 실제 서버·OS 권한·라우터 연동 증거가 아니다. 기본·다크와 실패/복구를 각각 확인한다.
126
126
 
127
- 2026-10-13 실제 예제 연결 검수: 새 기록은 빈 초안으로 시작하고, 성공한 제목·본문을 고유 ID의 목록 항목에 함께 보관한다. 다른 기록 상세는 현재 편집 초안을 읽지 않는다. 저장 성공 시 전체 목록으로 돌아가 새 항목을 보이며, 빈 목록에서 작성한 뒤에는 ready로 전환한다. 필터와 새 기록 행동을 표시해도 새로고침 실패 Notice를 덮어쓰지 않는다.
127
+ 예제 목록 연결: 새 기록은 빈 초안으로 시작하고, 성공한 제목·본문을 고유 ID의 목록 항목에 함께 보관한다. 다른 기록 상세는 현재 편집 초안을 읽지 않는다. 저장 성공 시 전체 목록으로 돌아가 새 항목을 보이며, 빈 목록에서 작성한 뒤에는 ready로 전환한다. 필터와 새 기록 행동을 표시해도 새로고침 실패 Notice를 덮어쓰지 않는다.
128
+
129
+ ## 근거
130
+
131
+ - 예제 목록 연결(빈 초안·고유 ID·목록 복귀·Notice 유지): 2026-10-13 실제 예제 연결 검수.
@@ -4,7 +4,7 @@
4
4
  - 상태: 배포
5
5
  - 지원: Web · Native
6
6
  - 적용: 구성 API 지침 참조
7
- - 검토일: 2026-10-15
7
+ - 검토일: 2026-10-17
8
8
  - 근거: [반복 화면 조합](../../screen-patterns.md); 공통 API와 실제 Web·Native 예제의 슬롯·상태를 대조해 중복 조립 방지. 2026-10-06 사용자 승인으로 스토리북 배포(이전 `실험/화면/기본 흐름/작성과 수정`, [승인 기록](../../../../../docs/STORYBOOK_NAVIGATION.md#21-2026-10-06-전체-승격과-규격-확정))
9
9
  - 스토리북: `배포/화면/콘텐츠/작성과 수정`
10
10
 
@@ -46,8 +46,6 @@ host: 남은 높이·safe area·키보드
46
46
 
47
47
  ### 초안과 서버 확정
48
48
 
49
- 2026-10-13 전수 점검에서 최초 조회와 변경 요청의 상태를 같은 골격으로 설명하던 부분을 구체화했다.
50
-
51
49
  | 상황 | 연결 | 유지·복구 |
52
50
  | --- | --- | --- |
53
51
  | 새 작성 | ready + 빈 입력 | 빈 초안은 화면 empty가 아니다. 입력을 바로 보여 준다 |
@@ -56,8 +54,7 @@ host: 남은 높이·safe area·키보드
56
54
  | 저장 성공 | 제출 snapshot을 saved로 갱신 | 요청 후 새로 쓴 내용까지 saved로 처리하지 않는다. draftStatus는 실제 임시 저장 성공 때만 표시한다 |
57
55
  | 브라우저·OS 뒤로 | 제품 router의 이탈 가드 | EditorScreen cancel의 discard와 같은 제품 결정을 공유한다 |
58
56
 
59
- 본문이 ready가 아닌 최초 조회·오류·제한 상태에서는 헤더/하단 어느 위치의 저장도 비활성이다.
60
- 2026-10-13 입력을 표시하지 않는 상태에서도 저장할 수 있던 경로를 수정했다. 실패한 저장은
57
+ 본문이 ready가 아닌 최초 조회·오류·제한 상태에서는 헤더/하단 어느 위치의 저장도 비활성이다. 실패한 저장은
61
58
  본문을 오류 화면으로 교체하지 않고 위 표처럼 ready + notice로 유지한다.
62
59
 
63
60
  ## 사용하는 지침
@@ -137,4 +134,10 @@ import { TextField } from "@hjmds/react-native/inputs";
137
134
  - 저장 실패는 입력 유지; 저장 성공 확인 후에만 dirty 해제.
138
135
  - Storybook은 실제 서버·OS 권한·라우터 연동 증거가 아니다. 기본·다크와 실패/복구를 각각 확인한다.
139
136
 
140
- 2026-10-13 실제 예제 연결 검수: 저장 중 입력은 Web readOnly·Native disabled로 잠그고, 같은 렌더 사이에 들어온 저장 요청도 동기 잠금으로 한 번만 처리한다. 실패 예약은 요청 시작 때 소비하며 진행 중 예약 변경은 무시한다. 실패 후 현재 초안으로 재시도하고, 폐기하면 마지막으로 확정한 snapshot으로 돌아간다. 예제의 400ms 지연과 메모리 저장은 서버 저장 증거가 아니다.
137
+ 예제 저장 연결: 저장 중 입력은 Web readOnly·Native disabled로 잠그고, 같은 렌더 사이에 들어온 저장 요청도 동기 잠금으로 한 번만 처리한다. 실패 예약은 요청 시작 때 소비하며 진행 중 예약 변경은 무시한다. 실패 후 현재 초안으로 재시도하고, 폐기하면 마지막으로 확정한 snapshot으로 돌아간다. 예제의 400ms 지연과 메모리 저장은 서버 저장 증거가 아니다.
138
+
139
+ ## 근거
140
+
141
+ - 초안과 서버 확정 상황 표: 2026-10-13 전수 점검 — 최초 조회와 변경 요청의 상태를 같은 골격으로 설명하던 부분을 구체화했다.
142
+ - ready가 아닐 때 저장 비활성: 2026-10-13 입력을 표시하지 않는 상태에서도 저장할 수 있던 경로를 수정했다.
143
+ - 예제 저장 연결(입력 잠금·동기 잠금·실패 예약 소비): 2026-10-13 실제 예제 연결 검수.