@hjmds/design-contracts 1.18.0 → 1.19.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 (244) hide show
  1. package/dist/action-session.d.ts +16 -0
  2. package/dist/action-session.d.ts.map +1 -1
  3. package/dist/action-session.js +18 -0
  4. package/dist/action-session.js.map +1 -1
  5. package/dist/behaviors.d.ts +7 -6
  6. package/dist/behaviors.d.ts.map +1 -1
  7. package/dist/behaviors.js +4 -3
  8. package/dist/behaviors.js.map +1 -1
  9. package/dist/bottom-navigation.d.ts +4 -0
  10. package/dist/bottom-navigation.d.ts.map +1 -1
  11. package/dist/bottom-navigation.js +11 -0
  12. package/dist/bottom-navigation.js.map +1 -1
  13. package/dist/carousel.d.ts +3 -0
  14. package/dist/carousel.d.ts.map +1 -1
  15. package/dist/carousel.js.map +1 -1
  16. package/dist/catalog.d.ts +42 -0
  17. package/dist/catalog.d.ts.map +1 -1
  18. package/dist/component-recipes.d.ts +42 -0
  19. package/dist/component-recipes.d.ts.map +1 -1
  20. package/dist/component-recipes.js +14 -1
  21. package/dist/component-recipes.js.map +1 -1
  22. package/dist/field-group.d.ts +14 -0
  23. package/dist/field-group.d.ts.map +1 -1
  24. package/dist/field-group.js +9 -0
  25. package/dist/field-group.js.map +1 -1
  26. package/dist/form.d.ts.map +1 -1
  27. package/dist/form.js +10 -3
  28. package/dist/form.js.map +1 -1
  29. package/dist/screen-patterns.d.ts +4 -0
  30. package/dist/screen-patterns.d.ts.map +1 -1
  31. package/dist/screen-patterns.js +12 -3
  32. package/dist/screen-patterns.js.map +1 -1
  33. package/dist/toast.d.ts +10 -1
  34. package/dist/toast.d.ts.map +1 -1
  35. package/dist/toast.js +43 -2
  36. package/dist/toast.js.map +1 -1
  37. package/dist/version.d.ts +1 -1
  38. package/dist/version.js +1 -1
  39. package/dist/version.js.map +1 -1
  40. package/docs/action-session.md +2 -0
  41. package/docs/asset.md +7 -0
  42. package/docs/bottom-navigation.md +11 -0
  43. package/docs/brand-boundary.md +3 -3
  44. package/docs/collection-rail.md +4 -0
  45. package/docs/compound-controls.md +8 -3
  46. package/docs/consumer-policy.md +39 -120
  47. package/docs/generated/component-maturity.md +1 -1
  48. package/docs/generated/renderer-evidence.json +3 -3
  49. package/docs/generated/renderer-evidence.md +1 -1
  50. package/docs/generated/showcase-manifest.json +1 -1
  51. package/docs/gravity-letters.md +2 -0
  52. package/docs/grid-reveal.md +2 -0
  53. package/docs/layout.md +2 -2
  54. package/docs/load-more.md +5 -0
  55. package/docs/migration-native-legacy-removal.md +30 -17
  56. package/docs/optional-adapters.md +4 -0
  57. package/docs/screen-patterns.md +23 -1
  58. package/docs/toast.md +41 -2
  59. package/docs/usage/README.md +248 -245
  60. package/docs/usage/SELECTION.md +43 -0
  61. package/docs/usage/STANDARD.md +22 -10
  62. package/docs/usage/components/accordion.md +5 -3
  63. package/docs/usage/components/agreement.md +6 -9
  64. package/docs/usage/components/alert-dialog.md +12 -6
  65. package/docs/usage/components/asset.md +10 -4
  66. package/docs/usage/components/auth-provider-button.md +2 -2
  67. package/docs/usage/components/avatar.md +8 -6
  68. package/docs/usage/components/badge.md +4 -4
  69. package/docs/usage/components/bottom-cta.md +4 -4
  70. package/docs/usage/components/bottom-info.md +2 -2
  71. package/docs/usage/components/bottom-navigation.md +16 -5
  72. package/docs/usage/components/button.md +2 -2
  73. package/docs/usage/components/card.md +1 -1
  74. package/docs/usage/components/carousel.md +12 -4
  75. package/docs/usage/components/chat-message.md +6 -2
  76. package/docs/usage/components/chat-screen.md +5 -5
  77. package/docs/usage/components/checkbox-group.md +1 -1
  78. package/docs/usage/components/checkbox.md +2 -2
  79. package/docs/usage/components/chip.md +2 -2
  80. package/docs/usage/components/code-block.md +3 -4
  81. package/docs/usage/components/collapsible.md +6 -4
  82. package/docs/usage/components/combobox.md +2 -2
  83. package/docs/usage/components/command-palette.md +1 -1
  84. package/docs/usage/components/comment-thread-screen.md +7 -4
  85. package/docs/usage/components/container.md +1 -1
  86. package/docs/usage/components/content-transition.md +2 -2
  87. package/docs/usage/components/context-menu.md +2 -2
  88. package/docs/usage/components/counter-badge.md +3 -3
  89. package/docs/usage/components/data-table.md +1 -1
  90. package/docs/usage/components/date-picker.md +4 -4
  91. package/docs/usage/components/description-list.md +3 -3
  92. package/docs/usage/components/design-system-provider.md +2 -2
  93. package/docs/usage/components/dialog.md +14 -3
  94. package/docs/usage/components/divider.md +2 -2
  95. package/docs/usage/components/editor-screen.md +5 -3
  96. package/docs/usage/components/effect-surface.md +1 -1
  97. package/docs/usage/components/empty-state.md +6 -6
  98. package/docs/usage/components/field.md +5 -3
  99. package/docs/usage/components/file-picker.md +6 -3
  100. package/docs/usage/components/form.md +1 -1
  101. package/docs/usage/components/heading.md +3 -3
  102. package/docs/usage/components/icon-button.md +3 -3
  103. package/docs/usage/components/icon.md +1 -1
  104. package/docs/usage/components/image.md +5 -4
  105. package/docs/usage/components/keyboard-dock.md +1 -1
  106. package/docs/usage/components/layout.md +1 -1
  107. package/docs/usage/components/link.md +2 -2
  108. package/docs/usage/components/list-detail-screen.md +6 -6
  109. package/docs/usage/components/list-row.md +13 -4
  110. package/docs/usage/components/list.md +5 -4
  111. package/docs/usage/components/masonry.md +2 -1
  112. package/docs/usage/components/media-selection-screen.md +11 -5
  113. package/docs/usage/components/mentions.md +2 -2
  114. package/docs/usage/components/menu.md +3 -2
  115. package/docs/usage/components/menubar.md +2 -2
  116. package/docs/usage/components/message-composer.md +3 -3
  117. package/docs/usage/components/moderation-screen.md +3 -2
  118. package/docs/usage/components/notice.md +2 -2
  119. package/docs/usage/components/notification-inbox-screen.md +1 -1
  120. package/docs/usage/components/notification-item.md +2 -2
  121. package/docs/usage/components/number-field.md +3 -3
  122. package/docs/usage/components/otp-field.md +1 -1
  123. package/docs/usage/components/overview-screen.md +1 -1
  124. package/docs/usage/components/permission-screen.md +1 -1
  125. package/docs/usage/components/photo-source-sheet.md +1 -1
  126. package/docs/usage/components/popover.md +2 -2
  127. package/docs/usage/components/profile-screen.md +1 -1
  128. package/docs/usage/components/progress.md +2 -2
  129. package/docs/usage/components/qr-code.md +2 -2
  130. package/docs/usage/components/radio-group.md +1 -2
  131. package/docs/usage/components/radio.md +1 -2
  132. package/docs/usage/components/result.md +1 -1
  133. package/docs/usage/components/saved-items-screen.md +4 -2
  134. package/docs/usage/components/screen-layout.md +4 -3
  135. package/docs/usage/components/search-field.md +1 -1
  136. package/docs/usage/components/search-screen.md +4 -4
  137. package/docs/usage/components/section.md +3 -3
  138. package/docs/usage/components/segmented-control.md +4 -4
  139. package/docs/usage/components/select.md +2 -3
  140. package/docs/usage/components/settings-screen.md +11 -49
  141. package/docs/usage/components/sheet.md +10 -5
  142. package/docs/usage/components/sidebar.md +1 -1
  143. package/docs/usage/components/skeleton.md +1 -1
  144. package/docs/usage/components/skip-nav.md +1 -1
  145. package/docs/usage/components/slider.md +2 -2
  146. package/docs/usage/components/spinner.md +2 -2
  147. package/docs/usage/components/splitter.md +1 -1
  148. package/docs/usage/components/statistic.md +5 -4
  149. package/docs/usage/components/steps.md +2 -2
  150. package/docs/usage/components/surface.md +2 -3
  151. package/docs/usage/components/switch.md +1 -1
  152. package/docs/usage/components/tabs.md +4 -4
  153. package/docs/usage/components/tag.md +2 -2
  154. package/docs/usage/components/tags-input.md +4 -4
  155. package/docs/usage/components/text-area.md +2 -3
  156. package/docs/usage/components/text.md +85 -4
  157. package/docs/usage/components/timeline.md +2 -2
  158. package/docs/usage/components/toast.md +17 -7
  159. package/docs/usage/components/toggle-group.md +3 -3
  160. package/docs/usage/components/tooltip.md +1 -1
  161. package/docs/usage/components/top-bar.md +5 -4
  162. package/docs/usage/components/top.md +3 -3
  163. package/docs/usage/components/transfer-list.md +5 -4
  164. package/docs/usage/components/upload-item.md +3 -3
  165. package/docs/usage/components/virtual-list.md +1 -0
  166. package/docs/usage/compositions/action-feedback.md +42 -6
  167. package/docs/usage/compositions/action-recovery-optimistic.md +6 -1
  168. package/docs/usage/compositions/action-recovery-save.md +19 -23
  169. package/docs/usage/compositions/action-recovery-undo.md +6 -1
  170. package/docs/usage/compositions/activity-status.md +11 -5
  171. package/docs/usage/compositions/collection-detail.md +2 -0
  172. package/docs/usage/compositions/command-records.md +15 -5
  173. package/docs/usage/compositions/common-message.md +4 -2
  174. package/docs/usage/compositions/common-notification.md +5 -3
  175. package/docs/usage/compositions/compound-controls.md +13 -5
  176. package/docs/usage/compositions/content-transition-comparison.md +2 -2
  177. package/docs/usage/compositions/date-entry.md +10 -5
  178. package/docs/usage/compositions/date-time-selection.md +3 -3
  179. package/docs/usage/compositions/design-profile-comparison.md +7 -5
  180. package/docs/usage/compositions/disclosure.md +1 -1
  181. package/docs/usage/compositions/document-resource.md +10 -3
  182. package/docs/usage/compositions/expo-interactions.md +3 -2
  183. package/docs/usage/compositions/family-drawer.md +9 -4
  184. package/docs/usage/compositions/field-group.md +7 -1
  185. package/docs/usage/compositions/floating-action-button.md +18 -18
  186. package/docs/usage/compositions/illustrated-outcome.md +3 -2
  187. package/docs/usage/compositions/input-sheet.md +6 -2
  188. package/docs/usage/compositions/interaction-adapters.md +1 -1
  189. package/docs/usage/compositions/interaction-flow-apply.md +6 -3
  190. package/docs/usage/compositions/interaction-flow-draft.md +5 -2
  191. package/docs/usage/compositions/interaction-flow-search.md +5 -2
  192. package/docs/usage/compositions/live-list.md +7 -5
  193. package/docs/usage/compositions/navigation-bar-collection.md +8 -7
  194. package/docs/usage/compositions/optional-adapters.md +1 -1
  195. package/docs/usage/compositions/origin-dialog.md +5 -5
  196. package/docs/usage/compositions/paper-surface.md +1 -1
  197. package/docs/usage/compositions/photo-source.md +1 -1
  198. package/docs/usage/compositions/purpose-input-comment.md +5 -3
  199. package/docs/usage/compositions/purpose-input-message.md +5 -3
  200. package/docs/usage/compositions/reference-first.md +63 -42
  201. package/docs/usage/compositions/reference-review.md +60 -50
  202. package/docs/usage/compositions/reference-settings.md +60 -50
  203. package/docs/usage/compositions/selection-scope.md +48 -35
  204. package/docs/usage/compositions/stea-order-progress.md +1 -0
  205. package/docs/usage/compositions/stea-otp-verify.md +5 -3
  206. package/docs/usage/compositions/stea-pixel-empty.md +3 -3
  207. package/docs/usage/compositions/time-selection.md +6 -5
  208. package/docs/usage/compositions/upload-recovery.md +10 -1
  209. package/docs/usage/compositions/video-dialog.md +10 -3
  210. package/docs/usage/compositions/web-additions.md +5 -5
  211. package/docs/usage/compositions/web-navigation.md +1 -1
  212. package/docs/usage/screens/common-chat.md +21 -8
  213. package/docs/usage/screens/common-comments.md +13 -1
  214. package/docs/usage/screens/common-inbox.md +19 -6
  215. package/docs/usage/screens/common-login.md +19 -7
  216. package/docs/usage/screens/common-profile.md +22 -9
  217. package/docs/usage/screens/common-saved.md +16 -2
  218. package/docs/usage/screens/common-search.md +24 -4
  219. package/docs/usage/screens/common-settings.md +22 -50
  220. package/docs/usage/screens/common-shell.md +14 -2
  221. package/docs/usage/screens/dashboard.md +52 -109
  222. package/docs/usage/screens/discovery-gallery.md +57 -148
  223. package/docs/usage/screens/flow-collection.md +38 -9
  224. package/docs/usage/screens/flow-editor.md +25 -7
  225. package/docs/usage/screens/flow-media.md +23 -7
  226. package/docs/usage/screens/flow-moderation.md +15 -2
  227. package/docs/usage/screens/flow-onboarding.md +3 -3
  228. package/docs/usage/screens/flow-permission.md +17 -3
  229. package/docs/usage/screens/landing.md +37 -20
  230. package/docs/usage/screens/mockup-studio.md +22 -7
  231. package/docs/usage/screens/notification-settings.md +18 -4
  232. package/docs/usage/screens/product-bento.md +18 -9
  233. package/docs/usage/screens/reference-comparison.md +29 -10
  234. package/docs/usage/tokens/color.md +6 -3
  235. package/docs/usage/tokens/elevation-opacity.md +4 -2
  236. package/docs/usage/tokens/font-roles.md +2 -2
  237. package/docs/usage/tokens/layers.md +3 -3
  238. package/docs/usage/tokens/motion.md +2 -2
  239. package/docs/usage/tokens/radius.md +4 -2
  240. package/docs/usage/tokens/spacing.md +3 -3
  241. package/docs/usage/tokens/theme-studio.md +2 -2
  242. package/docs/usage/tokens/typography-studio.md +14 -8
  243. package/docs/usage/tokens/typography.md +15 -5
  244. package/package.json +1 -1
@@ -129,6 +129,7 @@ function BookmarkToggle({ initial, setBookmark }: { initial: boolean; setBookmar
129
129
 
130
130
  ```tsx
131
131
  // Native
132
+ import { StatusText } from "@hjmds/react-native/status-text";
132
133
  import { useState, useSyncExternalStore } from "react";
133
134
  import { ScrollView, useWindowDimensions } from "react-native";
134
135
  import { createActionSession } from "@hjmds/design-contracts/action-session";
@@ -163,7 +164,7 @@ function BookmarkToggle({ initial, setBookmark }: { initial: boolean; setBookmar
163
164
  }
164
165
  ```
165
166
 
166
- `setBookmark`(서버가 확정한 boolean을 돌려줌)과 `StatusText`는 제품 소유다([공통 절](action-recovery-save.md#공통-세션과-상태-알림)의 helper).
167
+ `setBookmark`(서버가 확정한 boolean을 돌려줌)는 제품 소유다. `StatusText`는 [공개 Text companion](../components/text.md#statustext)이다.
167
168
 
168
169
  ## 플랫폼 차이
169
170
 
@@ -178,3 +179,7 @@ function BookmarkToggle({ initial, setBookmark }: { initial: boolean; setBookmar
178
179
 
179
180
  - 구획 제목은 Heading으로 표시한다. 이전 Text heading 예제는 2026-10-06 제목 의미 구조를 맞추면서 수정했다.
180
181
  - 재시도는 secondary로 두어 저장과 경쟁하는 primary를 만들지 않는다(2026-10-06 예제 반영).
182
+
183
+ ### 재시도 뒤 초점과 결과 구분
184
+
185
+ Web 예제는 사라질 재시도 버튼에서 기존 북마크 토글로 초점을 옮긴다. pending의 토글은 `aria-disabled`와 Button의 실행 차단을 사용해 키보드 위치를 유지한다. Native 예제의 `disabled`와 구분한다. 서버 새 상태로 reset한 뒤 이전 완료는 상태나 초점을 덮지 않는다. 예제는 메모리 요청이며 실제 서버 정합성을 검증하지 않는다.
@@ -145,6 +145,7 @@ function SaveNote({ save }: { save(value: string): Promise<string> }) {
145
145
 
146
146
  ```tsx
147
147
  // Native
148
+ import { StatusText } from "@hjmds/react-native/status-text";
148
149
  import { useState, useSyncExternalStore } from "react";
149
150
  import { ScrollView, useWindowDimensions } from "react-native";
150
151
  import { createActionSession } from "@hjmds/design-contracts/action-session";
@@ -173,7 +174,7 @@ function SaveNote({ save }: { save(value: string): Promise<string> }) {
173
174
  {t("common.save")}
174
175
  </Button>
175
176
  {state.status === "error" && <Button tone="secondary" onPress={() => void session.retry()}>{t("save.retry")}</Button>}
176
- <StatusText>{t(saveStatusKey[state.status])}</StatusText>{/* 아래 공통 절의 제품 helper */}
177
+ <StatusText>{t(saveStatusKey[state.status])}</StatusText>{/* 공개 Text companion */}
177
178
  {state.value ? <Text>{t("save.savedValue", { value: state.value })}</Text> : null}
178
179
  </Stack>
179
180
  </Container>
@@ -190,31 +191,20 @@ function SaveNote({ save }: { save(value: string): Promise<string> }) {
190
191
  화면보다 오래 살아야 하면 제품의 작업 소유자(store)에 둔다. unmount 시 자동 reset은 기본으로 넣지 않는다.
191
192
  - 구독은 Web·Native 모두 `useSyncExternalStore(session.subscribe, session.getSnapshot, session.getSnapshot)`다. SSR은 요청별 세션을 만든다.
192
193
  - `reset(value)`는 세대를 바꿔 이전 응답과 재시도를 버린다. 서버 작업을 취소하지는 않는다.
193
- - 상태 문구 알림: Web은 `<Text role="status">`다. Native는 `Text`에 `accessibilityLiveRegion="polite"`(Android)를 주고,
194
- iOS에서는 문구가 바뀌었고 비어 있지 않을 때만, 앱이 active일 때 `AccessibilityInfo.announceForAccessibilityWithOptions(message, { queue: true })`를 부른다.
195
- 스토리의 `PatternStatus`(`showcase/native/src/pattern-status.tsx`)는 공개 API가 아니므로 제품에서 같은 동작의 helper를 만든다.
196
- 나머지 Text prop은 `Omit<ComponentProps<typeof Text>, ...>`로 받아 그대로 넘긴다(optional prop을 `tone?: …`로 따로 받아 넘기면
197
- `exactOptionalPropertyTypes`에서 TS2375가 난다).
194
+ - 짧은 상태 문구는 공개 [StatusText](../components/text.md#statustext)를 쓴다. 문구와 실제 저장 결과는 제품이 소유하고,
195
+ Web live region·Native iOS 알림과 Android live region은 컴포넌트가 연결한다.
196
+ - 계속 표시되는 최초 안내는 iOS에서 알리지 않는다. 행동 결과를 조건부로 새로 마운트하면 Native의
197
+ `announceOnMount`를 명시한다. 빈 문구·같은 문구 재렌더·배경 변경은 iOS에서 발표하지 않으며 복귀 때 재생하지 않는다.
198
+ - Web은 동일 live node의 문구를 갱신한다. 조건부 마운트의 실제 낭독은 브라우저·보조기술에 따라 달라진다.
199
+ 제목·설명·재시도 행동을 묶으면 Notice, 화면 위 일시 알림은 Toast를 고른다.
198
200
 
199
201
  ```tsx
200
202
  // Native
201
- import { useEffect, useRef, type ComponentProps } from "react";
202
- import { AccessibilityInfo, AppState, Platform } from "react-native";
203
- import { Text } from "@hjmds/react-native/primitives";
204
-
205
- type StatusTextProps = Omit<ComponentProps<typeof Text>, "children" | "accessibilityLiveRegion"> & { children: string };
206
-
207
- function StatusText({ children, ...props }: StatusTextProps) {
208
- const previous = useRef(children); // 첫 렌더는 알리지 않는다
209
- useEffect(() => {
210
- const changed = previous.current !== children;
211
- previous.current = children;
212
- if (changed && children.trim() && Platform.OS === "ios" && AppState.currentState === "active") {
213
- AccessibilityInfo.announceForAccessibilityWithOptions(children, { queue: true });
214
- }
215
- }, [children]);
216
- return <Text {...props} accessibilityLiveRegion="polite">{children}</Text>;
217
- }
203
+ import { StatusText } from "@hjmds/react-native/status-text";
204
+ // Web: import { StatusText } from "@hjmds/react/status-text";
205
+
206
+ <StatusText>{t(saveStatusKey[state.status])}</StatusText>
207
+ {saved ? <StatusText announceOnMount>{t("save.complete")}</StatusText> : null} // Native
218
208
  ```
219
209
 
220
210
  ## 플랫폼 차이
@@ -233,3 +223,9 @@ function StatusText({ children, ...props }: StatusTextProps) {
233
223
  - 렌더 중 `createActionSession`을 부르면 매 렌더 새 세션이 생겨 중복 차단이 사라진다.
234
224
  - 구획 제목은 Heading으로 표시한다. 이전 Text heading 예제는 2026-10-06 제목 의미 구조를 맞추면서 수정했다.
235
225
  - 재시도는 secondary로 두어 저장과 경쟁하는 primary를 만들지 않는다(2026-10-06 예제 반영).
226
+
227
+ ### 재시도 뒤 초점과 결과 구분
228
+
229
+ Web 예제는 재시도 버튼이 pending에서 사라지기 전에 저장 행동으로 초점을 옮긴다. 현재 입력이 비어 저장이 비활성화됐으면 입력칸으로 이동한다. 완료 시에는 추가 초점 이동을 하지 않아 사용자가 그동안 선택한 입력이나 다른 행동을 보존한다.
230
+
231
+ 재시도는 실패한 제출 snapshot을 다시 실행하며, 새로 입력한 초안은 주 저장 행동으로 제출한다. 성공한 snapshot과 현재 초안이 다르면 현재 입력이 아직 저장되지 않았다고 별도로 안내한다. 확정 값으로 편집 중인 초안을 덮어쓰지 않는다.
@@ -138,6 +138,7 @@ function ArchiveItem({ id, name, api }: { id: string; name: string; api: Archive
138
138
 
139
139
  ```tsx
140
140
  // Native
141
+ import { StatusText } from "@hjmds/react-native/status-text";
141
142
  import { useState, useSyncExternalStore } from "react";
142
143
  import { ScrollView, useWindowDimensions } from "react-native";
143
144
  import { createActionSession, type ActionSnapshot } from "@hjmds/design-contracts/action-session";
@@ -177,7 +178,7 @@ function ArchiveItem({ id, name, api }: { id: string; name: string; api: Archive
177
178
  }
178
179
  ```
179
180
 
180
- `api.archive`·`api.unarchive`(서버가 확정한 boolean을 돌려줌)와 `StatusText`는 제품 소유다([공통 절](action-recovery-save.md#공통-세션과-상태-알림)의 helper).
181
+ `api.archive`·`api.unarchive`(서버가 확정한 boolean을 돌려줌)는 제품 소유다. `StatusText`는 [공개 Text companion](../components/text.md#statustext)이다.
181
182
 
182
183
  ## 플랫폼 차이
183
184
 
@@ -191,3 +192,7 @@ function ArchiveItem({ id, name, api }: { id: string; name: string; api: Archive
191
192
 
192
193
  - 구획 제목은 Heading으로 표시한다. 이전 Text heading 예제는 2026-10-06 제목 의미 구조를 맞추면서 수정했다.
193
194
  - 재시도는 secondary로 두어 저장과 경쟁하는 primary를 만들지 않는다(2026-10-06 예제 반영).
195
+
196
+ ### 재시도 뒤 초점과 결과 구분
197
+
198
+ Web 예제는 사라질 재시도 버튼에서 기존 보관/실행 취소 행동으로 초점을 옮긴다. 해당 버튼은 pending에도 같은 노드로 남아 완료 후 다음 행동으로 이어진다. 역연산이 실패하면 마지막으로 확정된 보관 상태를 유지하고, 성공한 때에만 목록으로 돌아간다.
@@ -4,7 +4,7 @@
4
4
  - 상태: 실험
5
5
  - 지원: Web · Native
6
6
  - 적용: 미게시(1.17.2 이후)
7
- - 검토일: 2026-10-10
7
+ - 검토일: 2026-10-13
8
8
  - 근거: [ActivityChip 계약](../../activity-chip.md), [Toast 패널](../../toast.md#패널-toast--미게시1172-이후), 양 Showcase `activity-status-preview.tsx`, `showcase/shared/activity-status.ts`
9
9
  - 스토리북: `실험/구성/피드백과 복구/백그라운드 작업 상태`
10
10
 
@@ -116,12 +116,12 @@ import { TopBar } from "@hjmds/react-native/top-bar";
116
116
 
117
117
  // 앱 루트: <ToastRegion placement="top" maxVisible={1} layoutStyle={{ flex: 1 }}>…</ToastRegion>
118
118
  function ReviewHeader({ task }: { task: ReviewTask | null }) {
119
- const toast = useToastRegion();
119
+ const { publish, dismiss } = useToastRegion();
120
120
  const [open, setOpen] = useState(false);
121
121
  useEffect(() => {
122
122
  if (!open || !task) return;
123
- toast.publish(reviewPanel(task, <Image source={mascot} style={{ width: 44, height: 44 }} />, () => setOpen(false)));
124
- }, [open, task, toast]);
123
+ publish(reviewPanel(task, <Image source={mascot} style={{ width: 44, height: 44 }} />, () => setOpen(false)));
124
+ }, [open, task, publish]);
125
125
  return (
126
126
  <TopBar
127
127
  title={t("screen.title")}
@@ -132,7 +132,7 @@ function ReviewHeader({ task }: { task: ReviewTask | null }) {
132
132
  emphasis={task.done ? "emphasis" : "default"}
133
133
  expanded={open}
134
134
  media={<Image source={mascot} style={{ width: 28, height: 28 }} />}
135
- onPress={() => (open ? toast.dismiss("review-status", "programmatic") : setOpen(true))}
135
+ onPress={() => (open ? dismiss("review-status", "programmatic") : setOpen(true))}
136
136
  />
137
137
  ),
138
138
  } : {})}
@@ -159,4 +159,10 @@ function ReviewHeader({ task }: { task: ReviewTask | null }) {
159
159
  - 패널을 닫는 경로가 여럿(칩·닫기·Escape·스와이프)이라 `open` 상태는 `onDismiss`에서만 내린다. 칩 핸들러에서 먼저
160
160
  `setOpen(false)`하고 `close`를 부르면 닫히는 동안(closing) 다시 누른 `publish`가 `ignored`(`closing`)로 돌아올 수 있다.
161
161
  - `media`는 장식이다. 마스코트에 의미가 있으면 그 뜻을 `title`·`description`에 쓴다.
162
+ - Native effect는 `useToastRegion()`의 안정적인 `publish` 함수에 의존한다. controller 객체 전체를 의존성에 넣으면
163
+ publish로 controller가 갱신될 때마다 effect가 다시 발행하는 반복 렌더가 생긴다(실제 예제 host에서 재현).
164
+ - 결과 보기의 예제 안내는 Web `role="status"`, 공개 `StatusText`로 전달한다.
165
+ Web은 패널이 실제 소유하던 초점 요소가 닫힘으로 제거됐을 때만 칩으로 돌아가고, 완료 칩 자체에 초점이 있을 때 자동 숨김이 일어나면 작업 시작 버튼으로 옮긴다.
166
+ Enter로 결과를 선택해 초점 요소가 제거되면 복구하지만, 포인터 클릭이 버튼에 초점을 두지 않은 경우에는 새 초점을 강제하지 않는다.
167
+ 사용자가 외부 컨트롤로 이동하거나 명시적으로 해제한 초점은 유지한다. body 자체를 패널 초점 소유의 근거로 삼지 않는다. 이는 예제의 결과 안내이며 실제 결과 화면 이동은 제품 소유다.
162
168
  - 현재 스토리는 작업 진행을 타이머로 흉내 낸다. 제품은 자기 작업 이벤트로 단계를 바꾼다.
@@ -89,3 +89,5 @@ Native는 controlled Dialog open/onOpenChange를 쓴다.
89
89
  | 상세 호출 | Dialog의 canonical trigger in Card.actions | Card.actions Button과 controlled Dialog, 실제 OS 복귀 검수 별도 |
90
90
  | 보기 변경 | ResizeObserver와 input focus 노출 | ScrollView layout/content-size와 focus 노출, OS touch/VoiceOver 별도 |
91
91
  | 테마 | 같은 Provider 경로의 10 profiles | 같은 profiles/데이터, 플랫폼 표현 |
92
+
93
+ 2026-10-13 예제 검수: 같은 페이지의 두 컬렉션이 `collection-walk` 같은 입력 ID를 중복 사용해 두 번째 라벨이 첫 번째 입력을 가리키는 문제를 재현했다. Web 입력 ID는 TextField의 고유 ID에 맡기고, 데이터 `item.id`는 카드 상태를 보존하는 key로만 사용한다. 양 플랫폼의 카드/상세 초안 분리, 테마 변경, 닫기·재열기와 빈 목록 전환은 실제 예제로 확인한다. 이 구성에는 서버 요청·저장·실패 콜백이 없으므로 해당 검증을 했다고 보지 않는다. Native OS 초점 복귀는 호스트 모형 검사와 별도다.
@@ -4,7 +4,7 @@
4
4
  - 상태: 배포
5
5
  - 지원: Web · Native
6
6
  - 적용: 1.16.0
7
- - 검토일: 2026-10-08
7
+ - 검토일: 2026-10-13
8
8
  - 근거: [후보 조사](../../../../../docs/qa/2026-10-07-reference-parallel-b.md), 양 Showcase `command-records-preview.tsx`
9
9
  - 스토리북: `배포/구성/정보 표시/명령 기록 표시`
10
10
 
@@ -59,8 +59,8 @@ Section 제목/설명 → 현재 테마/다음 테마
59
59
  | 상태 | 모습 | 포커스·알림 |
60
60
  | --- | --- | --- |
61
61
  | 기본 | 정확한 전체 원문, 두 패널·두 보기 방식 | 탭 manual focus, Web pre focus/Native selectable Text |
62
- | 진행 중 | 갱신 버튼 loading, 기존 원문·선택 유지 | 응답 도구만 갱신 상태 확정, Web status/Native PatternStatus |
63
- | 실패 | danger Notice·같은 갱신 재시도, 이전 원문 유지 | Web alert/Native Notice, Web 응답 뒤 갱신 버튼으로 복귀 |
62
+ | 진행 중 | 갱신 버튼 loading, 기존 원문·선택 유지 | 응답 도구만 갱신 상태 확정, Web status/Native StatusText |
63
+ | 실패 | danger Notice·같은 갱신 재시도, 이전 원문 유지 | Web alert/Native Notice, Web 응답 버튼이 초점을 소유할 때만 갱신 버튼으로 복귀 |
64
64
  | 복사 실패 | danger Notice·직접 원문 선택 안내 | 닫기는 안내만 제거하며 성공 아님. Web 닫기 후 복사 버튼으로 복귀 |
65
65
  | 복사 성공 | Web ClipboardButton copied | OS 요청 뒤 live status, 진행 중 포커스 유지 |
66
66
 
@@ -93,8 +93,9 @@ import { CodeBlock } from "@hjmds/react-native/code-block";
93
93
  | --- | --- | --- |
94
94
  | 복사 | ClipboardButton·OS Promise·live status | 원문 길게 누르기와 시스템 선택. ClipboardButton API 없음 |
95
95
  | 원문 | focus 가능한 pre, 긴 줄 자체 scroll | selectable Text, 긴 줄 horizontal ScrollView |
96
- | 갱신 응답 뒤 포커스 | fixture는 주 갱신 행동으로 복귀 | 기기/AT 검증 별도 |
97
- | 진행 알림 | Text role=status | Showcase PatternStatus iOS announce/Android live region. 제품은 같은 정책 연결 |
96
+ | 갱신 응답 뒤 포커스 | 응답 버튼의 초점만 주 갱신 행동으로 복귀. 원문·탭·외부 초점과 명시 blur는 유지 | 기기/AT 검증 별도 |
97
+ | 진행 알림 | Text role=status | 조건부 StatusText에 announceOnMount를 지정해 iOS 첫 진행도 알림. Android는 live region만 사용 |
98
+ | 갱신 실패 알림 | Notice danger의 alert | 기존 Notice announcement="assertive"로 iOS/Android 오류 알림 |
98
99
 
99
100
  ## 함정
100
101
 
@@ -104,3 +105,12 @@ token.text는 공백까지 원문과 같아야 하며 HTML로 삽입하지 않
104
105
  미게시 Web ClipboardButton 후속 수정은 중복 OS 쓰기를 막고, 원문 변경/언마운트 뒤 이전 결과 callback과
105
106
  성공 표시를 무시한다. 취소할 수 없는 OS 쓰기가 끝날 때까지 loading 잠금을 유지해 다음 쓰기와 순서가
106
107
  뒤집히지 않게 한다. Native 복사 실패 스토리는 제품 host 오류의 표시 fixture이며 실제 OS 거부 재현이 아니다.
108
+
109
+
110
+ 갱신 버튼은 `loading`으로 중복 실행을 막고 진행 중에도 키보드 초점을 유지한다. 같은 조건의 `disabled`를
111
+ 추가하면 Enter로 갱신을 시작할 때 초점이 body로 사라진다. 예제 응답 후 무조건 focus하는 effect도
112
+ 원문을 읽던 초점을 빼앗으므로, 응답 버튼이 소유한 초점만 비활성화 전에 주 행동으로 옮긴다.
113
+
114
+ 실제 예제의 탭 변경 중 복사 요청은 완료될 때까지 같은 ClipboardButton 인스턴스에서 잠근다. 이전 원문의
115
+ 성공·거부 결과는 새 탭에 표시하지 않으며, 완료 뒤 새 복사는 현재 탭의 원문을 전달한다. 줄 표시·테마·갱신
116
+ 전환은 현재 탭과 원문을 유지한다. Native는 같은 선택 연결과 selectable 원문을 제공하며 OS 복사 호출을 추가하지 않는다.
@@ -3,7 +3,7 @@
3
3
  - 단계: 구성
4
4
  - 상태: 배포
5
5
  - 지원: Web · Native
6
- - 적용: 미게시(1.12.1 이후)
6
+ - 적용: 구성 API 지침 참조
7
7
  - 검토일: 2026-10-06
8
8
  - 근거: [ChatMessage](../components/chat-message.md), [공통 실행과 실패 복구](../../action-session.md), `showcase/web/src/patterns/conversation-previews.tsx`, `showcase/native/src/conversation-previews.tsx`. 2026-10-06 사용자 승인으로 스토리북 배포(이전 `실험/구성/공통 화면/대화 메시지`, [승인 기록](../../../../../docs/STORYBOOK_NAVIGATION.md#21-2026-10-06-전체-승격과-규격-확정))
9
9
  - 스토리북: `배포/구성/정보 표시/대화 메시지`
@@ -65,7 +65,7 @@
65
65
  | 기본 | 작성자 → 말풍선 → 시각·전송 상태. 반응은 길게 누르기·메뉴로 연 말풍선 위 메뉴에서 고른다 | 말풍선 이름(Web `author`·Native 본문), 반응 이름은 `reactions.label` |
66
66
  | 진행 중 | 다시 보내기 `loading`, `deliveryLabel` "보내는 중" | 포커스는 버튼에 유지 |
67
67
  | 실패 | `deliveryLabel` "전송 실패" + 다시 보내기 유지, 메시지 본문 유지 | 상태 문구로 알림, 포커스 이동 없음 |
68
- | 성공 | `deliveryLabel` "전송됨", 다시 보내기 사라짐 | 상태 문구 |
68
+ | 성공 | `deliveryLabel` "전송됨", 다시 보내기 사라짐 | 성공 상태 문구. Web에서 다시 보내기에 초점이 남아 있을 때만 상태 문구로 옮기고 외부 이동·명시적 blur는 보존 |
69
69
 
70
70
  - 전송 상태·반응 문구는 id→문구 키 상수 표로 둔다. 원문 이동·답장 준비·실패 알림은 제품 알림 계층(Toast 등)으로 화면 단위로 한 번 알린다.
71
71
 
@@ -130,3 +130,5 @@ import { ChatMessage } from "@hjmds/react-native/screens";
130
130
  - 답장은 스와이프만으로 숨기지 않는다. Native 접근성 동작과 Web 키보드·메뉴 경로가 같은 `replyAction`을 부른다.
131
131
  - 화면 상태(불러오는 중·빈·오류·로그인 필요)를 말풍선 예제에 다시 만들지 않는다. [채팅 화면](../screens/common-chat.md)의 `state`가 소유한다.
132
132
  - 스토리의 "다음 전송 실패시키기"·350ms 지연은 데모 전용이다.
133
+
134
+ 2026-10-13 예제 검수: 재전송 성공 문구를 갱신하고 Web에서 사라지는 재전송 버튼의 초점을 보존했다. 양 플랫폼은 화면을 떠나면 예제 세션을 reset해 늦은 완료를 분리한다. 서버 취소를 뜻하지 않는다.
@@ -3,7 +3,7 @@
3
3
  - 단계: 구성
4
4
  - 상태: 배포
5
5
  - 지원: Web · Native
6
- - 적용: 미게시(1.12.1 이후)
6
+ - 적용: 구성 API 지침 참조
7
7
  - 검토일: 2026-10-06
8
8
  - 근거: [NotificationItem](../components/notification-item.md), [공통 실행과 실패 복구](../../action-session.md), `showcase/web/src/patterns/conversation-previews.tsx`, `showcase/native/src/conversation-previews.tsx`. 2026-10-06 사용자 승인으로 스토리북 배포(이전 `실험/구성/공통 화면/알림 항목`, [승인 기록](../../../../../docs/STORYBOOK_NAVIGATION.md#21-2026-10-06-전체-승격과-규격-확정))
9
9
  - 스토리북: `배포/구성/정보 표시/알림 항목`
@@ -49,13 +49,13 @@
49
49
  ## 흐름과 상태
50
50
 
51
51
  1. 안 읽은 알림을 누르면 `optimisticValue`로 곧바로 읽음 표시가 되고 제품이 상세로 이동한다.
52
- 2. 진행 중에는 다른 행을 `disabled`로 막아 같은 세션의 겹친 요청을 만들지 않는다(제품은 행마다 세션을 둘 수 있다).
52
+ 2. 진행 중에는 같은 세션의 겹친 요청을 막는다. Web 예제는 `aria-disabled`와 활성화 시점의 세션 검사로 현재 행의 키보드 초점을 유지하고, Native는 `disabled`를 사용한다(제품은 행마다 세션을 둘 수 있다).
53
53
  3. 서버가 실패하면 그 행만 읽지 않음으로 돌아오고 상태 문구가 알린다.
54
54
 
55
55
  | 상태 | 모습 | 포커스·알림 |
56
56
  | --- | --- | --- |
57
57
  | 기본 | 안 읽음: 제목 굵게, `statusLabel` "새 알림" | 행 이름에 제목·상태·시각 |
58
- | 진행 중 | 누른 행이 읽음으로 바뀜(낙관), 다른 행 `disabled` | 포커스 유지, 상태 문구 |
58
+ | 진행 중 | 누른 행이 읽음으로 바뀜(낙관), 추가 읽음 요청 차단 | 포커스 유지, 상태 문구 |
59
59
  | 실패 | 읽지 않음으로 복구 | 상태 문구 알림, 포커스 이동 없음 |
60
60
  | 성공 | 읽음 확정, `statusLabel` "확인함" | 상태 문구 |
61
61
 
@@ -99,3 +99,5 @@ import { NotificationItem } from "@hjmds/react-native/screens";
99
99
  - 읽음 처리 실패를 조용히 삼키지 않는다. 이전 상태로 되돌리고 알린다.
100
100
  - 필터·묶음 제목·화면 상태를 행 예제에 다시 만들지 않는다. [알림함 화면](../screens/common-inbox.md)이 소유한다.
101
101
  - 스토리의 "다음 읽음 처리 실패시키기"·350ms 지연은 데모 전용이다.
102
+
103
+ 2026-10-13 예제 검수: Web Enter 활성화 시 `disabled` 전환으로 초점이 사라지는 경로를 수정했다. 세션의 현재 snapshot에서 읽음 목록을 계산해 연속 콜백을 차단하고, 실패해도 이미 확정된 다른 행의 읽음 상태를 보존한다.
@@ -4,7 +4,7 @@
4
4
  - 상태: 배포
5
5
  - 지원: Web · Native
6
6
  - 적용: 1.12.1
7
- - 검토일: 2026-10-06
7
+ - 검토일: 2026-10-13
8
8
  - 근거: [복합 입력 계약](../../compound-controls.md), `showcase/shared/compound-controls.ts`, `showcase/{web/src/patterns,native/src}/compound-previews.tsx`
9
9
  - 스토리북: `배포/구성/비교와 검증/복합 입력 모음`
10
10
 
@@ -78,10 +78,10 @@
78
78
  | --- | --- | --- |
79
79
  | 기본 | 각 블록의 초기 값. InlineConfirm은 트리거 버튼 하나(`danger`) | — |
80
80
  | 확인 대기 | 질문 + [유지하기][삭제하기] | Web은 취소에 처음 포커스, Escape는 취소하고 트리거로 포커스 복귀. Native는 질문이 live region, iOS 한 번 알림 |
81
- | 진행 중 | InlineConfirm busy. 두 행동 비활성, 확인 버튼 `loading` + `pendingLabel`. DurationField·ReactionPicker·NotificationBell은 진행 상태가 없다 | Native iOS는 상태마다 한 번 알림, Web Escape 무시 |
81
+ | 진행 중 | InlineConfirm busy. 두 행동 비활성, 확인 버튼 `loading` + `pendingLabel`. DurationField·ReactionPicker·NotificationBell은 진행 상태가 없다 | Native iOS는 상태마다 한 번 알림, Web Escape 무시, 확인 버튼은 loading으로 재실행을 막고 키보드 초점 유지 |
82
82
  | 실패 | InlineConfirm error. 질문 아래 `errorLabel`(Native `tone="danger"`), 확인 버튼으로 다시 시도 | Web `role="alert"`, Native `accessibilityRole="alert"` + iOS는 대기 중 음성을 끊고 알린다 |
83
83
  | 재시도 실패 | 같은 `errorLabel`이 다시 나온다(busy → error로 바뀌므로 다시 알림) | 실패와 같다 |
84
- | 확인 성공 | `successLabel`이 남는다 | Web `role="status"`, Native live region. 초기화는 새 `key`로 remount |
84
+ | 확인 성공 | `successLabel`이 남는다 | Web `role="status"`에 해당 확인 묶음의 초점만 복귀(외부 초점 유지), Native live region. 초기화는 새 `key`로 remount |
85
85
  | 알림 수 증가 | 종이 400ms 한 번 흔들린다 | 종 그림·배지는 숨기고 `label` 한 번만 읽는다 |
86
86
  | reduced motion·배경·`active={false}` | 종이 흔들리지 않는다 | — |
87
87
 
@@ -178,9 +178,17 @@ function CompoundBlocks() {
178
178
  - NotificationBell 누름이 읽음 처리를 하지 않는다. 제품이 count를 갱신해야 배지가 줄어든다.
179
179
  - DurationField 범위 밖 `value`는 조용히 고치지 않고 오류를 던진다.
180
180
  - InlineConfirm의 성공 상태는 다시 트리거로 돌아가지 않는다. 같은 자리에서 다시 쓰려면 새 `key`로 remount한다.
181
- - 현재 Native 스토리는 블록 제목을 `Text variant="heading"`으로 그리고, Web 스토리는 `Heading level="level2"`(32)를 쓴다.
182
- 플랫폼별 크기 차이에 근거가 없으므로 제품은 두 플랫폼 모두 `Heading level="level3"`(24)과 화면 구조에 맞는 `semanticLevel`을 쓴다.
181
+ - 양 플랫폼 예제의 블록 제목은 `Heading level="level3"`(24)이다. 제품 문서 단계는 화면 구조에 맞는 `semanticLevel`로 지정한다. 기존 Web 32/Native 24의 근거 없는 차이는 2026-10-13 실제 구성 검수에서 통일했다.
183
182
  - 현재 Native 스토리는 `ScrollView contentContainerStyle={{ padding: spacing.xl, gap: spacing.xl }}`로 좌우 여백과 간격을 직접 준다.
184
183
  좌우 여백은 `Container gutter`, 위아래는 `paddingVertical: spacing.lg`, 자식 간격은 `Stack gap="xl"`로 둔다([화면 여백](../tokens/layout.md)).
185
184
  - 현재 스토리의 Switch "실패 응답 보기"·"예제 다시 시작"·"새 알림 추가" 버튼은 데모 조작이다. 알림 상태 스토리의 "새 알림 추가"는 primary라
186
185
  제품 화면에 옮기면 그 화면의 주 행동과 겹친다.
186
+
187
+
188
+ 2026-10-13 실제 구성 검수에서 실패 설정을 요청 완료 시 읽어 진행 중 결과가 바뀌고, 같은 렌더의 연속 알림 추가가 하나로 합쳐지는 문제를 확인했다. 예제의 600ms 지연은 유지하되 실패 여부는 확인 시작 시 캡처하고, 새 알림은 함수형 갱신으로 모두 반영한다. 처리 중 설정 변경은 다음 확인에 적용된다. 다시 시작은 새 로컬 예제를 열며 이전 삭제 요청의 실제 취소를 뜻하지 않는다. Web 확인의 pending/success 초점 문제는 예제의 DOM 우회가 아니라 공개 InlineConfirm에서 고쳤다.
189
+
190
+ ### 소요 시간의 하위 입력 그룹
191
+
192
+ `DurationField`는 `FieldGroup layout="inline"`으로 그룹 이름과 단위별 잠금을 연결하고, 각 값은 기존 NumberField로 편집한다. 좁은 영역에서는 시간·분·초 순서를 유지하며 줄바꿈한다. Native의 보이는 라벨은 각 단위만, 접근성 이름은 그룹 이름과 단위를 함께 전달한다. 그룹이 잠기거나 사라진 뒤 도착한 이전 단위 callback은 값을 바꾸지 않는다. 총 초 단위 값·상한 보정은 기존 duration-field 계약이 소유한다.
193
+
194
+ 2026-10-13 날짜 직접 입력과 반복되던 그룹 제목·입력 행을 FieldGroup과 공통 `fieldGroupRecipe`로 옮겼다. 자세한 배치 축은 [관련 입력 묶음](field-group.md)을 따른다.
@@ -41,7 +41,7 @@ Motion Primitives Transition Panel의 두 제공 예제를 실제 검토한 후
41
41
  | 영역 | 컴포넌트 | 위치 | 크기·간격 |
42
42
  | --- | --- | --- | --- |
43
43
  | 바깥 틀 | Stack·Native ScrollView | 세로, 비교 도구와 단계 viewport를 함께 스크롤 | `spacing.lg` 20 |
44
- | 표현 선택 | SegmentedControl | 안내 아래 | 공개 `presentation="pills"`, 큰 글자·좁은 화면 줄바꿈 |
44
+ | 표현 선택 | SegmentedControl | 안내 아래 | 공개 `presentation="pills"`, 좁은 화면 줄바꿈 |
45
45
  | 탭 본문 | TabPanel·ContentTransition·Stack | 탭 목록 아래 | `spacing.md` 16, 높이는 현재 내용 측정 |
46
46
  | 단계 틀 | OnboardingScreen | 표현 선택 아래 | Web preview 70dvh/minHeight 360, Native preview 720. 이는 fixture viewport이며 제품은 실제 가용 높이 사용 |
47
47
  | 주 행동 | OnboardingScreen footer | 본문 scroll 바깥 | [기존 안내 배치](../components/onboarding-screen.md#배치) |
@@ -52,7 +52,7 @@ Motion Primitives Transition Panel의 두 제공 예제를 실제 검토한 후
52
52
  2. 테마를 순회하거나 나타남·떠오름·옆으로·확대·즉시를 선택한다. `preset` 생략은 테마 기본값, 명시 값은 우선한다.
53
53
  3. 단계별 완료 스토리에서 다음·이전으로 이동한다. 화면 전체를 key로 다시 마운트하지 않고 본문만 전환한다.
54
54
  4. 마지막 단계에서 완료 실패 재현을 예약하고 기록 확인을 누른다. 초안을 유지한 실패 알림 뒤 같은 완료를 다시 누른다.
55
- 5. 확인 결과와 초안을 보고 다시 비교한다. 완료는 로컬 fixture이며 서버 저장·라우팅 완료로 안내하지 않는다.
55
+ 5. 완료 시점의 확인 결과를 별도로 보관한다. 같은 예제의 탭 모드에서 초안을 수정해도 이미 확인한 결과는 바뀌지 않는다. 다시 비교하면 현재 초안을 유지한 첫 단계로 돌아가며 Web은 초안 입력에 초점을 옮긴다. 완료는 로컬 fixture이며 서버 저장·라우팅 완료로 안내하지 않는다.
56
56
 
57
57
  | 상태 | 모습 | 포커스·알림 |
58
58
  | --- | --- | --- |
@@ -4,7 +4,7 @@
4
4
  - 상태: 배포
5
5
  - 지원: Web · Native
6
6
  - 적용: 1.14.0
7
- - 검토일: 2026-10-07
7
+ - 검토일: 2026-10-13
8
8
  - 근거: [날짜 입력 조사](../../../../../docs/qa/2026-10-07-date-entry-reference.md), `src/date-entry.ts`
9
9
  - 스토리북: `배포/구성/입력과 작성/날짜 직접 입력`
10
10
 
@@ -14,13 +14,14 @@
14
14
 
15
15
  사용자가 알고 있는 날짜를 직접 입력할 때 쓴다. DatePicker는 달력에서 날짜를 선택하는 API이므로
16
16
  부분 연도·월 이름을 편집하는 초안을 담지 않는다. 새 DateEntry는 Field의 optional extension이며
17
- 기존 TextField를 합성한다. 달력·언어·시간대 계산은 제품이 맡는 기존 Calendar 경계를 유지한다.
17
+ 기존 FieldGroup의 `layout="inline"` 안에서 TextField를 합성한다. 달력·언어·시간대 계산은 제품이 맡는 기존 Calendar 경계를 유지한다.
18
18
 
19
19
  ## 구성 요소
20
20
 
21
21
  | 컴포넌트 | 역할 | 지침 |
22
22
  | --- | --- | --- |
23
23
  | DateEntry | 날짜 초안·필드 순서·오류 연결 | 이 문서 |
24
+ | FieldGroup | 그룹 이름·설명·대상별 오류 연결·입력 수명 | [관련 입력 묶음](field-group.md) |
24
25
  | TextField | 각 날짜 조각 입력·포커스·오류 | [Field](../components/field.md) |
25
26
  | Button | 제품의 확인/저장 | [Button](../components/button.md) |
26
27
 
@@ -29,8 +30,8 @@
29
30
  ```text
30
31
  그룹 이름
31
32
  설명(선택)
33
+ 오류 안내 한 번 ← 해당 칸 테두리로 연결
32
34
  [연도] [월] [일] ← 제품 order
33
- 오류 안내 한 번 ← 입력 위, 해당 칸 테두리로 연결
34
35
  [확인] ← 제품 행동
35
36
  결과/서버 상태 ← 제품 소유
36
37
  ```
@@ -59,8 +60,8 @@
59
60
  | 기본 | 세 입력과 설명 | 자동 초점 이동 없음 |
60
61
  | 진행 중 | 미완성 원문 유지 | 일반 Tab/터치로 이동, validation 노출은 제품 제어 |
61
62
  | 실패 | 그룹 오류 한 번과 해당 필드의 오류 테두리 | Web 연결된 오류 설명, Native 그룹+조각 이름·오류 hint, iOS 그룹 안내 한 번 |
62
- | 확인 | 제품에 전달한 valid 값 | 확인 UI/서버 저장은 제품 소유 |
63
- | 비활성·읽기 전용 | 편집 차단 | 호스트가 늦게 edit 이벤트를 보내도 callback 차단 |
63
+ | 확인 | 제품에 전달한 valid 값 | 확인 UI/서버 저장은 제품 소유. 결과 변경은 Web status, Native Android live region과 iOS announcement로 알림 |
64
+ | 비활성·읽기 전용 | 편집 차단 | 기존 callback도 새 잠금·읽기 전용을 따르며 언마운트 뒤 편집 차단 |
64
65
 
65
66
  ## 코드 골격
66
67
 
@@ -106,3 +107,7 @@ Android의 `birthdate-year/month/day`와 iOS의 명시적 `birthdateYear/Month/D
106
107
  - Calendar로 부분 입력을 강제로 변환하거나 NumberField로 교체하면 원문 보존 계약이 깨진다.
107
108
  - Web 390px 다크/RTL/2배 글자와 iOS 2배 글자에서 오류 복구를 확인했다. 제품 팔레트·스크린리더·자동완성 실제 검증은 남아 있다. 실험 등록은 승격·게시가 아니다.
108
109
  - Native 화면 호스트는 키보드 inset을 처리하는 ScrollView 등으로 하단 행동에 접근할 수 있어야 한다. DateEntry 내부에 중첩 스크롤을 만들지 않는다.
110
+
111
+ 2026-10-13 실제 독립 Story 검수에서 iOS 확인 결과 안내가 빠진 것을 확인해 기존 PatternStatus를 연결했다. 확인 후 필드 순서만 바꾸면 같은 날짜 결과를 유지하고, 원문을 편집하면 이전 결과를 지운다. 예제의 로컬 확인은 서버 저장을 뜻하지 않는다.
112
+
113
+ DateEntry는 그룹 설명을 모든 입력에, 그룹 오류를 영향받는 입력에만 연결한다. 재정렬은 입력 identity를 유지한다. 늦은 편집 callback도 최신 초안의 해당 조각만 바꾸며 그룹 잠금·언마운트는 FieldGroup의 edit session이 차단한다.
@@ -58,8 +58,8 @@ Section 제목/설명 → 선택 테마/다음 테마
58
58
  | 상태 | 모습 | 포커스·알림 |
59
59
  | --- | --- | --- |
60
60
  | 기본 | 세 선택값 없음·확인 비활성 | 날짜·시·분 모두 필수 이름. 셀 disabled는 API 계약으로 제어 |
61
- | 진행 중 | 확인 버튼 loading·입력/재설정 잠김 | placeholder를 서버 성공으로 바꾸지 않음; 이미 고른 값 유지 |
62
- | 실패 | danger Notice·다시 확인 | 같은 날짜·시·분 보존; Web alert·Native assertive; Web 응답 뒤 확인 버튼으로 포커스 복귀 |
61
+ | 진행 중 | 확인 버튼 loading·입력/재설정 잠김 | placeholder를 서버 성공으로 바꾸지 않음; 이미 고른 값 유지. Web 확인 버튼은 loading의 실행 차단을 사용해 키보드 초점을 보존 |
62
+ | 실패 | danger Notice·다시 확인 | 같은 날짜·시·분 보존; Web alert·Native assertive; Web에서 직접 누른 응답 버튼이 비활성화되면 확인 버튼으로 복귀. 사용자가 다른 곳으로 옮긴 초점은 유지 |
63
63
  | 성공 | success Notice·확정한 날짜와 시각 | 값 변경 시 이전 결과 제거. 실제 예약·서버 저장을 뜻하지 않음 |
64
64
  | 비활성 | 선택 트리거/행동 비활성 | CSS pointer-events로만 차단하지 않음 |
65
65
 
@@ -95,7 +95,7 @@ import { Select } from "@hjmds/react-native/forms";
95
95
  | --- | --- | --- |
96
96
  | 날짜 표면 | 필드에 붙은 popover | Sheet |
97
97
  | 시각 표면 | listbox popover | modal sheet |
98
- | 상태 | Text role=status | Showcase PatternStatus의 Android live region/iOS announce 보완. 제품은 공개Text/Notice와 같은 정책을 연결 |
98
+ | 상태 | Text role=status | 공개 StatusText의 Android live region/iOS 변경 알림. 제목·행동이 있는 안내는 Notice |
99
99
  | 날짜 키보드 | Calendar roving focus, 달 경계 callback은 제품 설정 | touch/AT, 실기기 확인 별도 |
100
100
 
101
101
  ## 함정
@@ -4,7 +4,7 @@
4
4
  - 상태: 배포
5
5
  - 지원: Web · Native
6
6
  - 적용: 1.16.0
7
- - 검토일: 2026-10-08
7
+ - 검토일: 2026-10-13
8
8
  - 근거: [프로필 계약](../../design-profile.md), [조사와 QA](../../../../../docs/qa/2026-10-07-design-profile-research.md), 두 Showcase `design-profile-preview.tsx`
9
9
  - 스토리북: `배포/구성/비교와 검증/테마 조합`
10
10
 
@@ -39,7 +39,7 @@
39
39
  표면 질감 비교: 장식 무늬 → Card 제목/설명 → 같은 초안 → 다음 테마
40
40
  탭 선택 표시 비교: 테마 따르기/밑줄/이동/늘어남 → 기록/보관함 → 같은 초안 → 다음 테마
41
41
  자산 액자 비교(기본 접힘): 같은 기존 Tick 그림 → 둥근/정사각/원형 → 다음 테마
42
- 입력·알림·오버레이 비교: Notice → Skeleton → Toast → Dialog/Sheet 열기
42
+ 입력·알림·오버레이 비교: Notice → Skeleton → Toast/알림 다시 보기 → Dialog/Sheet 열기
43
43
  오버레이: 제목/닫기 → 같은 초안 → 다음 테마(현재 10종 순환)
44
44
  10종 비교: 각 이름 → 같은 화면(현재 앱 설정도 함께 적용)
45
45
  ```
@@ -60,8 +60,8 @@
60
60
  나머지 표면/모서리/글자/질감은 고른 참고 테마를 상속한다. 두 설정은 제품 소유 설정 파일을 보여 주는 fixture이며 새 HJM 프리셋이 아니다.
61
61
  2. 도구 접기/펼치기를 확인한다. 항상 펼친 테마로 가면 내용이 보인다.
62
62
  3. 무늬 배경의 카드에 입력하고 다음 테마를 누른다. 유리·클레이 질감과 같은 초안 유지를 확인한다. Native 지원/접근성 설정에 따라 불투명 대체 경로도 확인한다.
63
- 4. 대화상자/패널을 열고 초안을 바꾼 뒤 다음 테마를 누른다. 열린 오버레이 안에서 프로필을 바꾸며 초안/문서 역할을 유지한다. 닫고 다시 열어도 제어 초안은 남는다.
64
- 5. 미리보기 저장 또는 실패 재현을 누른다. 실패 후 같은 입력을 재시도한다.
63
+ 4. 대화상자/패널을 열고 초안을 바꾼 뒤 다음 테마를 누른다. 열린 오버레이 안에서 프로필을 바꾸며 초안/문서 역할을 유지한다. 닫고 다시 열어도 제어 초안은 남고 닫기는 해당 열기 버튼으로 복귀한다. 토스트는 실제로 닫히며 테마 변경으로 다시 나타나지 않는다. `알림 다시 보기`로 명시적으로 재생한다.
64
+ 5. 미리보기 저장 또는 실패 재현을 누른다. 시작 시 이름·기간을 캡처하고 진행 중 중복 실행/늦은 실패 재현 콜백을 차단한다. 입력은 계속 편집할 수 있고 제출한 값과 달라지면 현재 입력은 미저장이라고 안내한다. 실패 후 `다시 저장`은 현재 편집한 이름·기간을 제출한다.
65
65
  6. 자산 액자 비교를 펼쳐 다음 테마를 누른다. 같은 기존 CC0 그림과 120px 액자를 유지하며
66
66
  rounded만 프로필을 따른다. 이 예제는 그림 재질·각도 자동 선택이나 Native 이미지 decode 검증의 완료 근거가 아니다.
67
67
 
@@ -70,7 +70,7 @@
70
70
  | 기본 | 저장 전 | 입력·선택 가능 |
71
71
  | 진행 중 | 저장 버튼 pending | 입력을 제거하지 않음 |
72
72
  | 실패 | 실패 문구·다시 저장 | 초안 유지·상태 알림 |
73
- | 성공 | 미리보기 저장 문구 | 서버 저장으로 안내하지 않음 |
73
+ | 성공 | 미리보기 저장 문구; 이후 수정된 초안은 미저장 안내 | 서버 저장으로 안내하지 않음 |
74
74
 
75
75
  ## 코드 골격
76
76
 
@@ -113,6 +113,8 @@ slide로 해석되고 나머지 6종은 standard다. 명시 값은 프로필보
113
113
 
114
114
  Web의 팝오버는 비모달 편집 초안과 테마 순회를 추가 비교한다. Native의 같은 용도는 기존 Sheet 경로다. 저장/실패 샘플은 두 플랫폼 공개 BottomCTA이며 브랜드가 바뀌어도 같은 저장 상태·초안·재시도 callback을 유지한다.
115
115
 
116
+ 2026-10-13 실제 예제 검수에서 저장 중 바꾼 입력도 저장됐다고 안내하고 Web 토스트 닫기 callback이 비어 있는 문제를 확인했다. 기존 action session으로 제출값/현재 초안을 구분하고 Web의 제어 토스트에 제거·재표시를 연결했다. Native 토스트는 기존 자체 lifecycle을 유지한다. 양 오버레이에 공개 `returnFocusRef`를 전달하고 Native 저장/FAB 결과에는 기존 iOS 안내·Android live region helper를 사용한다. Web 실패 재현 버튼은 pending에 disabled가 되면서 Chromium/Firefox 키보드 초점을 잃었으므로 기존 BottomCTA 보조 슬롯에 `Button aria-disabled`를 넣어 실행 차단과 초점을 유지한다. 로컬 지연은 기존 350ms 예제이며 unmount는 session을 분리해 늦은 결과를 무시한다. 실제 요청 취소·서버 저장을 뜻하지 않는다.
117
+
116
118
 
117
119
  ### 플로팅 행동 변형
118
120
 
@@ -131,7 +131,7 @@ import { Container, Stack, Text } from "@hjmds/react/layout";
131
131
 
132
132
  | 항목 | Web | Native |
133
133
  | --- | --- | --- |
134
- | `Collapsible` | `/collapsible`, 배치는 `layoutStyle`(또는 `className`) | `/collapsible`, 배치는 `layoutStyle`(`style`은 deprecated) |
134
+ | `Collapsible` | `/collapsible`, 배치는 `layoutStyle`(또는 `className`) | `/collapsible`, 배치는 `layoutStyle`(`style`은 제거된 속성) |
135
135
  | 대상 메뉴 | `ContextMenu`(stable) | `NativeContextMenu`(실험적 어댑터) |
136
136
  | `Menubar` | 있음 | 없음 |
137
137
 
@@ -4,7 +4,7 @@
4
4
  - 상태: 배포
5
5
  - 지원: Web · Native
6
6
  - 적용: 1.14.0
7
- - 검토일: 2026-10-07
7
+ - 검토일: 2026-10-13
8
8
  - 근거: [파일 원본 대조](../../../../../docs/qa/2026-10-07-file-reference.md), `src/document-resource.ts`
9
9
  - 스토리북: `배포/구성/정보 표시/문서와 파일`
10
10
 
@@ -52,6 +52,10 @@
52
52
  파일명은 줄바꿈한다. 미리보기 크기와 내용은 제품 host가 공급하며, 카드 전체를 링크/버튼으로
53
53
  감싸지 않는다. `moreAction`도 독립 행동이다. 상위 화면이 스크롤을 소유한다.
54
54
 
55
+ 미리보기와 내보내기는 각각 상태 문구·해당 행동을 한 영역으로 묶어 중앙 정렬한다.
56
+ 파일명·설명·실제 미리보기 콘텐츠의 정렬은 유지한다. 2026-10-13 실제 예제에서 오류와 로딩
57
+ 문구가 왼쪽에 붙어 있던 것을 확인해 공통 상태 중앙 배치 기준에 맞췄다.
58
+
55
59
  Web에서 초점을 가진 미리보기 재시도 버튼이 제거되면 같은 문서의 사용 가능한 미리보기
56
60
  버튼으로 복귀하며, 없으면 저장 버튼으로 옮긴다. 2026-10-07 실제 브라우저에서 제거된 버튼의
57
61
  초점이 body로 떨어진 회귀에 따른 규칙이다. 제품이 다른 문서로 바꾸거나 사용자가 이미 다른
@@ -60,6 +64,9 @@ Web에서 초점을 가진 미리보기 재시도 버튼이 제거되면 같은
60
64
  Showcase에는 긴 파일명 전환과 미리보기 준비 중·실패·없음 조작이 있다. 이름을 전환하면
61
65
  기존 action-session 결과를 reset한다. 미리보기 로딩은 저장을 자동으로 잠그지 않는다.
62
66
  저장 잠금 토글은 미리보기와 그 재시도를 유지하며 저장·저장 재시도만 잠근다.
67
+ Native 공유 모듈이 없는 경우에도 `saveDisabled`로 내보내기만 잠근다. 로컬 본문 읽기와
68
+ 미리보기 복구에 전체 `disabled`를 적용하지 않는다. 양 예제는 본문 펼침에 따라 보기/접기
69
+ 행동 이름을 바꾸고, 다른 문서로 전환하면 이전 미리보기 로딩·오류를 초기화한다.
63
70
 
64
71
  ## 흐름과 상태
65
72
 
@@ -73,7 +80,7 @@ Showcase에는 긴 파일명 전환과 미리보기 준비 중·실패·없음
73
80
  6. onSave/onRetrySave는 제품 action-session에 연결한다. 중복 실행은 session이 막으며 파일 교체·
74
81
  unmount에서 reset으로 이전 결과를 분리한다. reset은 실제 OS/서버 작업 취소가 아니다.
75
82
  7. 브라우저 anchor 시작이나 Native 공유 sheet 완료만으로 saved를 넣지 않는다. 실제 host가
76
- 확인한 저장 결과만 saved다. 예제 Web은 Blob 다운로드 시작, Native는 텍스트 공유 예제다.
83
+ 확인한 저장 결과만 saved다. 예제 Web은 Blob 다운로드 시작, Native는 자체 생성 TXT 파일 공유다.
77
84
 
78
85
  | 상태 | 모습 | 포커스·알림 |
79
86
  | --- | --- | --- |
@@ -121,4 +128,4 @@ Showcase의 첫 실패는 합성 fixture이며 운영 서버 실패가 아니다
121
128
  검증을 대신하지 않는다. HJM이 제품의 실패를 저장 성공으로 해석하지 않도록 action-session의
122
129
  success와 host 결과 started/saved/cancelled를 따로 연결한다. 오류를 내부 상태로 기록하고
123
130
  resolve하는 제품 세션은 Promise 완료만으로 성공을 판단하지 말고 세션 snapshot의 결과·오류를 읽는다. 제품 metadata에 URL·서버 오류 원문을
124
- 자동 노출하지 않는다. 아직 실험이며 전체 환경 검증·승격·npm 게시·소비 적용은 별도다.
131
+ 자동 노출하지 않는다. Storybook 분류는 배포다. 전체 환경 검증·npm 게시·소비 제품 적용은 별도 증거로 확인한다.
@@ -88,6 +88,7 @@
88
88
 
89
89
  ```tsx
90
90
  // Native
91
+ import { StatusText } from "@hjmds/react-native/status-text";
91
92
  import { useState } from "react";
92
93
  import { ScrollView, useWindowDimensions } from "react-native";
93
94
  import { spacing } from "@hjmds/design-contracts/foundations";
@@ -118,7 +119,7 @@ function FlowStatus() {
118
119
  </Stack>
119
120
  {visible ? (
120
121
  <ContentTransition stateKey={String(index)} preset="rise" motion={reduced ? "none" : "system"}>
121
- <StatusText>{t(flowStateKey[index] ?? flowStateKey[0])}</StatusText>{/* 저장과 재시도의 공통 절 helper */}
122
+ <StatusText>{t(flowStateKey[index] ?? flowStateKey[0])}</StatusText>{/* 공개 Text companion */}
122
123
  </ContentTransition>
123
124
  ) : null}
124
125
  </Stack>
@@ -129,7 +130,7 @@ function FlowStatus() {
129
130
  ```
130
131
 
131
132
  상태 목록·문구·동작 줄이기 설정의 저장 위치는 제품 소유다. 제품에 별도 설정이 없으면 토글을 두지 말고 `motion="system"`만 쓴다.
132
- `StatusText`는 [저장과 재시도](action-recovery-save.md#공통-세션과-상태-알림)의 helper(Android live region + iOS active일 때 바뀐 문구 알림)다.
133
+ `StatusText`는 [저장과 재시도](action-recovery-save.md#공통-세션과-상태-알림)에서 사용하는 공개 Text companion이다.
133
134
 
134
135
  ## 플랫폼 차이
135
136
 
@@ -67,7 +67,7 @@
67
67
 
68
68
  ## 흐름과 상태
69
69
 
70
- 1. 트리거를 누르면 단계를 0으로, 완료·실패 표시를 지우고 시트를 연다.
70
+ 1. 트리거는 중단한 단계와 실패 상태를 유지해 시트를 다시 연다. 이전 저장이 성공한 뒤 새로 시작할 때만 단계를 0으로 초기화한다.
71
71
  2. "계속"은 다음 단계로 간다. 첫 단계에서 "이전"은 `disabled`다.
72
72
  3. 마지막 단계에서 주 행동 라벨이 "설정 마치기"로 바뀐다. 누르면 결과를 저장한다. 저장이 비동기면 저장 중 Sheet `busy`로 닫기를 막고 주 행동은 `loading`이다.
73
73
  4. 저장이 성공하면 시트를 닫고 시작 화면에 완료 문구가 나타난다.
@@ -137,6 +137,7 @@ const isLast = step === stepIds.length - 1;
137
137
 
138
138
  ```tsx
139
139
  // Native
140
+ import { StatusText } from "@hjmds/react-native/status-text";
140
141
  import { ScrollView } from "react-native";
141
142
  import { spacing } from "@hjmds/design-contracts/foundations";
142
143
  import { Button } from "@hjmds/react-native/actions";
@@ -180,7 +181,7 @@ import { Steps } from "@hjmds/react-native/steps";
180
181
  ```
181
182
 
182
183
  단계 목록·문구·저장 동작은 제품 소유다. `gutter`는 폭 구간으로 고른다(`resolveWindowClass(width) === "compact" ? "compact" : "regular"`).
183
- `StatusText`는 [저장과 재시도](action-recovery-save.md#공통-세션과-상태-알림)의 helper이며, 완료 문구는 새로 나타나므로 첫 렌더에도 알리게 한다(스토리 `PatternStatus announceOnMount`).
184
+ `StatusText`는 [저장과 재시도](action-recovery-save.md#공통-세션과-상태-알림)에서 사용하는 공개 Text companion이며, 완료 문구는 새로 나타나므로 첫 렌더에도 알리게 한다(Native `StatusText announceOnMount`).
184
185
 
185
186
  ## 플랫폼 차이
186
187
 
@@ -195,7 +196,11 @@ import { Steps } from "@hjmds/react-native/steps";
195
196
  ## 함정
196
197
 
197
198
  - 저장을 시트 닫힘 사유와 묶지 않는다. 닫기·바깥·back 닫힘은 취소다.
198
- - 단계를 열 때마다 0으로 되돌린다. 이전 진행 상태가 남으면 마지막 단계에서 바로 열린다.
199
+ - 중단 후 다시 열면 진행 중이던 단계로 이어간다. 성공 뒤 새 작업을 시작할 때만 0으로 되돌린다. 영속 초안이 필요한 제품은 별도 저장소 정책을 연결한다.
199
200
  - 저장 중 `busy`를 빼면 사용자가 시트를 닫아 저장 결과를 볼 곳이 사라진다.
200
201
  - 현재 스토리는 시작 화면에 바깥 틀(Container·ScrollView)이 없고 Storybook 프레임이 감싼다. 제품은 위 바깥 틀 행을 따른다.
201
- - 현재 스토리는 저장이 동기라 진행 중·실패 상태가 없다.
202
+ - 스토리는 데모 비동기 저장의 진행 중·실패·재시도를 포함한다. 실제 서버 저장은 제품이 연결한다.
203
+
204
+ ## 실행 예제의 복구 경계
205
+
206
+ 2026-10-13 Web·Native 스토리는 `createActionSession` 기반 데모 저장을 연결한다. 마지막 단계 저장 중에는 이동·닫기를 잠그고, 실패하면 `Steps.currentStepStatus="error"`와 Notice를 표시한 채 현재 단계를 유지한다. 같은 주 버튼이 다시 저장을 수행한다. 중간에 닫았다 열면 현재 단계로 이어지고, 성공 뒤 다시 시작할 때만 첫 단계로 초기화한다. footer는 기본 Sheet 배치를 따르는 직접 자식이다. Web은 이전 → 주 행동, Native는 주 행동 → 이전이며 Native에서 가로 Stack으로 재정의하지 않는다. 실제 서버 저장·영속 초안·제품별 단계 입력은 포함하지 않는다.