@hjmds/design-contracts 1.32.0 → 1.33.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.
@@ -61,20 +61,12 @@ export declare function validateScreenExit(exit: Readonly<{
61
61
  export type ScreenContentInset = "default" | "none" | "body-edge" | "body-bleed";
62
62
  export declare const screenContentInsets: readonly ["default", "none", "body-edge", "body-bleed"];
63
63
  /**
64
- * 화면 진입 시 첫 입력 포커스(2026-10-18 사용자 요청: "input이 있는 화면에 진입하면 포커스되도록, 웹앱 둘 다").
65
- * 앱마다 autoFocus를 따로 붙이던 일을 화면 골격이 맡는다. 입력 칸은 가장 가까운 화면·시트 범위에 자신을 등록하고,
66
- * 범위가 진입이 끝난 뒤(Native: 전환·애니메이션 interaction 뒤, Web: mount 직후) 아래 규칙으로 한 번 고른다.
67
- *
68
- * - `first-input`: `initialFocusTarget`을 준 칸이 있으면 그 칸, 없으면 등록 순서상 첫 칸. 입력이 화면의 주 행동인 구성
69
- * (검색, 작성·편집)의 기본값이다.
70
- * - `single-input`: 등록된 텍스트 입력이 정확히 하나일 때만 그 칸. 시트·대화상자의 기본값이다 — 칸이 여럿이면 어느
71
- * 것이 주인지 알 수 없어 기존 포커스 규칙(첫 포커스 가능 요소·덜 파괴적인 행동)을 그대로 둔다.
72
- * - `none`: 고르지 않는다. 일반 화면과, 입력이 읽기의 보조인 대화·댓글 화면의 기본값이다 — 키보드가 올라오면 읽던
73
- * 기록을 가린다. 같은 prop으로 켤 수 있다.
74
- *
75
- * 비활성·읽기 전용 칸은 고르지 않고 `initialFocusTarget={false}` 칸은 제외한다. 화면 범위는 mount당 한 번만 포커스한다:
76
- * 스택에서 돌아온 화면(이미 mount된 화면)을 다시 포커스하면 키보드가 돌아온 맥락을 가리고, HJM은 내비게이션 포커스
77
- * 신호를 알지 못한다. 시트·대화상자는 열릴 때마다 다시 고른다. 모션 감소는 애니메이션에만 적용하고 포커스는 그대로 한다.
64
+ * 2026-10-18 사용자 지시: 입력 자동 포커스는 페이지 진입에만 적용하고 하단 시트에서는 폐지한다.
65
+ * 페이지는 `first-input`으로 명시한 주 입력 또는 첫 유효 입력을 한 번 고른다. 하단 시트는 renderer의
66
+ * 상속 경계에서 중첩 화면까지 자동 포커스를 막는다. Dialog·측면 시트의 모달 포커스 계약은 유지한다.
67
+ * `single-input`은 Dialog·측면 시트의 단일 입력 선택, `none`은 입력 자동 선택을 끈다.
68
+ * 비활성·읽기 전용·initialFocusTarget={false} 칸은 제외한다. 이미 포커스된 입력을 빼앗지 않으며
69
+ * 화면은 mount당 한 번만 포커스한다. 모션 감소는 포커스 정책을 바꾸지 않는다.
78
70
  */
79
71
  export type InitialFocusMode = "first-input" | "single-input" | "none";
80
72
  export type ScreenInitialFocus = "first-input" | "none";
@@ -88,13 +80,13 @@ export type InitialFocusCandidate = Readonly<{
88
80
  }>;
89
81
  /** Defaults per composition; renderers read these instead of repeating literals. */
90
82
  export declare const initialFocusDefaults: {
91
- readonly screen: "none";
83
+ readonly screen: "first-input";
92
84
  readonly search: "first-input";
93
85
  readonly editor: "first-input";
94
- readonly chat: "none";
95
- readonly commentThread: "none";
86
+ readonly chat: "first-input";
87
+ readonly commentThread: "first-input";
96
88
  readonly overlay: "single-input";
97
- readonly auth: "none";
89
+ readonly auth: "first-input";
98
90
  };
99
91
  export declare function resolveInitialFocusTarget<T extends InitialFocusCandidate>(candidates: readonly T[], mode: InitialFocusMode): T | null;
100
92
  /**
@@ -106,8 +98,7 @@ export declare function resolveInitialFocusTarget<T extends InitialFocusCandidat
106
98
  * - `open`: 진입이 끝났지만 아직 포커스하지 못했다. 칸이 새로 등록되거나 위 오버레이가 닫히면 다시 고른다. 모달 아래에서
107
99
  * 진입한 화면은 모달이 닫힐 때 사용자가 그 화면을 아직 만지지 않았으면 그때 포커스한다.
108
100
  * - `settled`: 포커스했거나, 사용자가 범위를 직접 조작(터치·포인터·키·휠)했거나, 이미 다른 텍스트 칸에 초점이 있다.
109
- * 이후에는 `initialFocusTarget`을 준 칸이 새로 나타날 때만 그 칸을 포커스한다 — 검색 열기·단계 이동처럼 사용자
110
- * 행동으로 드러난 주 입력이다. 이 규칙은 `initialFocus` 모드와 무관하다(`none` 화면의 검색 칸도 드러날 때 포커스).
101
+ * 이후에 입력이 새로 나타나도 자동 포커스를 다시 시작하지 않는다(2026-10-18 페이지 진입에만 적용).
111
102
  *
112
103
  * 위에 다른 오버레이가 열려 있는 동안(`blocked`)은 고르지 않고, 그 동안의 조작은 아래 범위의 조작으로 세지 않는다
113
104
  * (약관 동의 탭이 화면의 진입 포커스를 끝내면 안 된다). 텍스트 칸에 이미 초점이 있으면 빼앗지 않는다.
@@ -1 +1 @@
1
- {"version":3,"file":"screen-patterns.d.ts","sourceRoot":"","sources":["../src/screen-patterns.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAEzD,0FAA0F;AAC1F,MAAM,MAAM,kBAAkB,GAC1B,QAAQ,CAAC;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC,GAC3B,QAAQ,CAAC;IAAE,IAAI,EAAE,SAAS,GAAG,OAAO,GAAG,OAAO,GAAG,YAAY,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE1G,wBAAgB,yBAAyB,CAAC,KAAK,GAAE,kBAAsC;;;;;EAatF;AAED;;;;GAIG;AACH,eAAO,MAAM,kBAAkB;;;;;;IAM7B;;;OAGG;;;CAGK,CAAC;AAEX;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,OAAO,CAAC;AAC9C,eAAO,MAAM,gBAAgB;IAC3B,gHAAgH;;;;CAIxG,CAAC;AACX,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,EAAE,UAAU,EAAE,OAAO,QAItG;AAED;;;;;;;;;;GAUG;AACH,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,MAAM,GAAG,WAAW,GAAG,YAAY,CAAC;AACjF,eAAO,MAAM,mBAAmB,yDAAkG,CAAC;AAEnI;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,gBAAgB,GAAG,aAAa,GAAG,cAAc,GAAG,MAAM,CAAC;AACvE,MAAM,MAAM,kBAAkB,GAAG,aAAa,GAAG,MAAM,CAAC;AACxD,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC;IAC3C,wDAAwD;IACxD,KAAK,EAAE,MAAM,CAAC;IACd,0GAA0G;IAC1G,UAAU,EAAE,OAAO,GAAG,SAAS,CAAC;IAChC,8DAA8D;IAC9D,SAAS,EAAE,OAAO,CAAC;CACpB,CAAC,CAAC;AAEH,oFAAoF;AACpF,eAAO,MAAM,oBAAoB;;;;;;;;CASoB,CAAC;AAEtD,wBAAgB,yBAAyB,CAAC,CAAC,SAAS,qBAAqB,EAAE,UAAU,EAAE,SAAS,CAAC,EAAE,EAAE,IAAI,EAAE,gBAAgB,GAAG,CAAC,GAAG,IAAI,CASrI;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,UAAU,GAAG,MAAM,GAAG,SAAS,CAAC;AAC3E,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,qBAAqB;AACzD,sDAAsD;AACpD,QAAQ,CAAC;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC;AAChC,iDAAiD;GAC/C,QAAQ,CAAC;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,CAAC;AAClC,qCAAqC;GACnC,QAAQ,CAAC;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAC7B,kFAAkF;GAChF,QAAQ,CAAC;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAE,CAAC;AAC1C,yCAAyC;GACvC,QAAQ,CAAC;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC;AAC/B,iEAAiE;GAC/D,QAAQ,CAAC;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC,CAAC;AACnC,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,qBAAqB,IAAI,QAAQ,CAAC;IACtE,IAAI,EAAE,gBAAgB,CAAC;IACvB,iFAAiF;IACjF,UAAU,EAAE,SAAS,CAAC,EAAE,CAAC;IACzB,gDAAgD;IAChD,OAAO,EAAE,OAAO,CAAC;IACjB,gEAAgE;IAChE,gBAAgB,EAAE,OAAO,CAAC;CAC3B,CAAC,CAAC;AACH,MAAM,MAAM,oBAAoB,CAAC,CAAC,IAAI,QAAQ,CAAC;IAAE,KAAK,EAAE,eAAe,CAAC;IAAC,MAAM,EAAE,CAAC,GAAG,IAAI,CAAA;CAAE,CAAC,CAAC;AAE7F,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,qBAAqB,EAAE,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,eAAe,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG,oBAAoB,CAAC,CAAC,CAAC,CAkBxK;AAGD,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;CAmBtB,CAAC;AAEX,0GAA0G;AAC1G,MAAM,MAAM,yBAAyB,GAAG,QAAQ,CAAC;IAC/C,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,4EAA4E;IAC5E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,yFAAyF;IACzF,UAAU,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;IAChC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC,CAAC;AACH,4FAA4F;AAC5F,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,QAAQ,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,EAAE,IAAI,GAAE,SAAS,GAAG,MAAkB,GAAG,OAAO,CAEnL;AACD,wBAAgB,gBAAgB,CAAC,EAAE,KAAK,EAAE,QAAgB,EAAE,OAAe,EAAE,eAAmB,EAAE,EAAE,yBAAyB,GAAG,OAAO,CAGtI;AAED;;;;GAIG;AACH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;IAuB5B;;;OAGG;;;;;;;;CAEK,CAAC;AAEX,MAAM,MAAM,uBAAuB,GAAG,QAAQ,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAC7F,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC;IACzC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,SAAS,uBAAuB,EAAE,CAAC;IAC1C;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC,CAAC;AACH;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,mBAAmB,GAAG,SAAS,CAAC,uBAAuB,GAAG,QAAQ,CAAC;IAAE,SAAS,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC,EAAE,CAUzJ;AAED;;;;GAIG;AACH,eAAO,MAAM,qBAAqB;;;IAGhC,uFAAuF;;;;CAI/E,CAAC;AAEX;;;GAGG;AACH,eAAO,MAAM,uBAAuB;;;;;CAK1B,CAAC;AAEX,yGAAyG;AACzG,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAGhE;AAED;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB;IAC3B,wEAAwE;;IAExE,wDAAwD;;IAExD,oGAAoG;;;IAGpG;;;;OAIG;;;;;;;;CAEK,CAAC;AAEX,MAAM,MAAM,wBAAwB,GAAG,SAAS,GAAG,OAAO,CAAC;AAC3D,qGAAqG;AACrG,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,wBAAwB,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAGhG;AAED,oGAAoG;AACpG,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAGrE;AAED,iHAAiH;AACjH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,GAAG,IAAI,CAEjF;AAED,6GAA6G;AAC7G,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAG3E;AAED;;;;GAIG;AACH,4GAA4G;AAC5G,eAAO,MAAM,yBAAyB;;;;CAA4D,CAAC;AAEnG,eAAO,MAAM,cAAc;;;;;;;;;;CAUjB,CAAC;AAEX,iGAAiG;AACjG,MAAM,MAAM,wBAAwB,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;AAE9E;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,SAAS,QAAQ,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,EAAE,GAAG,WAAW,CAAC,MAAM,EAAE,wBAAwB,CAAC,CASvK;AAED,yGAAyG;AACzG,wBAAgB,8BAA8B,CAAC,QAAQ,GAAE,wBAAmC;;;;;;EAK3F;AAED,gHAAgH;AAChH,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC;IAC3C,SAAS,EAAE,UAAU,GAAG,UAAU,CAAC;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,yJAAyJ;IACzJ,qBAAqB,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC;IAC3C,mGAAmG;IACnG,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,yEAAyE;IACzE,aAAa,CAAC,EAAE,wBAAwB,CAAC;CAC1C,CAAC,CAAC;AAEH,8FAA8F;AAC9F,MAAM,MAAM,sBAAsB,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,aAAa,CAAC;AACrF,wBAAgB,uBAAuB,CAAC,MAAM,EAAC,sBAAsB,8CAEpE;AACD,wBAAgB,qBAAqB,CAAC,KAAK,EAAC,MAAM,EAAC,KAAK,EAAC,MAAM;;;EAG9D;AAED;;;;GAIG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;IAkB9B;;;;;;OAMG;;;;;CAEK,CAAC;AAEX;;;GAGG;AACH,MAAM,MAAM,yBAAyB,CAAC,GAAG,SAAS,MAAM,GAAG,MAAM,IAAI,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,EAAE,IAAI,GAAG,OAAO,GAAG,UAAU,GAAG,MAAM,CAAC,CAAC;AACzI,8HAA8H;AAC9H,wBAAgB,uBAAuB,CAAC,GAAG,SAAS,MAAM,EAAE,KAAK,EAAE,SAAS,yBAAyB,CAAC,GAAG,CAAC,EAAE,GAAG,SAAS,yBAAyB,CAAC,GAAG,CAAC,EAAE,CAOvJ;AAED,qGAAqG;AACrG,wBAAgB,qBAAqB,CAAC,KAAK,EAAC,SAAS;IAAC,EAAE,EAAC,MAAM,CAAC;IAAA,QAAQ,EAAC,MAAM,GAAC,IAAI,CAAA;CAAC,EAAE,QAGtF;AAED,0FAA0F;AAC1F,MAAM,MAAM,2BAA2B,GAAG,QAAQ,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC,CAAC,qEAAqE;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC;AAClL,wBAAgB,0BAA0B,CAAC,WAAW,EAAE,SAAS,2BAA2B,EAAE,GAAG,IAAI,CAMpG;AAED,wFAAwF;AACxF,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAE5D;AAED,wFAAwF;AACxF,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,QAAQ,CAAC;AAC/C,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAC,MAAM,CAAC;IAAC,OAAO,EAAC,MAAM,CAAC;IAAC,MAAM,EAAC,MAAM,CAAC;IAAC,MAAM,EAAC,MAAM,CAAA;CAAE,CAAC,CAAC;AAEzG,MAAM,MAAM,SAAS,GAAG,QAAQ,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAChE,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAC,CAAC;AAClG,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE5I,oGAAoG;AACpG,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,SAAS,EAAE,KAAK,EAAC,SAAS,CAAC,EAAE,EAAC,WAAW,EAAC,SAAS,eAAe,EAAE,EAAC,YAAY,EAAC,MAAM,GAAC,IAAI,GAAC,SAAS,EAAC,cAAc,EAAC,MAAM,GAAC,IAAI,GAAC,SAAS;;;YAJ3I,MAAM;eAAS,MAAM;iBAAW,SAAS,MAAM,EAAE;;;;EAa7F;AAED;;;;;;GAMG;AACH,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;AAC9D,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,MAAM,GAAG,iBAAiB,CAKlG;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGhE;AAED;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC;AAC5D,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,kBAAkB,EAAE,MAAM,GAAG,gBAAgB,CAO1G;AAED;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,OAAO,CAAC;AACnD,eAAO,MAAM,kBAAkB,8BAAsE,CAAC;AACtG;;;;;GAKG;AACH,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,iBAAiB,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,gBAAgB,EAAE,IAAI,GAAE,iBAA4B,GAAG,OAAO,CAI/J;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAKxF;AAED,2FAA2F;AAC3F,eAAO,MAAM,kBAAkB;IAC7B,8EAA8E;;IAE9E,8GAA8G;;IAE9G,kDAAkD;;IAElD,2DAA2D;;IAE3D,iDAAiD;;IAEjD,wEAAwE;;IAExE,8DAA8D;;CAEtD,CAAC;AAEX,8FAA8F;AAC9F,wBAAgB,uBAAuB,CAAC,KAAK,GAAE,MAAsC,GAAG,MAAM,CAG7F;AAED,oGAAoG;AACpG,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAGpE;AAED;oFACoF;AACpF,eAAO,MAAM,qBAAqB;;;;;;;;;;CAGxB,CAAC;AAEX,yEAAyE;AACzE,eAAO,MAAM,oBAAoB;;IAE/B;;;;OAIG;;;;;EAEH,CAAC;AAEH,mGAAmG;AACnG;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,MAAM,CAAC;AAC/C,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAC7D,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,MAAM,CAAC;AAC5C,eAAO,MAAM,cAAc;;;;;;;;IACzB,uFAAuF;YAClB,QAAQ,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;;;;eAE9D,aAAa;eAAuB,aAAa;cAAmB,YAAY;;EAAM,CAAC;AAEvI;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEzD,wHAAwH;AACxH,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAGjE"}
1
+ {"version":3,"file":"screen-patterns.d.ts","sourceRoot":"","sources":["../src/screen-patterns.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAEzD,0FAA0F;AAC1F,MAAM,MAAM,kBAAkB,GAC1B,QAAQ,CAAC;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC,GAC3B,QAAQ,CAAC;IAAE,IAAI,EAAE,SAAS,GAAG,OAAO,GAAG,OAAO,GAAG,YAAY,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE1G,wBAAgB,yBAAyB,CAAC,KAAK,GAAE,kBAAsC;;;;;EAatF;AAED;;;;GAIG;AACH,eAAO,MAAM,kBAAkB;;;;;;IAM7B;;;OAGG;;;CAGK,CAAC;AAEX;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,OAAO,CAAC;AAC9C,eAAO,MAAM,gBAAgB;IAC3B,gHAAgH;;;;CAIxG,CAAC;AACX,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,EAAE,UAAU,EAAE,OAAO,QAItG;AAED;;;;;;;;;;GAUG;AACH,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,MAAM,GAAG,WAAW,GAAG,YAAY,CAAC;AACjF,eAAO,MAAM,mBAAmB,yDAAkG,CAAC;AAEnI;;;;;;;GAOG;AACH,MAAM,MAAM,gBAAgB,GAAG,aAAa,GAAG,cAAc,GAAG,MAAM,CAAC;AACvE,MAAM,MAAM,kBAAkB,GAAG,aAAa,GAAG,MAAM,CAAC;AACxD,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC;IAC3C,wDAAwD;IACxD,KAAK,EAAE,MAAM,CAAC;IACd,0GAA0G;IAC1G,UAAU,EAAE,OAAO,GAAG,SAAS,CAAC;IAChC,8DAA8D;IAC9D,SAAS,EAAE,OAAO,CAAC;CACpB,CAAC,CAAC;AAEH,oFAAoF;AACpF,eAAO,MAAM,oBAAoB;;;;;;;;CASoB,CAAC;AAEtD,wBAAgB,yBAAyB,CAAC,CAAC,SAAS,qBAAqB,EAAE,UAAU,EAAE,SAAS,CAAC,EAAE,EAAE,IAAI,EAAE,gBAAgB,GAAG,CAAC,GAAG,IAAI,CASrI;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,UAAU,GAAG,MAAM,GAAG,SAAS,CAAC;AAC3E,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,qBAAqB;AACzD,sDAAsD;AACpD,QAAQ,CAAC;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC;AAChC,iDAAiD;GAC/C,QAAQ,CAAC;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,CAAC;AAClC,qCAAqC;GACnC,QAAQ,CAAC;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAC7B,kFAAkF;GAChF,QAAQ,CAAC;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAE,CAAC;AAC1C,yCAAyC;GACvC,QAAQ,CAAC;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC;AAC/B,iEAAiE;GAC/D,QAAQ,CAAC;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC,CAAC;AACnC,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,qBAAqB,IAAI,QAAQ,CAAC;IACtE,IAAI,EAAE,gBAAgB,CAAC;IACvB,iFAAiF;IACjF,UAAU,EAAE,SAAS,CAAC,EAAE,CAAC;IACzB,gDAAgD;IAChD,OAAO,EAAE,OAAO,CAAC;IACjB,gEAAgE;IAChE,gBAAgB,EAAE,OAAO,CAAC;CAC3B,CAAC,CAAC;AACH,MAAM,MAAM,oBAAoB,CAAC,CAAC,IAAI,QAAQ,CAAC;IAAE,KAAK,EAAE,eAAe,CAAC;IAAC,MAAM,EAAE,CAAC,GAAG,IAAI,CAAA;CAAE,CAAC,CAAC;AAE7F,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,qBAAqB,EAAE,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,eAAe,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG,oBAAoB,CAAC,CAAC,CAAC,CAiBxK;AAGD,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;CAmBtB,CAAC;AAEX,0GAA0G;AAC1G,MAAM,MAAM,yBAAyB,GAAG,QAAQ,CAAC;IAC/C,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,4EAA4E;IAC5E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,yFAAyF;IACzF,UAAU,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;IAChC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC,CAAC;AACH,4FAA4F;AAC5F,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,QAAQ,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,EAAE,IAAI,GAAE,SAAS,GAAG,MAAkB,GAAG,OAAO,CAEnL;AACD,wBAAgB,gBAAgB,CAAC,EAAE,KAAK,EAAE,QAAgB,EAAE,OAAe,EAAE,eAAmB,EAAE,EAAE,yBAAyB,GAAG,OAAO,CAGtI;AAED;;;;GAIG;AACH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;IAuB5B;;;OAGG;;;;;;;;CAEK,CAAC;AAEX,MAAM,MAAM,uBAAuB,GAAG,QAAQ,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAC7F,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC;IACzC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,SAAS,uBAAuB,EAAE,CAAC;IAC1C;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC,CAAC;AACH;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,mBAAmB,GAAG,SAAS,CAAC,uBAAuB,GAAG,QAAQ,CAAC;IAAE,SAAS,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC,EAAE,CAUzJ;AAED;;;;GAIG;AACH,eAAO,MAAM,qBAAqB;;;IAGhC,uFAAuF;;;;CAI/E,CAAC;AAEX;;;GAGG;AACH,eAAO,MAAM,uBAAuB;;;;;CAK1B,CAAC;AAEX,yGAAyG;AACzG,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAGhE;AAED;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB;IAC3B,wEAAwE;;IAExE,wDAAwD;;IAExD,oGAAoG;;;IAGpG;;;;OAIG;;;;;;;;CAEK,CAAC;AAEX,MAAM,MAAM,wBAAwB,GAAG,SAAS,GAAG,OAAO,CAAC;AAC3D,qGAAqG;AACrG,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,wBAAwB,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAGhG;AAED,oGAAoG;AACpG,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAGrE;AAED,iHAAiH;AACjH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,GAAG,IAAI,CAEjF;AAED,6GAA6G;AAC7G,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAG3E;AAED;;;;GAIG;AACH,4GAA4G;AAC5G,eAAO,MAAM,yBAAyB;;;;CAA4D,CAAC;AAEnG,eAAO,MAAM,cAAc;;;;;;;;;;CAUjB,CAAC;AAEX,iGAAiG;AACjG,MAAM,MAAM,wBAAwB,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;AAE9E;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,SAAS,QAAQ,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,EAAE,GAAG,WAAW,CAAC,MAAM,EAAE,wBAAwB,CAAC,CASvK;AAED,yGAAyG;AACzG,wBAAgB,8BAA8B,CAAC,QAAQ,GAAE,wBAAmC;;;;;;EAK3F;AAED,gHAAgH;AAChH,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC;IAC3C,SAAS,EAAE,UAAU,GAAG,UAAU,CAAC;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,yJAAyJ;IACzJ,qBAAqB,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC;IAC3C,mGAAmG;IACnG,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,yEAAyE;IACzE,aAAa,CAAC,EAAE,wBAAwB,CAAC;CAC1C,CAAC,CAAC;AAEH,8FAA8F;AAC9F,MAAM,MAAM,sBAAsB,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,aAAa,CAAC;AACrF,wBAAgB,uBAAuB,CAAC,MAAM,EAAC,sBAAsB,8CAEpE;AACD,wBAAgB,qBAAqB,CAAC,KAAK,EAAC,MAAM,EAAC,KAAK,EAAC,MAAM;;;EAG9D;AAED;;;;GAIG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;IAkB9B;;;;;;OAMG;;;;;CAEK,CAAC;AAEX;;;GAGG;AACH,MAAM,MAAM,yBAAyB,CAAC,GAAG,SAAS,MAAM,GAAG,MAAM,IAAI,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,EAAE,IAAI,GAAG,OAAO,GAAG,UAAU,GAAG,MAAM,CAAC,CAAC;AACzI,8HAA8H;AAC9H,wBAAgB,uBAAuB,CAAC,GAAG,SAAS,MAAM,EAAE,KAAK,EAAE,SAAS,yBAAyB,CAAC,GAAG,CAAC,EAAE,GAAG,SAAS,yBAAyB,CAAC,GAAG,CAAC,EAAE,CAOvJ;AAED,qGAAqG;AACrG,wBAAgB,qBAAqB,CAAC,KAAK,EAAC,SAAS;IAAC,EAAE,EAAC,MAAM,CAAC;IAAA,QAAQ,EAAC,MAAM,GAAC,IAAI,CAAA;CAAC,EAAE,QAGtF;AAED,0FAA0F;AAC1F,MAAM,MAAM,2BAA2B,GAAG,QAAQ,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC,CAAC,qEAAqE;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC;AAClL,wBAAgB,0BAA0B,CAAC,WAAW,EAAE,SAAS,2BAA2B,EAAE,GAAG,IAAI,CAMpG;AAED,wFAAwF;AACxF,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAE5D;AAED,wFAAwF;AACxF,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,QAAQ,CAAC;AAC/C,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAC,MAAM,CAAC;IAAC,OAAO,EAAC,MAAM,CAAC;IAAC,MAAM,EAAC,MAAM,CAAC;IAAC,MAAM,EAAC,MAAM,CAAA;CAAE,CAAC,CAAC;AAEzG,MAAM,MAAM,SAAS,GAAG,QAAQ,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAChE,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAC,CAAC;AAClG,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE5I,oGAAoG;AACpG,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,SAAS,EAAE,KAAK,EAAC,SAAS,CAAC,EAAE,EAAC,WAAW,EAAC,SAAS,eAAe,EAAE,EAAC,YAAY,EAAC,MAAM,GAAC,IAAI,GAAC,SAAS,EAAC,cAAc,EAAC,MAAM,GAAC,IAAI,GAAC,SAAS;;;YAJ3I,MAAM;eAAS,MAAM;iBAAW,SAAS,MAAM,EAAE;;;;EAa7F;AAED;;;;;;GAMG;AACH,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;AAC9D,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,MAAM,GAAG,iBAAiB,CAKlG;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGhE;AAED;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC;AAC5D,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,kBAAkB,EAAE,MAAM,GAAG,gBAAgB,CAO1G;AAED;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,OAAO,CAAC;AACnD,eAAO,MAAM,kBAAkB,8BAAsE,CAAC;AACtG;;;;;GAKG;AACH,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,iBAAiB,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,gBAAgB,EAAE,IAAI,GAAE,iBAA4B,GAAG,OAAO,CAI/J;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAKxF;AAED,2FAA2F;AAC3F,eAAO,MAAM,kBAAkB;IAC7B,8EAA8E;;IAE9E,8GAA8G;;IAE9G,kDAAkD;;IAElD,2DAA2D;;IAE3D,iDAAiD;;IAEjD,wEAAwE;;IAExE,8DAA8D;;CAEtD,CAAC;AAEX,8FAA8F;AAC9F,wBAAgB,uBAAuB,CAAC,KAAK,GAAE,MAAsC,GAAG,MAAM,CAG7F;AAED,oGAAoG;AACpG,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAGpE;AAED;oFACoF;AACpF,eAAO,MAAM,qBAAqB;;;;;;;;;;CAGxB,CAAC;AAEX,yEAAyE;AACzE,eAAO,MAAM,oBAAoB;;IAE/B;;;;OAIG;;;;;EAEH,CAAC;AAEH,mGAAmG;AACnG;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,MAAM,CAAC;AAC/C,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAC7D,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,MAAM,CAAC;AAC5C,eAAO,MAAM,cAAc;;;;;;;;IACzB,uFAAuF;YAClB,QAAQ,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;;;;eAE9D,aAAa;eAAuB,aAAa;cAAmB,YAAY;;EAAM,CAAC;AAEvI;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEzD,wHAAwH;AACxH,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAGjE"}
@@ -49,14 +49,14 @@ export function validateScreenExit(exit, hasLeading) {
49
49
  export const screenContentInsets = ["default", "none", "body-edge", "body-bleed"];
50
50
  /** Defaults per composition; renderers read these instead of repeating literals. */
51
51
  export const initialFocusDefaults = {
52
- screen: "none",
52
+ // 2026-10-18: input focus belongs to page entry, including chat/auth pages; bottom sheets opt out at their boundary.
53
+ screen: "first-input",
53
54
  search: "first-input",
54
55
  editor: "first-input",
55
- chat: "none",
56
- commentThread: "none",
56
+ chat: "first-input",
57
+ commentThread: "first-input",
57
58
  overlay: "single-input",
58
- // Auth screens lead with provider buttons; a signup nickname step opts in with `initialFocus="first-input"`.
59
- auth: "none",
59
+ auth: "first-input",
60
60
  };
61
61
  export function resolveInitialFocusTarget(candidates, mode) {
62
62
  if (mode === "none")
@@ -93,10 +93,8 @@ export function resolveEntryFocus(phase, event, facts) {
93
93
  return stay;
94
94
  if (phase === "open")
95
95
  return attempt();
96
- if (event.kind !== "register")
97
- return stay;
98
- const { field } = event;
99
- return field.preference === true && field.focusable && !facts.blocked && !facts.textEntryFocused ? { phase, target: field } : stay;
96
+ // Page entry only: revealing a marked field after interaction/entry must not open the keyboard again.
97
+ return stay;
100
98
  }
101
99
  // Reuse reading-width and spacing tokens; do not derive geometry from a product's device or artwork.
102
100
  export const screenPatternRecipe = {
@@ -1 +1 @@
1
- {"version":3,"file":"screen-patterns.js","sourceRoot":"","sources":["../src/screen-patterns.ts"],"names":[],"mappings":"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","sourcesContent":["import { glyph, layout, radius, spacing } from \"./foundations.js\";\nimport { avatarRecipe } from \"./component-recipes.js\";\nimport type { MenuItemDescriptor } from \"./behaviors.js\";\n\n/** Initial load replaces the body; refresh/save errors belong beside retained content. */\nexport type ScreenContentState =\n | Readonly<{ kind: \"ready\" }>\n | Readonly<{ kind: \"loading\" | \"empty\" | \"error\" | \"restricted\"; title: string; description?: string }>;\n\nexport function resolveScreenContentState(state: ScreenContentState = { kind: \"ready\" }) {\n if (![\"ready\", \"loading\", \"empty\", \"error\", \"restricted\"].includes(state.kind)) {\n throw new TypeError(\"Unknown screen content state\");\n }\n if (state.kind !== \"ready\" && !state.title.trim()) {\n throw new TypeError(\"Screen state requires a localized title\");\n }\n return {\n kind: state.kind,\n replacesContent: state.kind !== \"ready\",\n busy: state.kind === \"loading\",\n announcement: state.kind === \"error\" ? \"assertive\" : \"polite\",\n } as const;\n}\n\n/**\n * Native header band heights. Titled centered headers match the iOS navigation bar (44 control + 4 + 4);\n * icon-only centered headers are compact (2026-10-14 야잘알 tab spacing, 2026-10-17 \"상단 여백 줄여\").\n * Body top padding shrinks only under a compact header without a notice.\n */\nexport const screenHeaderRecipe = {\n minHeight: 52,\n paddingVertical: spacing.xxs,\n compactMinHeight: 48,\n compactPaddingVertical: 2,\n compactBodyTop: spacing.xxs,\n /**\n * Heading levels for the route title, shared by Web and Native (2026-10-17 comparison: Native drew the\n * title as regular-weight Text — 20 start / 18 centered — while Web used bold Heading levels).\n */\n titleLevel: \"level3\",\n centeredTitleLevel: \"level5\",\n} as const;\n\n/**\n * Leaving a screen (back, close, \"설정 나가기\") is an icon-only button fixed at the header's leading edge\n * (2026-10-17 user rule: exits had drifted into body text buttons with mixed glyphs). The label is read\n * by assistive tech only. `back` mirrors in RTL; `close` is for modal-style routes.\n */\nexport type ScreenExitKind = \"back\" | \"close\";\nexport const screenExitRecipe = {\n /** Chevron arm length and stroke; close cross arm length. Drawn, not font glyphs, so every platform matches. */\n chevronSize: 10,\n closeSize: 14,\n stroke: 2,\n} as const;\nexport function validateScreenExit(exit: Readonly<{ kind: string; label: string }>, hasLeading: boolean) {\n if (exit.kind !== \"back\" && exit.kind !== \"close\") throw new TypeError(\"Screen exit kind must be back or close\");\n if (!exit.label.trim()) throw new TypeError(\"Screen exit needs a localized accessible label\");\n if (hasLeading) throw new TypeError(\"Screen exit owns the leading slot; do not also pass leading\");\n}\n\n/**\n * Who owns the screen gutters. `default`: the shared shell pads header, notice, body and footer. `none`: the host\n * route already insets everything (≈45 product call sites rely on this meaning, so it cannot change).\n * `body-edge`: header, notice and footer keep the shell padding; only the body's inline padding is 0 so full-width\n * list rows (ConversationRow, ListRow-based items) supply their own inset — review S5#9 measured DM avatars ~32pt\n * from the edge because body padding and row padding stacked (2026-10-17 user decision).\n * `body-bleed`: header, notice and footer keep the shell padding; the body has no gutter on any side, so a map or\n * canvas meets the header (or notice), the screen edges and the footer. With no footer the body is still the last\n * region and clears the bottom insets the screen owns (`safeAreaBottom`, an overlay BottomNavigation) without the\n * gutter. choose-window's map screens used `body-edge` and kept a 16pt strip above and below the map (2026-10-18).\n */\nexport type ScreenContentInset = \"default\" | \"none\" | \"body-edge\" | \"body-bleed\";\nexport const screenContentInsets = [\"default\", \"none\", \"body-edge\", \"body-bleed\"] as const satisfies readonly ScreenContentInset[];\n\n/**\n * 화면 진입 시 첫 입력 포커스(2026-10-18 사용자 요청: \"input이 있는 화면에 진입하면 포커스되도록, 웹앱 둘 다\").\n * 앱마다 autoFocus를 따로 붙이던 일을 화면 골격이 맡는다. 입력 칸은 가장 가까운 화면·시트 범위에 자신을 등록하고,\n * 범위가 진입이 끝난 뒤(Native: 전환·애니메이션 interaction 뒤, Web: mount 직후) 아래 규칙으로 한 번 고른다.\n *\n * - `first-input`: `initialFocusTarget`을 준 칸이 있으면 그 칸, 없으면 등록 순서상 첫 칸. 입력이 화면의 주 행동인 구성\n * (검색, 작성·편집)의 기본값이다.\n * - `single-input`: 등록된 텍스트 입력이 정확히 하나일 때만 그 칸. 시트·대화상자의 기본값이다 — 칸이 여럿이면 어느\n * 것이 주인지 알 수 없어 기존 포커스 규칙(첫 포커스 가능 요소·덜 파괴적인 행동)을 그대로 둔다.\n * - `none`: 고르지 않는다. 일반 화면과, 입력이 읽기의 보조인 대화·댓글 화면의 기본값이다 — 키보드가 올라오면 읽던\n * 기록을 가린다. 같은 prop으로 켤 수 있다.\n *\n * 비활성·읽기 전용 칸은 고르지 않고 `initialFocusTarget={false}` 칸은 제외한다. 화면 범위는 mount당 한 번만 포커스한다:\n * 스택에서 돌아온 화면(이미 mount된 화면)을 다시 포커스하면 키보드가 돌아온 맥락을 가리고, HJM은 내비게이션 포커스\n * 신호를 알지 못한다. 시트·대화상자는 열릴 때마다 다시 고른다. 모션 감소는 애니메이션에만 적용하고 포커스는 그대로 한다.\n */\nexport type InitialFocusMode = \"first-input\" | \"single-input\" | \"none\";\nexport type ScreenInitialFocus = \"first-input\" | \"none\";\nexport type InitialFocusCandidate = Readonly<{\n /** Registration order inside the scope (tree order). */\n order: number;\n /** `initialFocusTarget` on the field: true prefers it, false excludes it, undefined keeps it eligible. */\n preference: boolean | undefined;\n /** False for disabled or read-only fields at resolve time. */\n focusable: boolean;\n}>;\n\n/** Defaults per composition; renderers read these instead of repeating literals. */\nexport const initialFocusDefaults = {\n screen: \"none\",\n search: \"first-input\",\n editor: \"first-input\",\n chat: \"none\",\n commentThread: \"none\",\n overlay: \"single-input\",\n // Auth screens lead with provider buttons; a signup nickname step opts in with `initialFocus=\"first-input\"`.\n auth: \"none\",\n} as const satisfies Record<string, InitialFocusMode>;\n\nexport function resolveInitialFocusTarget<T extends InitialFocusCandidate>(candidates: readonly T[], mode: InitialFocusMode): T | null {\n if (mode === \"none\") return null;\n const ordered = [...candidates].sort((a, b) => a.order - b.order);\n if (mode === \"single-input\") {\n const only = ordered.length === 1 ? ordered[0]! : null;\n return only && only.focusable && only.preference !== false ? only : null;\n }\n const eligible = ordered.filter(candidate => candidate.focusable && candidate.preference !== false);\n return eligible.find(candidate => candidate.preference === true) ?? eligible[0] ?? null;\n}\n\n/**\n * 진입 포커스 창(2026-10-18 1.28.1 앱 채택 점검). mount 시점 한 번만 고르면 세 경우가 앱 우회로 남았다: 진입 때 열린\n * 모달 아래 칸(번뚝 첫 가입 약관), 진입 중 비동기로 나타나는 칸(번뚝 계정 삭제 확인), 사용자 행동으로 나중에 나타나는 칸\n * (utilverse 검색 열기, 번뚝 아이디어 시트·spint 작성 단계). 그래서 범위는 아래 상태를 가진다.\n *\n * - `entering`: 진입 중(Native 전환·시트 등장 interaction 전). 칸 등록만 받는다.\n * - `open`: 진입이 끝났지만 아직 포커스하지 못했다. 칸이 새로 등록되거나 위 오버레이가 닫히면 다시 고른다. 모달 아래에서\n * 진입한 화면은 모달이 닫힐 때 사용자가 그 화면을 아직 만지지 않았으면 그때 포커스한다.\n * - `settled`: 포커스했거나, 사용자가 범위를 직접 조작(터치·포인터·키·휠)했거나, 이미 다른 텍스트 칸에 초점이 있다.\n * 이후에는 `initialFocusTarget`을 준 칸이 새로 나타날 때만 그 칸을 포커스한다 — 검색 열기·단계 이동처럼 사용자\n * 행동으로 드러난 주 입력이다. 이 규칙은 `initialFocus` 모드와 무관하다(`none` 화면의 검색 칸도 드러날 때 포커스).\n *\n * 위에 다른 오버레이가 열려 있는 동안(`blocked`)은 고르지 않고, 그 동안의 조작은 아래 범위의 조작으로 세지 않는다\n * (약관 동의 탭이 화면의 진입 포커스를 끝내면 안 된다). 텍스트 칸에 이미 초점이 있으면 빼앗지 않는다.\n */\nexport type EntryFocusPhase = \"inactive\" | \"entering\" | \"open\" | \"settled\";\nexport type EntryFocusEvent<T extends InitialFocusCandidate> =\n /** The scope mounted (screen) or opened (overlay). */\n | Readonly<{ kind: \"activate\" }>\n /** The overlay closed or the scope unmounted. */\n | Readonly<{ kind: \"deactivate\" }>\n /** The enter transition finished. */\n | Readonly<{ kind: \"enter\" }>\n /** A field mounted into the scope (first registration of that field instance). */\n | Readonly<{ kind: \"register\"; field: T }>\n /** An overlay above the scope closed. */\n | Readonly<{ kind: \"unblock\" }>\n /** User touch, pointer, key or wheel input reached the scope. */\n | Readonly<{ kind: \"interact\" }>;\nexport type EntryFocusFacts<T extends InitialFocusCandidate> = Readonly<{\n mode: InitialFocusMode;\n /** Every field registered in the scope now, including a newly registered one. */\n candidates: readonly T[];\n /** Another overlay is open above this scope. */\n blocked: boolean;\n /** A text field already has focus (the user's or the app's). */\n textEntryFocused: boolean;\n}>;\nexport type EntryFocusResolution<T> = Readonly<{ phase: EntryFocusPhase; target: T | null }>;\n\nexport function resolveEntryFocus<T extends InitialFocusCandidate>(phase: EntryFocusPhase, event: EntryFocusEvent<T>, facts: EntryFocusFacts<T>): EntryFocusResolution<T> {\n const stay = { phase, target: null };\n if (event.kind === \"deactivate\") return { phase: \"inactive\", target: null };\n if (event.kind === \"activate\") return { phase: \"entering\", target: null };\n if (phase === \"inactive\") return stay;\n if (event.kind === \"interact\") return facts.blocked || phase === \"settled\" ? stay : { phase: \"settled\", target: null };\n const attempt = (): EntryFocusResolution<T> => {\n if (facts.blocked) return { phase: \"open\", target: null };\n if (facts.textEntryFocused) return { phase: \"settled\", target: null };\n const target = resolveInitialFocusTarget(facts.candidates, facts.mode);\n return target ? { phase: \"settled\", target } : { phase: \"open\", target: null };\n };\n if (event.kind === \"enter\") return phase !== \"entering\" ? stay : facts.mode === \"none\" ? { phase: \"settled\", target: null } : attempt();\n if (phase === \"entering\") return stay;\n if (phase === \"open\") return attempt();\n if (event.kind !== \"register\") return stay;\n const { field } = event;\n return field.preference === true && field.focusable && !facts.blocked && !facts.textEntryFocused ? { phase, target: field } : stay;\n}\n\n// Reuse reading-width and spacing tokens; do not derive geometry from a product's device or artwork.\nexport const screenPatternRecipe = {\n maxWidth: layout.readingMaxWidth,\n padding: spacing.md,\n sectionGap: spacing.xl,\n itemGap: spacing.sm,\n stateGap: spacing.md,\n // Compact screens must keep back/title/save on one row; scale this floor for large text instead of reserving a desktop-width title.\n headerMinWidth: layout.readingMaxWidth / 6,\n // Reserve the opposite edge for authorship and cap the composer so history remains visible.\n messageMaxWidth: \"84%\",\n // Social thumbnails stay compact; selection order and removal replace a second toolbar.\n attachmentSize: 80,\n attachmentRemoveSize: 24,\n reactionHoldMs: 450,\n reactionMoveTolerance: 10,\n reactionMenuWidth: 320,\n replySwipeDistance: 60,\n composerMinLines: 1,\n composerMaxLines: 5,\n} as const;\n\n/** Controlled composer: HJM never clears drafts or interprets a fulfilled callback as server delivery. */\nexport type MessageComposerDescriptor = Readonly<{\n value: string;\n label: string;\n sendLabel: string;\n /** Short visual copy may differ from the complete accessible input name. */\n placeholder?: string;\n description?: string;\n error?: string;\n invalid?: boolean;\n /** Newline remains the compatible default; desktop chat may explicitly opt into send. */\n submitMode?: \"newline\" | \"send\";\n disabled?: boolean;\n pending?: boolean;\n attachmentCount?: number;\n}>;\n/** Safari may report the final IME Enter as keyCode 229 after isComposing becomes false. */\nexport function shouldSubmitMessageKey(event: Readonly<{ key: string; shiftKey?: boolean; isComposing?: boolean; keyCode?: number }>, mode: \"newline\" | \"send\" = \"newline\"): boolean {\n return mode === \"send\" && event.key === \"Enter\" && !event.shiftKey && !event.isComposing && event.keyCode !== 229;\n}\nexport function canSubmitMessage({ value, disabled = false, pending = false, attachmentCount = 0 }: MessageComposerDescriptor): boolean {\n if (!Number.isSafeInteger(attachmentCount) || attachmentCount < 0) throw new RangeError(\"Attachment count must be a nonnegative integer\");\n return !disabled && !pending && (value.trim().length > 0 || attachmentCount > 0);\n}\n\n/**\n * Shared timeline geometry. 2026-10-16 user decision: the common chat standard follows the measured\n * KakaoTalk iOS chat room (@3x capture: bubble ~35pt tall, radius ~14, run gap ~6, group gap ~12,\n * avatar ~34, avatar gap 8). Brand colors and artwork are not copied; surfaces stay semantic tokens.\n */\nexport const chatMessageRecipe = {\n // Must be a size the Avatar renders on both platforms: 36 was no Web Avatar step (32/40/48/64), so Web drew a 32\n // avatar in a 36 slot while Native drew 36 (2026-10-17 measured audit). `small` is the step nearest the ~34 reference.\n avatarSize: avatarRecipe.sizes.small,\n avatarGap: spacing.xs,\n authorGap: spacing.xxs,\n metaGap: spacing.xxs,\n bubbleRadius: 14,\n bubblePaddingInline: 10,\n bubblePaddingBlock: 6,\n // Bubbles of one run read as a stack; a new author or minute starts a visibly separate group.\n runGap: 6,\n groupGap: spacing.sm,\n // Flat-topped tip continuing the bubble's top edge; the tipped corner tightens to meet it.\n tailWidth: 7,\n tailHeight: 10,\n tailCornerRadius: 4,\n jumpToLatestGap: spacing.xs,\n // Timeline side gutter (measured 10pt); header and composer keep the screen padding.\n timelineInset: 10,\n // Photos render without the bubble surface (measured reference: ~210pt wide, bubble radius).\n mediaWidth: 210,\n mediaGap: 2,\n /**\n * Reaction summary pill beside the time (2026-10-18 BurnTok DM): one button that opens the same hold menu.\n * Counts below `minCount` show the emoji alone, as the measured DM references do for a single reaction.\n */\n reactionSummary: { minHeight: 24, paddingInline: spacing.xs, itemGap: spacing.xxs, emojiGap: 2, minCount: 2 },\n} as const;\n\nexport type ChatReactionSummaryItem = Readonly<{ id: string; emoji: string; count: number }>;\nexport type ChatReactionSummary = Readonly<{\n label: string;\n items: readonly ChatReactionSummaryItem[];\n /**\n * Counts only, for readers who cannot react (2026-10-18 Utilverse guests): drawn in the same place beside the time\n * as non-interactive text named by `label`, without the pill surface, so it does not look or act like a control.\n * Default `false` (the pill button that opens the hold menu).\n */\n readOnly?: boolean;\n}>;\n/**\n * Validates ChatMessage `reactions.summary` and marks which counts are drawn. The label (the button's accessible name)\n * is required; ids are unique and non-empty, emoji non-empty and counts positive integers; a count below\n * `reactionSummary.minCount` shows the emoji alone.\n */\nexport function resolveChatReactionSummary({ label, items }: ChatReactionSummary): readonly (ChatReactionSummaryItem & Readonly<{ showCount: boolean }>)[] {\n if (!label.trim()) throw new TypeError(\"Reaction summary requires a localized label\");\n const ids = new Set<string>();\n return items.map(item => {\n if (!item.id.trim() || ids.has(item.id)) throw new TypeError(\"Reaction summary ids must be unique and non-empty\");\n if (!item.emoji.trim()) throw new TypeError(\"Reaction summary items require an emoji\");\n if (!Number.isSafeInteger(item.count) || item.count < 1) throw new RangeError(\"Reaction summary counts must be positive integers\");\n ids.add(item.id);\n return { ...item, showCount: item.count >= chatMessageRecipe.reactionSummary.minCount };\n });\n}\n\n/**\n * \"Someone is typing\" row in a chat timeline (2026-10-18 BurnTok DM). It reuses the incoming bubble geometry\n * (chatMessageRecipe) so it reads as the next incoming message; only the dots are its own values. Under reduced\n * motion the dots stay at full opacity instead of pulsing.\n */\nexport const typingIndicatorRecipe = {\n dotSize: 6,\n dotGap: spacing.xxs,\n /** One pulse of the three dots; each dot starts `staggerMs` after the previous one. */\n cycleMs: 1200,\n staggerMs: 160,\n restOpacity: 0.35,\n} as const;\n\n/**\n * Centered date (or time) row between timeline groups (2026-10-18 Utilverse chat, which drew it as a product\n * caption). The next message still owns its own group gap above it, so this row only adds the space before it.\n */\nexport const chatDateSeparatorRecipe = {\n paddingTop: spacing.sm,\n paddingBottom: spacing.xxs,\n textVariant: \"caption\",\n tone: \"muted\",\n} as const;\n\n/** Column count for a photo set: pairs read as 2-up, other counts as 3-up; one photo spans the width. */\nexport function resolveChatMediaColumns(count: number): 1 | 2 | 3 {\n if (!Number.isSafeInteger(count) || count < 1) throw new RangeError(\"Chat media requires at least one item\");\n return count === 1 ? 1 : count === 2 || count === 4 ? 2 : 3;\n}\n\n/**\n * Paged photo album for ChatMessage `mediaAlbum` and CommentThreadItem `mediaAlbum` (2026-10-18, Utilverse social\n * photos). Only the shown page is rendered, so a product that authorizes each image can fetch one photo at a time\n * instead of every photo in a timeline row. The counter row matches CarouselMotion `navigation=\"counter\"`:\n * previous icon · position · next icon under the page; the frame keeps the host's media width and radius.\n */\nexport const mediaAlbumRecipe = {\n /** Page frame width / height when the product gives no aspect ratio. */\n defaultAspectRatio: 1,\n /** Space between the page frame and the counter row. */\n controlGap: spacing.xxs,\n /** Previous/next are small ghost IconButtons so the counter row stays compact beside thumbnails. */\n controlSize: \"small\",\n controlGlyphSize: glyph.sm,\n /**\n * Loading / failed page drawn by `MediaAlbumPageStatus` (2026-10-18 Utilverse: the page frame was one accessible\n * image, so the loading text and retry button inside it were unreachable by screen readers). The status block is\n * centered in the frame (VQ-09); retry is a small secondary Button.\n */\n pageStatus: { gap: spacing.xs, padding: spacing.sm, textVariant: \"caption\", retryTone: \"secondary\", retrySize: \"small\" },\n} as const;\n\nexport type MediaAlbumPageStatusKind = \"loading\" | \"error\";\n/** A page status names what the reader is waiting for or what failed; the label is the only copy. */\nexport function validateMediaAlbumPageStatus(kind: MediaAlbumPageStatusKind, label: string): void {\n if (kind !== \"loading\" && kind !== \"error\") throw new TypeError(\"Media album page status must be loading or error\");\n if (!label.trim()) throw new TypeError(\"Media album page status requires a localized label\");\n}\n\n/** A controlled index outside the set is a contract error, like Carousel's unknown `currentKey`. */\nexport function validateMediaAlbum(count: number, index: number): void {\n if (!Number.isSafeInteger(count) || count < 1) throw new RangeError(\"Media album requires at least one page\");\n if (!Number.isSafeInteger(index) || index < 0 || index >= count) throw new RangeError(\"Media album index must point at an existing page\");\n}\n\n/** One message or comment carries one photo set: either the eager `media` previews or the paged `mediaAlbum`. */\nexport function validateMediaSetChoice(hasMedia: boolean, hasAlbum: boolean): void {\n if (hasMedia && hasAlbum) throw new TypeError(\"Pass either media or mediaAlbum, not both\");\n}\n\n/** Uncontrolled albums keep the nearest page when the product's photo set shrinks (moderation, deletion). */\nexport function resolveMediaAlbumIndex(count: number, index: number): number {\n if (!Number.isSafeInteger(count) || count < 1) throw new RangeError(\"Media album requires at least one page\");\n return Math.min(Math.max(0, Math.trunc(index)), count - 1);\n}\n\n/**\n * Long-press menu geometry measured from the Instagram iOS DM hold menu (2026-10-16 @3x capture):\n * full-width reaction strip ~60pt with ~30pt emoji, the pressed bubble itself as preview, and a ~200pt\n * action card aligned to the bubble's side with ~56pt rows. 12pt between the three blocks.\n */\n/** Emoji row above a comment composer (Instagram comment sheet, 2026-10-16): evenly spread 44pt targets. */\nexport const composerQuickInsertRecipe = { itemSize: 44, emojiSize: 26, gap: spacing.xs } as const;\n\nexport const holdMenuRecipe = {\n inset: 10,\n gap: spacing.sm,\n stripHeight: 60,\n stripItemSize: 48,\n emojiSize: 28,\n actionWidth: 200,\n actionRowHeight: 56,\n actionRadius: 18,\n iconSize: 24,\n} as const;\n\n/** Position inside a run of consecutive messages from one author within one displayed minute. */\nexport type ChatMessageGroupPosition = \"single\" | \"first\" | \"middle\" | \"last\";\n\n/**\n * Runs break when the author or the displayed timestamp changes, so each minute shows its time once\n * and a new minute re-introduces the author (KakaoTalk behavior). Products pass the same localized\n * timestamp string they render; HJM never parses dates.\n */\nexport function resolveChatMessageGroups(items: readonly Readonly<{ id: string; authorId: string; timestamp: string }>[]): ReadonlyMap<string, ChatMessageGroupPosition> {\n const positions = new Map<string, ChatMessageGroupPosition>();\n items.forEach((item, index) => {\n if (!item.id.trim() || positions.has(item.id)) throw new TypeError(\"Chat messages require unique, nonempty ids\");\n const same = (other: (typeof items)[number] | undefined) => !!other && other.authorId === item.authorId && other.timestamp === item.timestamp;\n const before = same(items[index - 1]), after = same(items[index + 1]);\n positions.set(item.id, before ? (after ? \"middle\" : \"last\") : after ? \"first\" : \"single\");\n });\n return positions;\n}\n\n/** What one bubble shows for its run position; both renderers read this instead of re-deriving rules. */\nexport function resolveChatMessageGroupDisplay(position: ChatMessageGroupPosition = \"single\") {\n if (![\"single\", \"first\", \"middle\", \"last\"].includes(position)) throw new TypeError(\"Unknown chat message group position\");\n const starts = position === \"single\" || position === \"first\";\n const ends = position === \"single\" || position === \"last\";\n return { showAuthor: starts, showAvatar: starts, showTail: starts, showTimestamp: ends, spacingBefore: starts ? chatMessageRecipe.groupGap : chatMessageRecipe.runGap } as const;\n}\n\n/** Bubble alignment expresses authorship, not delivery; delivery text always comes from the product receipt. */\nexport type ChatMessageDescriptor = Readonly<{\n direction: \"incoming\" | \"outgoing\";\n author: string;\n timestamp: string;\n deliveryLabel?: string;\n /** Inward drag reveals time (incoming right, outgoing left) while delivery state remains visible; default shows time beside the last bubble of a run. */\n timestampPresentation?: \"always\" | \"swipe\";\n /** Authorship tip on the run's first bubble. Default on since the 2026-10-16 timeline standard. */\n bubbleTail?: boolean;\n /** From resolveChatMessageGroups; omitted means a standalone message. */\n groupPosition?: ChatMessageGroupPosition;\n}>;\n\n/** A denied permission goes to settings; rendering never requests OS access automatically. */\nexport type PermissionScreenStatus = \"prompt\" | \"denied\" | \"granted\" | \"unavailable\";\nexport function resolvePermissionAction(status:PermissionScreenStatus) {\n switch(status){case \"prompt\":return \"request\";case \"denied\":return \"settings\";case \"granted\":return \"continue\";case \"unavailable\":return null;default:throw new TypeError(\"Unknown permission state\");}\n}\nexport function resolveOnboardingStep(total:number,index:number){\n if(!Number.isInteger(total)||total<1||!Number.isInteger(index)||index<0||index>=total)throw new RangeError(\"Onboarding requires a valid step index\");\n return {first:index===0,last:index===total-1};\n}\n\n/**\n * Shared comment-row geometry. 2026-10-16 user decision: comments follow the measured Instagram iOS\n * comment sheet (@3x capture: avatar 32 / reply 24, avatar gap 8, like count under the heart,\n * \"Reply\" as quiet text below the body, replies indented to the text column). Artwork is not copied.\n */\nexport const commentThreadRecipe = {\n avatarSize: 32,\n replyAvatarSize: 24,\n avatarGap: spacing.xs,\n lineGap: 2,\n rowGap: spacing.sm,\n // Replies align with the parent's text column instead of an arbitrary section inset.\n replyIndent: 32 + spacing.xs,\n // The heart keeps the 44pt touch target even though the measured reference draws a smaller glyph.\n likeColumnWidth: 44,\n // Optional photo/sticker under the body; the measured reference keeps it thumbnail-sized.\n mediaMaxWidth: 160,\n mediaRadius: radius.md,\n // Inline per-comment panel (edit/report/block form) under the action row (2026-10-18, Utilverse): a form reads as its\n // own block, so it sits a full spacing step below the reply line instead of the 2pt line gap.\n panelGap: spacing.xs,\n // The trailing vertical-dots menu glyph; the button keeps the shared 44pt IconButton target.\n menuGlyphSize: glyph.sm,\n /**\n * Trailing actions (like, product actions, more) centre their 44 targets on the identity's first line instead of\n * top-aligning the target box (2026-10-17 measured audit: avatar 168 / author line 162 / glyphs 174). The cluster\n * shifts by (first-line height − visible target) / 2 — negative while the line is shorter than the target — and the\n * last control takes IconButton `edge=\"end\"` so its glyph slot ends on the row inset like the avatar starts on it.\n * The first line is the author caption, or the body line when the author runs inline with `bodyText`.\n */\n actionAnchor: { authorTextVariant: \"caption\", inlineTextVariant: \"body\" },\n} as const;\n\n/**\n * Items of the HJM-owned per-comment \"more\" menu (2026-10-17: message/comment structure belongs to HJM, review S4#7).\n * Same vocabulary as Menu/ContextMenu; the product supplies only ids, localized labels, tone and availability.\n */\nexport type CommentMenuItemDescriptor<Key extends string = string> = Pick<MenuItemDescriptor<Key>, \"id\" | \"label\" | \"disabled\" | \"tone\">;\n/** Danger actions (delete, block) always sit last so a destructive row is never the first target; order is otherwise kept. */\nexport function resolveCommentMenuItems<Key extends string>(items: readonly CommentMenuItemDescriptor<Key>[]): readonly CommentMenuItemDescriptor<Key>[] {\n const ids = new Set<string>();\n for (const item of items) {\n if (!item.id.trim() || ids.has(item.id) || !item.label.trim()) throw new TypeError(\"Comment menu items require unique ids and localized labels\");\n ids.add(item.id);\n }\n return [...items.filter(item => item.tone !== \"danger\"), ...items.filter(item => item.tone === \"danger\")];\n}\n\n/** Replies are one level deep; reject orphaned/duplicate records instead of silently losing them. */\nexport function validateCommentThread(items:readonly {id:string;parentId:string|null}[]){\n const roots=new Set(items.filter(item=>item.parentId===null).map(item=>item.id));const ids=new Set<string>();\n for(const item of items){if(!item.id.trim()||ids.has(item.id))throw new TypeError(\"Comment ids must be unique and nonempty\");ids.add(item.id);if(item.parentId!==null&&(!roots.has(item.parentId)||item.parentId===item.id))throw new TypeError(\"Replies require an existing root comment\");}\n}\n\n/** Source bytes, permission requests, ordering and upload limits remain product-owned. */\nexport type MessageAttachmentDescriptor = Readonly<{ id: string; removeLabel: string; /** Local preparation can lock removal without locking text entry. */ disabled?: boolean }>;\nexport function validateMessageAttachments(attachments: readonly MessageAttachmentDescriptor[]): void {\n const ids = new Set<string>();\n for (const item of attachments) {\n if (!item.id.trim() || ids.has(item.id) || !item.removeLabel.trim()) throw new TypeError(\"Attachments require unique ids and localized removal labels\");\n ids.add(item.id);\n }\n}\n\n/** Require horizontal intent so a timeline scroll cannot accidentally start a reply. */\nexport function isReplySwipe(dx: number, dy: number): boolean {\n return Math.abs(dx) >= screenPatternRecipe.replySwipeDistance && Math.abs(dx) > Math.abs(dy) * 2;\n}\n\n/** The UI never infers permission or camera availability; product adapters own both. */\nexport type PhotoSource = \"library\" | \"camera\";\nexport type PhotoSourceLabels = Readonly<{ title:string; library:string; camera:string; cancel:string }>;\n\nexport type SavedItem = Readonly<{ id: string; title: string }>;\nexport type SavedCollection = Readonly<{ id: string; title: string; itemIds: readonly string[] }>;\nexport type SavedItemsLabels = Readonly<{ allItems: string; privateNotice: string; back: string; empty: string; createCollection: string }>;\n\n/** Unsave removes an item from every collection view without rewriting product-owned membership. */\nexport function resolveSavedItems<T extends SavedItem>(items:readonly T[],collections:readonly SavedCollection[],collectionId:string|null|undefined,selectedItemId:string|null|undefined){\n const ids=new Set<string>(),groups=new Set<string>();\n for(const item of items){if(!item.id.trim()||ids.has(item.id)||!item.title.trim())throw new TypeError(\"Saved items require unique ids and nonempty titles\");ids.add(item.id);}\n for(const group of collections){if(!group.id.trim()||groups.has(group.id)||!group.title.trim())throw new TypeError(\"Saved collections require unique ids and nonempty titles\");groups.add(group.id);}\n const collection=collections.find(item=>item.id===collectionId);\n // A deleted collection returns to the overview instead of showing a mislabeled all-posts view.\n const home=collectionId===undefined||(collectionId!==null&&!collection);\n const visible=home||collectionId===null?items:items.filter(item=>collection?.itemIds.includes(item.id));\n return {home,collection,visible,selected:home?undefined:visible.find(item=>item.id===selectedItemId)};\n}\n\n/**\n * SearchScreen phases (2026-10-06 search redesign, docs/screen-patterns.md#searchscreen-두-단계-검색).\n * Without a committed query (one-step search, the pre-1.13 behavior) any nonblank query is already\n * `results`. With one, typing a different query is `typing`: suggestions replace the results until the\n * user commits (Enter, a suggestion, a recent or suggested query). Comparing trimmed values keeps a\n * trailing space from flipping committed results back into suggestions.\n */\nexport type SearchScreenPhase = \"idle\" | \"typing\" | \"results\";\nexport function resolveSearchScreenPhase(query: string, committedQuery?: string): SearchScreenPhase {\n const typed = query.trim();\n if (!typed) return \"idle\";\n if (committedQuery === undefined) return \"results\";\n return typed === committedQuery.trim() ? \"results\" : \"typing\";\n}\n\n/**\n * The value a commit records, or `null` when there is nothing to commit. Blank commits are dropped\n * so a stray Enter never writes an empty recent search or fetches \"everything\".\n */\nexport function resolveSearchCommit(value: string): string | null {\n const next = value.trim();\n return next ? next : null;\n}\n\n/**\n * Why a committed search shows nothing. `filters` keeps the filter rail and offers \"clear all\";\n * `query` hides the rail (no filter can fix it) and offers suggested queries instead. Reference\n * services hide the rail on every zero result, which removes the only way to undo a filter.\n * `null` count means the result is still loading, so no cause is reported yet.\n */\nexport type SearchEmptyCause = \"none\" | \"filters\" | \"query\";\nexport function resolveSearchEmptyCause(count: number | null, appliedFilterCount: number): SearchEmptyCause {\n // Loading/nonempty results must not bypass the same count contract used by empty results.\n if ((count !== null && (count < 0 || !Number.isSafeInteger(count))) || !Number.isSafeInteger(appliedFilterCount) || appliedFilterCount < 0) {\n throw new RangeError(\"Search counts must be nonnegative integers\");\n }\n if (count === null || count > 0) return \"none\";\n return appliedFilterCount > 0 ? \"filters\" : \"query\";\n}\n\n/**\n * What the SearchScreen filter rail does to a result. `narrow` (default) only removes results, so with nothing applied\n * a zero result is the query's and no filter can bring results back. `scope` chooses what is searched (keyword vs\n * address, region, map area): changing it can turn a zero into results even with nothing applied (2026-10-18:\n * choose-window's keyword/address switch is the recovery for a zero result, so it skipped resultSummary entirely).\n */\nexport type SearchFiltersRole = \"narrow\" | \"scope\";\nexport const searchFiltersRoles = [\"narrow\", \"scope\"] as const satisfies readonly SearchFiltersRole[];\n/**\n * Whether SearchScreen shows its filter rail (`filters` and the filter sheet trigger). Two-step search shows it only\n * beside results so typing keeps the pinned area short. A query-caused zero hides it only when the filters narrow,\n * because then none of them can fix the zero; scope filters stay as the recovery path. A filter-caused zero always keeps\n * it so the cause can be undone. Both renderers call this so the rule cannot drift between platforms.\n */\nexport function resolveSearchFilterRailVisible(phase: SearchScreenPhase, twoStep: boolean, cause: SearchEmptyCause, role: SearchFiltersRole = \"narrow\"): boolean {\n if (!searchFiltersRoles.includes(role)) throw new RangeError(\"SearchScreen filtersRole must be narrow or scope\");\n if (twoStep && phase !== \"results\") return false;\n return cause !== \"query\" || role === \"scope\";\n}\n\n/**\n * Index to focus after removing item `removedIndex` from a list that now has `remaining` items:\n * the item that slid into its place, else the new last item, else -1 (move to the fallback target).\n * Removing a focused chip or row would otherwise drop focus to <body>.\n */\nexport function resolveFocusAfterRemoval(removedIndex: number, remaining: number): number {\n if (!Number.isInteger(removedIndex) || removedIndex < 0 || !Number.isInteger(remaining) || remaining < 0) {\n throw new RangeError(\"Removal indexes must be nonnegative integers\");\n }\n return remaining === 0 ? -1 : Math.min(removedIndex, remaining - 1);\n}\n\n/** Recipe for the SearchScreen phase sections; geometry stays on shared spacing tokens. */\nexport const searchScreenRecipe = {\n /** Debounced typing delay; explicit submit remains a separate host action. */\n debounceMs: 300,\n /** Recent searches shown before the user types; reference services show 5–10 and let the rest scroll away. */\n recentVisible: 5,\n /** Suggestions under the \"search for ‘q’\" row. */\n suggestionVisible: 6,\n /** Skeleton rows while the first page of results loads. */\n loadingRows: 4,\n /** Between idle sections (recent, suggested). */\n sectionGap: spacing.xl,\n /** Section title to its list, and results header to applied filters. */\n headerGap: spacing.sm,\n /** Between chips in applied filters and suggested queries. */\n chipGap: spacing.xs,\n} as const;\n\n/** Keep finite negative delays at zero; reject values whose timer behavior varies by host. */\nexport function resolveSearchDebounceMs(value: number = searchScreenRecipe.debounceMs): number {\n if (!Number.isFinite(value)) throw new RangeError(\"Search debounceMs must be finite\");\n return Math.max(0, value);\n}\n\n/** Horizontal intent preserves vertical scrolling and uses one reveal distance across renderers. */\nexport function timestampRevealOffset(dx: number, dy: number): number {\n // A 12px dead zone and 2:1 direction bias avoid turning taps/list scrolling into a time gesture.\n return dx > 12 && dx > Math.abs(dy) * 2 ? Math.min(dx, 88) : 0;\n}\n\n/** Notification previews need an actor/type badge and a separate subject thumbnail.\n * Keep this geometry shared so inboxes do not recreate the same row per product. */\nexport const notificationRowRecipe = {\n gap: 12, contentGap: 4, avatarSize: 44, avatarBadgeSize: 20,\n thumbnailSize: 44, unreadSize: 6, previewLines: 2, paddingBlock: 12, paddingInline: 16,\n} as const;\n\n/** Shared profile identity geometry; product owns the avatar content. */\nexport const profileSummaryRecipe = Object.freeze({\n avatarSize: 72,\n /**\n * ProfileScreen `editPlacement=\"summary\"` draws the edit action first in the summary's action row. A framed small\n * button reads as the card's own action; the below-summary placement keeps its ghost button (2026-10-15 utilverse\n * account decision: the edit action belongs inside the profile card).\n */\n editAction: Object.freeze({ tone: \"secondary\", size: \"small\" } as const),\n});\n\n/** Launcher artwork stays above its caption; product overlays may extend beyond the icon frame. */\n/**\n * Frame of an IconTile icon (2026-10-18 Utilverse launcher). `surface` draws the shared filled, hairlined frame;\n * `none` leaves product artwork that is already a complete launcher icon unframed. `shape` follows the product's\n * icon theme (squircle on iPhone shells, square on Windows/Linux shells); `size: \"fill\"` lets the icon span its\n * grid cell between `fillMinSize` and `fillMaxSize` instead of a product computing the cell size.\n */\nexport type IconTileFrame = \"surface\" | \"none\";\nexport type IconTileShape = \"squircle\" | \"square\" | \"circle\";\nexport type IconTileSize = \"fixed\" | \"fill\";\nexport const iconTileRecipe = Object.freeze({ iconSize: 64, labelMaxWidth: 72, multilineLabelMaxWidth: 84, multilineLabelMinHeight: 44, ghostOpacity: 0.55, gap: 6, iconRadius: \"22%\",\n /** Corner radius as a fraction of the icon size; `squircle` keeps the original 22%. */\n shapes: Object.freeze({ squircle: 0.22, square: 0, circle: 0.5 }) as Readonly<Record<IconTileShape, number>>,\n fillMinSize: 48, fillMaxSize: 68,\n defaults: Object.freeze({ frame: \"surface\" as IconTileFrame, shape: \"squircle\" as IconTileShape, size: \"fixed\" as IconTileSize }) });\n\n/**\n * What an IconTile passes to an `icon` render function: the rendered icon edge (`iconSize` for `fixed`, the measured\n * cell-clamped edge for `fill`). Fixed-size artwork (images, generated launcher art) sizes itself from it; Utilverse\n * re-computed the 48–68 clamp from its grid width to size its artwork (2026-10-18).\n */\nexport type IconTileArtwork = Readonly<{ edge: number }>;\n\n/** The edge a `size=\"fill\"` icon takes in a cell of `cellWidth`, for layout estimates made before the tile measures. */\nexport function resolveIconTileFillEdge(cellWidth: number): number {\n if (!Number.isFinite(cellWidth) || cellWidth < 0) throw new RangeError(\"IconTile cell width must be a non-negative finite number\");\n return Math.min(iconTileRecipe.fillMaxSize, Math.max(iconTileRecipe.fillMinSize, cellWidth));\n}\n"]}
1
+ {"version":3,"file":"screen-patterns.js","sourceRoot":"","sources":["../src/screen-patterns.ts"],"names":[],"mappings":"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","sourcesContent":["import { glyph, layout, radius, spacing } from \"./foundations.js\";\nimport { avatarRecipe } from \"./component-recipes.js\";\nimport type { MenuItemDescriptor } from \"./behaviors.js\";\n\n/** Initial load replaces the body; refresh/save errors belong beside retained content. */\nexport type ScreenContentState =\n | Readonly<{ kind: \"ready\" }>\n | Readonly<{ kind: \"loading\" | \"empty\" | \"error\" | \"restricted\"; title: string; description?: string }>;\n\nexport function resolveScreenContentState(state: ScreenContentState = { kind: \"ready\" }) {\n if (![\"ready\", \"loading\", \"empty\", \"error\", \"restricted\"].includes(state.kind)) {\n throw new TypeError(\"Unknown screen content state\");\n }\n if (state.kind !== \"ready\" && !state.title.trim()) {\n throw new TypeError(\"Screen state requires a localized title\");\n }\n return {\n kind: state.kind,\n replacesContent: state.kind !== \"ready\",\n busy: state.kind === \"loading\",\n announcement: state.kind === \"error\" ? \"assertive\" : \"polite\",\n } as const;\n}\n\n/**\n * Native header band heights. Titled centered headers match the iOS navigation bar (44 control + 4 + 4);\n * icon-only centered headers are compact (2026-10-14 야잘알 tab spacing, 2026-10-17 \"상단 여백 줄여\").\n * Body top padding shrinks only under a compact header without a notice.\n */\nexport const screenHeaderRecipe = {\n minHeight: 52,\n paddingVertical: spacing.xxs,\n compactMinHeight: 48,\n compactPaddingVertical: 2,\n compactBodyTop: spacing.xxs,\n /**\n * Heading levels for the route title, shared by Web and Native (2026-10-17 comparison: Native drew the\n * title as regular-weight Text — 20 start / 18 centered — while Web used bold Heading levels).\n */\n titleLevel: \"level3\",\n centeredTitleLevel: \"level5\",\n} as const;\n\n/**\n * Leaving a screen (back, close, \"설정 나가기\") is an icon-only button fixed at the header's leading edge\n * (2026-10-17 user rule: exits had drifted into body text buttons with mixed glyphs). The label is read\n * by assistive tech only. `back` mirrors in RTL; `close` is for modal-style routes.\n */\nexport type ScreenExitKind = \"back\" | \"close\";\nexport const screenExitRecipe = {\n /** Chevron arm length and stroke; close cross arm length. Drawn, not font glyphs, so every platform matches. */\n chevronSize: 10,\n closeSize: 14,\n stroke: 2,\n} as const;\nexport function validateScreenExit(exit: Readonly<{ kind: string; label: string }>, hasLeading: boolean) {\n if (exit.kind !== \"back\" && exit.kind !== \"close\") throw new TypeError(\"Screen exit kind must be back or close\");\n if (!exit.label.trim()) throw new TypeError(\"Screen exit needs a localized accessible label\");\n if (hasLeading) throw new TypeError(\"Screen exit owns the leading slot; do not also pass leading\");\n}\n\n/**\n * Who owns the screen gutters. `default`: the shared shell pads header, notice, body and footer. `none`: the host\n * route already insets everything (≈45 product call sites rely on this meaning, so it cannot change).\n * `body-edge`: header, notice and footer keep the shell padding; only the body's inline padding is 0 so full-width\n * list rows (ConversationRow, ListRow-based items) supply their own inset — review S5#9 measured DM avatars ~32pt\n * from the edge because body padding and row padding stacked (2026-10-17 user decision).\n * `body-bleed`: header, notice and footer keep the shell padding; the body has no gutter on any side, so a map or\n * canvas meets the header (or notice), the screen edges and the footer. With no footer the body is still the last\n * region and clears the bottom insets the screen owns (`safeAreaBottom`, an overlay BottomNavigation) without the\n * gutter. choose-window's map screens used `body-edge` and kept a 16pt strip above and below the map (2026-10-18).\n */\nexport type ScreenContentInset = \"default\" | \"none\" | \"body-edge\" | \"body-bleed\";\nexport const screenContentInsets = [\"default\", \"none\", \"body-edge\", \"body-bleed\"] as const satisfies readonly ScreenContentInset[];\n\n/**\n * 2026-10-18 사용자 지시: 입력 자동 포커스는 페이지 진입에만 적용하고 하단 시트에서는 폐지한다.\n * 페이지는 `first-input`으로 명시한 주 입력 또는 첫 유효 입력을 한 번 고른다. 하단 시트는 renderer의\n * 상속 경계에서 중첩 화면까지 자동 포커스를 막는다. Dialog·측면 시트의 모달 포커스 계약은 유지한다.\n * `single-input`은 Dialog·측면 시트의 단일 입력 선택, `none`은 입력 자동 선택을 끈다.\n * 비활성·읽기 전용·initialFocusTarget={false} 칸은 제외한다. 이미 포커스된 입력을 빼앗지 않으며\n * 화면은 mount당 한 번만 포커스한다. 모션 감소는 포커스 정책을 바꾸지 않는다.\n */\nexport type InitialFocusMode = \"first-input\" | \"single-input\" | \"none\";\nexport type ScreenInitialFocus = \"first-input\" | \"none\";\nexport type InitialFocusCandidate = Readonly<{\n /** Registration order inside the scope (tree order). */\n order: number;\n /** `initialFocusTarget` on the field: true prefers it, false excludes it, undefined keeps it eligible. */\n preference: boolean | undefined;\n /** False for disabled or read-only fields at resolve time. */\n focusable: boolean;\n}>;\n\n/** Defaults per composition; renderers read these instead of repeating literals. */\nexport const initialFocusDefaults = {\n // 2026-10-18: input focus belongs to page entry, including chat/auth pages; bottom sheets opt out at their boundary.\n screen: \"first-input\",\n search: \"first-input\",\n editor: \"first-input\",\n chat: \"first-input\",\n commentThread: \"first-input\",\n overlay: \"single-input\",\n auth: \"first-input\",\n} as const satisfies Record<string, InitialFocusMode>;\n\nexport function resolveInitialFocusTarget<T extends InitialFocusCandidate>(candidates: readonly T[], mode: InitialFocusMode): T | null {\n if (mode === \"none\") return null;\n const ordered = [...candidates].sort((a, b) => a.order - b.order);\n if (mode === \"single-input\") {\n const only = ordered.length === 1 ? ordered[0]! : null;\n return only && only.focusable && only.preference !== false ? only : null;\n }\n const eligible = ordered.filter(candidate => candidate.focusable && candidate.preference !== false);\n return eligible.find(candidate => candidate.preference === true) ?? eligible[0] ?? null;\n}\n\n/**\n * 진입 포커스 창(2026-10-18 1.28.1 앱 채택 점검). mount 시점 한 번만 고르면 세 경우가 앱 우회로 남았다: 진입 때 열린\n * 모달 아래 칸(번뚝 첫 가입 약관), 진입 중 비동기로 나타나는 칸(번뚝 계정 삭제 확인), 사용자 행동으로 나중에 나타나는 칸\n * (utilverse 검색 열기, 번뚝 아이디어 시트·spint 작성 단계). 그래서 범위는 아래 상태를 가진다.\n *\n * - `entering`: 진입 중(Native 전환·시트 등장 interaction 전). 칸 등록만 받는다.\n * - `open`: 진입이 끝났지만 아직 포커스하지 못했다. 칸이 새로 등록되거나 위 오버레이가 닫히면 다시 고른다. 모달 아래에서\n * 진입한 화면은 모달이 닫힐 때 사용자가 그 화면을 아직 만지지 않았으면 그때 포커스한다.\n * - `settled`: 포커스했거나, 사용자가 범위를 직접 조작(터치·포인터·키·휠)했거나, 이미 다른 텍스트 칸에 초점이 있다.\n * 이후에 입력이 새로 나타나도 자동 포커스를 다시 시작하지 않는다(2026-10-18 페이지 진입에만 적용).\n *\n * 위에 다른 오버레이가 열려 있는 동안(`blocked`)은 고르지 않고, 그 동안의 조작은 아래 범위의 조작으로 세지 않는다\n * (약관 동의 탭이 화면의 진입 포커스를 끝내면 안 된다). 텍스트 칸에 이미 초점이 있으면 빼앗지 않는다.\n */\nexport type EntryFocusPhase = \"inactive\" | \"entering\" | \"open\" | \"settled\";\nexport type EntryFocusEvent<T extends InitialFocusCandidate> =\n /** The scope mounted (screen) or opened (overlay). */\n | Readonly<{ kind: \"activate\" }>\n /** The overlay closed or the scope unmounted. */\n | Readonly<{ kind: \"deactivate\" }>\n /** The enter transition finished. */\n | Readonly<{ kind: \"enter\" }>\n /** A field mounted into the scope (first registration of that field instance). */\n | Readonly<{ kind: \"register\"; field: T }>\n /** An overlay above the scope closed. */\n | Readonly<{ kind: \"unblock\" }>\n /** User touch, pointer, key or wheel input reached the scope. */\n | Readonly<{ kind: \"interact\" }>;\nexport type EntryFocusFacts<T extends InitialFocusCandidate> = Readonly<{\n mode: InitialFocusMode;\n /** Every field registered in the scope now, including a newly registered one. */\n candidates: readonly T[];\n /** Another overlay is open above this scope. */\n blocked: boolean;\n /** A text field already has focus (the user's or the app's). */\n textEntryFocused: boolean;\n}>;\nexport type EntryFocusResolution<T> = Readonly<{ phase: EntryFocusPhase; target: T | null }>;\n\nexport function resolveEntryFocus<T extends InitialFocusCandidate>(phase: EntryFocusPhase, event: EntryFocusEvent<T>, facts: EntryFocusFacts<T>): EntryFocusResolution<T> {\n const stay = { phase, target: null };\n if (event.kind === \"deactivate\") return { phase: \"inactive\", target: null };\n if (event.kind === \"activate\") return { phase: \"entering\", target: null };\n if (phase === \"inactive\") return stay;\n if (event.kind === \"interact\") return facts.blocked || phase === \"settled\" ? stay : { phase: \"settled\", target: null };\n const attempt = (): EntryFocusResolution<T> => {\n if (facts.blocked) return { phase: \"open\", target: null };\n if (facts.textEntryFocused) return { phase: \"settled\", target: null };\n const target = resolveInitialFocusTarget(facts.candidates, facts.mode);\n return target ? { phase: \"settled\", target } : { phase: \"open\", target: null };\n };\n if (event.kind === \"enter\") return phase !== \"entering\" ? stay : facts.mode === \"none\" ? { phase: \"settled\", target: null } : attempt();\n if (phase === \"entering\") return stay;\n if (phase === \"open\") return attempt();\n // Page entry only: revealing a marked field after interaction/entry must not open the keyboard again.\n return stay;\n}\n\n// Reuse reading-width and spacing tokens; do not derive geometry from a product's device or artwork.\nexport const screenPatternRecipe = {\n maxWidth: layout.readingMaxWidth,\n padding: spacing.md,\n sectionGap: spacing.xl,\n itemGap: spacing.sm,\n stateGap: spacing.md,\n // Compact screens must keep back/title/save on one row; scale this floor for large text instead of reserving a desktop-width title.\n headerMinWidth: layout.readingMaxWidth / 6,\n // Reserve the opposite edge for authorship and cap the composer so history remains visible.\n messageMaxWidth: \"84%\",\n // Social thumbnails stay compact; selection order and removal replace a second toolbar.\n attachmentSize: 80,\n attachmentRemoveSize: 24,\n reactionHoldMs: 450,\n reactionMoveTolerance: 10,\n reactionMenuWidth: 320,\n replySwipeDistance: 60,\n composerMinLines: 1,\n composerMaxLines: 5,\n} as const;\n\n/** Controlled composer: HJM never clears drafts or interprets a fulfilled callback as server delivery. */\nexport type MessageComposerDescriptor = Readonly<{\n value: string;\n label: string;\n sendLabel: string;\n /** Short visual copy may differ from the complete accessible input name. */\n placeholder?: string;\n description?: string;\n error?: string;\n invalid?: boolean;\n /** Newline remains the compatible default; desktop chat may explicitly opt into send. */\n submitMode?: \"newline\" | \"send\";\n disabled?: boolean;\n pending?: boolean;\n attachmentCount?: number;\n}>;\n/** Safari may report the final IME Enter as keyCode 229 after isComposing becomes false. */\nexport function shouldSubmitMessageKey(event: Readonly<{ key: string; shiftKey?: boolean; isComposing?: boolean; keyCode?: number }>, mode: \"newline\" | \"send\" = \"newline\"): boolean {\n return mode === \"send\" && event.key === \"Enter\" && !event.shiftKey && !event.isComposing && event.keyCode !== 229;\n}\nexport function canSubmitMessage({ value, disabled = false, pending = false, attachmentCount = 0 }: MessageComposerDescriptor): boolean {\n if (!Number.isSafeInteger(attachmentCount) || attachmentCount < 0) throw new RangeError(\"Attachment count must be a nonnegative integer\");\n return !disabled && !pending && (value.trim().length > 0 || attachmentCount > 0);\n}\n\n/**\n * Shared timeline geometry. 2026-10-16 user decision: the common chat standard follows the measured\n * KakaoTalk iOS chat room (@3x capture: bubble ~35pt tall, radius ~14, run gap ~6, group gap ~12,\n * avatar ~34, avatar gap 8). Brand colors and artwork are not copied; surfaces stay semantic tokens.\n */\nexport const chatMessageRecipe = {\n // Must be a size the Avatar renders on both platforms: 36 was no Web Avatar step (32/40/48/64), so Web drew a 32\n // avatar in a 36 slot while Native drew 36 (2026-10-17 measured audit). `small` is the step nearest the ~34 reference.\n avatarSize: avatarRecipe.sizes.small,\n avatarGap: spacing.xs,\n authorGap: spacing.xxs,\n metaGap: spacing.xxs,\n bubbleRadius: 14,\n bubblePaddingInline: 10,\n bubblePaddingBlock: 6,\n // Bubbles of one run read as a stack; a new author or minute starts a visibly separate group.\n runGap: 6,\n groupGap: spacing.sm,\n // Flat-topped tip continuing the bubble's top edge; the tipped corner tightens to meet it.\n tailWidth: 7,\n tailHeight: 10,\n tailCornerRadius: 4,\n jumpToLatestGap: spacing.xs,\n // Timeline side gutter (measured 10pt); header and composer keep the screen padding.\n timelineInset: 10,\n // Photos render without the bubble surface (measured reference: ~210pt wide, bubble radius).\n mediaWidth: 210,\n mediaGap: 2,\n /**\n * Reaction summary pill beside the time (2026-10-18 BurnTok DM): one button that opens the same hold menu.\n * Counts below `minCount` show the emoji alone, as the measured DM references do for a single reaction.\n */\n reactionSummary: { minHeight: 24, paddingInline: spacing.xs, itemGap: spacing.xxs, emojiGap: 2, minCount: 2 },\n} as const;\n\nexport type ChatReactionSummaryItem = Readonly<{ id: string; emoji: string; count: number }>;\nexport type ChatReactionSummary = Readonly<{\n label: string;\n items: readonly ChatReactionSummaryItem[];\n /**\n * Counts only, for readers who cannot react (2026-10-18 Utilverse guests): drawn in the same place beside the time\n * as non-interactive text named by `label`, without the pill surface, so it does not look or act like a control.\n * Default `false` (the pill button that opens the hold menu).\n */\n readOnly?: boolean;\n}>;\n/**\n * Validates ChatMessage `reactions.summary` and marks which counts are drawn. The label (the button's accessible name)\n * is required; ids are unique and non-empty, emoji non-empty and counts positive integers; a count below\n * `reactionSummary.minCount` shows the emoji alone.\n */\nexport function resolveChatReactionSummary({ label, items }: ChatReactionSummary): readonly (ChatReactionSummaryItem & Readonly<{ showCount: boolean }>)[] {\n if (!label.trim()) throw new TypeError(\"Reaction summary requires a localized label\");\n const ids = new Set<string>();\n return items.map(item => {\n if (!item.id.trim() || ids.has(item.id)) throw new TypeError(\"Reaction summary ids must be unique and non-empty\");\n if (!item.emoji.trim()) throw new TypeError(\"Reaction summary items require an emoji\");\n if (!Number.isSafeInteger(item.count) || item.count < 1) throw new RangeError(\"Reaction summary counts must be positive integers\");\n ids.add(item.id);\n return { ...item, showCount: item.count >= chatMessageRecipe.reactionSummary.minCount };\n });\n}\n\n/**\n * \"Someone is typing\" row in a chat timeline (2026-10-18 BurnTok DM). It reuses the incoming bubble geometry\n * (chatMessageRecipe) so it reads as the next incoming message; only the dots are its own values. Under reduced\n * motion the dots stay at full opacity instead of pulsing.\n */\nexport const typingIndicatorRecipe = {\n dotSize: 6,\n dotGap: spacing.xxs,\n /** One pulse of the three dots; each dot starts `staggerMs` after the previous one. */\n cycleMs: 1200,\n staggerMs: 160,\n restOpacity: 0.35,\n} as const;\n\n/**\n * Centered date (or time) row between timeline groups (2026-10-18 Utilverse chat, which drew it as a product\n * caption). The next message still owns its own group gap above it, so this row only adds the space before it.\n */\nexport const chatDateSeparatorRecipe = {\n paddingTop: spacing.sm,\n paddingBottom: spacing.xxs,\n textVariant: \"caption\",\n tone: \"muted\",\n} as const;\n\n/** Column count for a photo set: pairs read as 2-up, other counts as 3-up; one photo spans the width. */\nexport function resolveChatMediaColumns(count: number): 1 | 2 | 3 {\n if (!Number.isSafeInteger(count) || count < 1) throw new RangeError(\"Chat media requires at least one item\");\n return count === 1 ? 1 : count === 2 || count === 4 ? 2 : 3;\n}\n\n/**\n * Paged photo album for ChatMessage `mediaAlbum` and CommentThreadItem `mediaAlbum` (2026-10-18, Utilverse social\n * photos). Only the shown page is rendered, so a product that authorizes each image can fetch one photo at a time\n * instead of every photo in a timeline row. The counter row matches CarouselMotion `navigation=\"counter\"`:\n * previous icon · position · next icon under the page; the frame keeps the host's media width and radius.\n */\nexport const mediaAlbumRecipe = {\n /** Page frame width / height when the product gives no aspect ratio. */\n defaultAspectRatio: 1,\n /** Space between the page frame and the counter row. */\n controlGap: spacing.xxs,\n /** Previous/next are small ghost IconButtons so the counter row stays compact beside thumbnails. */\n controlSize: \"small\",\n controlGlyphSize: glyph.sm,\n /**\n * Loading / failed page drawn by `MediaAlbumPageStatus` (2026-10-18 Utilverse: the page frame was one accessible\n * image, so the loading text and retry button inside it were unreachable by screen readers). The status block is\n * centered in the frame (VQ-09); retry is a small secondary Button.\n */\n pageStatus: { gap: spacing.xs, padding: spacing.sm, textVariant: \"caption\", retryTone: \"secondary\", retrySize: \"small\" },\n} as const;\n\nexport type MediaAlbumPageStatusKind = \"loading\" | \"error\";\n/** A page status names what the reader is waiting for or what failed; the label is the only copy. */\nexport function validateMediaAlbumPageStatus(kind: MediaAlbumPageStatusKind, label: string): void {\n if (kind !== \"loading\" && kind !== \"error\") throw new TypeError(\"Media album page status must be loading or error\");\n if (!label.trim()) throw new TypeError(\"Media album page status requires a localized label\");\n}\n\n/** A controlled index outside the set is a contract error, like Carousel's unknown `currentKey`. */\nexport function validateMediaAlbum(count: number, index: number): void {\n if (!Number.isSafeInteger(count) || count < 1) throw new RangeError(\"Media album requires at least one page\");\n if (!Number.isSafeInteger(index) || index < 0 || index >= count) throw new RangeError(\"Media album index must point at an existing page\");\n}\n\n/** One message or comment carries one photo set: either the eager `media` previews or the paged `mediaAlbum`. */\nexport function validateMediaSetChoice(hasMedia: boolean, hasAlbum: boolean): void {\n if (hasMedia && hasAlbum) throw new TypeError(\"Pass either media or mediaAlbum, not both\");\n}\n\n/** Uncontrolled albums keep the nearest page when the product's photo set shrinks (moderation, deletion). */\nexport function resolveMediaAlbumIndex(count: number, index: number): number {\n if (!Number.isSafeInteger(count) || count < 1) throw new RangeError(\"Media album requires at least one page\");\n return Math.min(Math.max(0, Math.trunc(index)), count - 1);\n}\n\n/**\n * Long-press menu geometry measured from the Instagram iOS DM hold menu (2026-10-16 @3x capture):\n * full-width reaction strip ~60pt with ~30pt emoji, the pressed bubble itself as preview, and a ~200pt\n * action card aligned to the bubble's side with ~56pt rows. 12pt between the three blocks.\n */\n/** Emoji row above a comment composer (Instagram comment sheet, 2026-10-16): evenly spread 44pt targets. */\nexport const composerQuickInsertRecipe = { itemSize: 44, emojiSize: 26, gap: spacing.xs } as const;\n\nexport const holdMenuRecipe = {\n inset: 10,\n gap: spacing.sm,\n stripHeight: 60,\n stripItemSize: 48,\n emojiSize: 28,\n actionWidth: 200,\n actionRowHeight: 56,\n actionRadius: 18,\n iconSize: 24,\n} as const;\n\n/** Position inside a run of consecutive messages from one author within one displayed minute. */\nexport type ChatMessageGroupPosition = \"single\" | \"first\" | \"middle\" | \"last\";\n\n/**\n * Runs break when the author or the displayed timestamp changes, so each minute shows its time once\n * and a new minute re-introduces the author (KakaoTalk behavior). Products pass the same localized\n * timestamp string they render; HJM never parses dates.\n */\nexport function resolveChatMessageGroups(items: readonly Readonly<{ id: string; authorId: string; timestamp: string }>[]): ReadonlyMap<string, ChatMessageGroupPosition> {\n const positions = new Map<string, ChatMessageGroupPosition>();\n items.forEach((item, index) => {\n if (!item.id.trim() || positions.has(item.id)) throw new TypeError(\"Chat messages require unique, nonempty ids\");\n const same = (other: (typeof items)[number] | undefined) => !!other && other.authorId === item.authorId && other.timestamp === item.timestamp;\n const before = same(items[index - 1]), after = same(items[index + 1]);\n positions.set(item.id, before ? (after ? \"middle\" : \"last\") : after ? \"first\" : \"single\");\n });\n return positions;\n}\n\n/** What one bubble shows for its run position; both renderers read this instead of re-deriving rules. */\nexport function resolveChatMessageGroupDisplay(position: ChatMessageGroupPosition = \"single\") {\n if (![\"single\", \"first\", \"middle\", \"last\"].includes(position)) throw new TypeError(\"Unknown chat message group position\");\n const starts = position === \"single\" || position === \"first\";\n const ends = position === \"single\" || position === \"last\";\n return { showAuthor: starts, showAvatar: starts, showTail: starts, showTimestamp: ends, spacingBefore: starts ? chatMessageRecipe.groupGap : chatMessageRecipe.runGap } as const;\n}\n\n/** Bubble alignment expresses authorship, not delivery; delivery text always comes from the product receipt. */\nexport type ChatMessageDescriptor = Readonly<{\n direction: \"incoming\" | \"outgoing\";\n author: string;\n timestamp: string;\n deliveryLabel?: string;\n /** Inward drag reveals time (incoming right, outgoing left) while delivery state remains visible; default shows time beside the last bubble of a run. */\n timestampPresentation?: \"always\" | \"swipe\";\n /** Authorship tip on the run's first bubble. Default on since the 2026-10-16 timeline standard. */\n bubbleTail?: boolean;\n /** From resolveChatMessageGroups; omitted means a standalone message. */\n groupPosition?: ChatMessageGroupPosition;\n}>;\n\n/** A denied permission goes to settings; rendering never requests OS access automatically. */\nexport type PermissionScreenStatus = \"prompt\" | \"denied\" | \"granted\" | \"unavailable\";\nexport function resolvePermissionAction(status:PermissionScreenStatus) {\n switch(status){case \"prompt\":return \"request\";case \"denied\":return \"settings\";case \"granted\":return \"continue\";case \"unavailable\":return null;default:throw new TypeError(\"Unknown permission state\");}\n}\nexport function resolveOnboardingStep(total:number,index:number){\n if(!Number.isInteger(total)||total<1||!Number.isInteger(index)||index<0||index>=total)throw new RangeError(\"Onboarding requires a valid step index\");\n return {first:index===0,last:index===total-1};\n}\n\n/**\n * Shared comment-row geometry. 2026-10-16 user decision: comments follow the measured Instagram iOS\n * comment sheet (@3x capture: avatar 32 / reply 24, avatar gap 8, like count under the heart,\n * \"Reply\" as quiet text below the body, replies indented to the text column). Artwork is not copied.\n */\nexport const commentThreadRecipe = {\n avatarSize: 32,\n replyAvatarSize: 24,\n avatarGap: spacing.xs,\n lineGap: 2,\n rowGap: spacing.sm,\n // Replies align with the parent's text column instead of an arbitrary section inset.\n replyIndent: 32 + spacing.xs,\n // The heart keeps the 44pt touch target even though the measured reference draws a smaller glyph.\n likeColumnWidth: 44,\n // Optional photo/sticker under the body; the measured reference keeps it thumbnail-sized.\n mediaMaxWidth: 160,\n mediaRadius: radius.md,\n // Inline per-comment panel (edit/report/block form) under the action row (2026-10-18, Utilverse): a form reads as its\n // own block, so it sits a full spacing step below the reply line instead of the 2pt line gap.\n panelGap: spacing.xs,\n // The trailing vertical-dots menu glyph; the button keeps the shared 44pt IconButton target.\n menuGlyphSize: glyph.sm,\n /**\n * Trailing actions (like, product actions, more) centre their 44 targets on the identity's first line instead of\n * top-aligning the target box (2026-10-17 measured audit: avatar 168 / author line 162 / glyphs 174). The cluster\n * shifts by (first-line height − visible target) / 2 — negative while the line is shorter than the target — and the\n * last control takes IconButton `edge=\"end\"` so its glyph slot ends on the row inset like the avatar starts on it.\n * The first line is the author caption, or the body line when the author runs inline with `bodyText`.\n */\n actionAnchor: { authorTextVariant: \"caption\", inlineTextVariant: \"body\" },\n} as const;\n\n/**\n * Items of the HJM-owned per-comment \"more\" menu (2026-10-17: message/comment structure belongs to HJM, review S4#7).\n * Same vocabulary as Menu/ContextMenu; the product supplies only ids, localized labels, tone and availability.\n */\nexport type CommentMenuItemDescriptor<Key extends string = string> = Pick<MenuItemDescriptor<Key>, \"id\" | \"label\" | \"disabled\" | \"tone\">;\n/** Danger actions (delete, block) always sit last so a destructive row is never the first target; order is otherwise kept. */\nexport function resolveCommentMenuItems<Key extends string>(items: readonly CommentMenuItemDescriptor<Key>[]): readonly CommentMenuItemDescriptor<Key>[] {\n const ids = new Set<string>();\n for (const item of items) {\n if (!item.id.trim() || ids.has(item.id) || !item.label.trim()) throw new TypeError(\"Comment menu items require unique ids and localized labels\");\n ids.add(item.id);\n }\n return [...items.filter(item => item.tone !== \"danger\"), ...items.filter(item => item.tone === \"danger\")];\n}\n\n/** Replies are one level deep; reject orphaned/duplicate records instead of silently losing them. */\nexport function validateCommentThread(items:readonly {id:string;parentId:string|null}[]){\n const roots=new Set(items.filter(item=>item.parentId===null).map(item=>item.id));const ids=new Set<string>();\n for(const item of items){if(!item.id.trim()||ids.has(item.id))throw new TypeError(\"Comment ids must be unique and nonempty\");ids.add(item.id);if(item.parentId!==null&&(!roots.has(item.parentId)||item.parentId===item.id))throw new TypeError(\"Replies require an existing root comment\");}\n}\n\n/** Source bytes, permission requests, ordering and upload limits remain product-owned. */\nexport type MessageAttachmentDescriptor = Readonly<{ id: string; removeLabel: string; /** Local preparation can lock removal without locking text entry. */ disabled?: boolean }>;\nexport function validateMessageAttachments(attachments: readonly MessageAttachmentDescriptor[]): void {\n const ids = new Set<string>();\n for (const item of attachments) {\n if (!item.id.trim() || ids.has(item.id) || !item.removeLabel.trim()) throw new TypeError(\"Attachments require unique ids and localized removal labels\");\n ids.add(item.id);\n }\n}\n\n/** Require horizontal intent so a timeline scroll cannot accidentally start a reply. */\nexport function isReplySwipe(dx: number, dy: number): boolean {\n return Math.abs(dx) >= screenPatternRecipe.replySwipeDistance && Math.abs(dx) > Math.abs(dy) * 2;\n}\n\n/** The UI never infers permission or camera availability; product adapters own both. */\nexport type PhotoSource = \"library\" | \"camera\";\nexport type PhotoSourceLabels = Readonly<{ title:string; library:string; camera:string; cancel:string }>;\n\nexport type SavedItem = Readonly<{ id: string; title: string }>;\nexport type SavedCollection = Readonly<{ id: string; title: string; itemIds: readonly string[] }>;\nexport type SavedItemsLabels = Readonly<{ allItems: string; privateNotice: string; back: string; empty: string; createCollection: string }>;\n\n/** Unsave removes an item from every collection view without rewriting product-owned membership. */\nexport function resolveSavedItems<T extends SavedItem>(items:readonly T[],collections:readonly SavedCollection[],collectionId:string|null|undefined,selectedItemId:string|null|undefined){\n const ids=new Set<string>(),groups=new Set<string>();\n for(const item of items){if(!item.id.trim()||ids.has(item.id)||!item.title.trim())throw new TypeError(\"Saved items require unique ids and nonempty titles\");ids.add(item.id);}\n for(const group of collections){if(!group.id.trim()||groups.has(group.id)||!group.title.trim())throw new TypeError(\"Saved collections require unique ids and nonempty titles\");groups.add(group.id);}\n const collection=collections.find(item=>item.id===collectionId);\n // A deleted collection returns to the overview instead of showing a mislabeled all-posts view.\n const home=collectionId===undefined||(collectionId!==null&&!collection);\n const visible=home||collectionId===null?items:items.filter(item=>collection?.itemIds.includes(item.id));\n return {home,collection,visible,selected:home?undefined:visible.find(item=>item.id===selectedItemId)};\n}\n\n/**\n * SearchScreen phases (2026-10-06 search redesign, docs/screen-patterns.md#searchscreen-두-단계-검색).\n * Without a committed query (one-step search, the pre-1.13 behavior) any nonblank query is already\n * `results`. With one, typing a different query is `typing`: suggestions replace the results until the\n * user commits (Enter, a suggestion, a recent or suggested query). Comparing trimmed values keeps a\n * trailing space from flipping committed results back into suggestions.\n */\nexport type SearchScreenPhase = \"idle\" | \"typing\" | \"results\";\nexport function resolveSearchScreenPhase(query: string, committedQuery?: string): SearchScreenPhase {\n const typed = query.trim();\n if (!typed) return \"idle\";\n if (committedQuery === undefined) return \"results\";\n return typed === committedQuery.trim() ? \"results\" : \"typing\";\n}\n\n/**\n * The value a commit records, or `null` when there is nothing to commit. Blank commits are dropped\n * so a stray Enter never writes an empty recent search or fetches \"everything\".\n */\nexport function resolveSearchCommit(value: string): string | null {\n const next = value.trim();\n return next ? next : null;\n}\n\n/**\n * Why a committed search shows nothing. `filters` keeps the filter rail and offers \"clear all\";\n * `query` hides the rail (no filter can fix it) and offers suggested queries instead. Reference\n * services hide the rail on every zero result, which removes the only way to undo a filter.\n * `null` count means the result is still loading, so no cause is reported yet.\n */\nexport type SearchEmptyCause = \"none\" | \"filters\" | \"query\";\nexport function resolveSearchEmptyCause(count: number | null, appliedFilterCount: number): SearchEmptyCause {\n // Loading/nonempty results must not bypass the same count contract used by empty results.\n if ((count !== null && (count < 0 || !Number.isSafeInteger(count))) || !Number.isSafeInteger(appliedFilterCount) || appliedFilterCount < 0) {\n throw new RangeError(\"Search counts must be nonnegative integers\");\n }\n if (count === null || count > 0) return \"none\";\n return appliedFilterCount > 0 ? \"filters\" : \"query\";\n}\n\n/**\n * What the SearchScreen filter rail does to a result. `narrow` (default) only removes results, so with nothing applied\n * a zero result is the query's and no filter can bring results back. `scope` chooses what is searched (keyword vs\n * address, region, map area): changing it can turn a zero into results even with nothing applied (2026-10-18:\n * choose-window's keyword/address switch is the recovery for a zero result, so it skipped resultSummary entirely).\n */\nexport type SearchFiltersRole = \"narrow\" | \"scope\";\nexport const searchFiltersRoles = [\"narrow\", \"scope\"] as const satisfies readonly SearchFiltersRole[];\n/**\n * Whether SearchScreen shows its filter rail (`filters` and the filter sheet trigger). Two-step search shows it only\n * beside results so typing keeps the pinned area short. A query-caused zero hides it only when the filters narrow,\n * because then none of them can fix the zero; scope filters stay as the recovery path. A filter-caused zero always keeps\n * it so the cause can be undone. Both renderers call this so the rule cannot drift between platforms.\n */\nexport function resolveSearchFilterRailVisible(phase: SearchScreenPhase, twoStep: boolean, cause: SearchEmptyCause, role: SearchFiltersRole = \"narrow\"): boolean {\n if (!searchFiltersRoles.includes(role)) throw new RangeError(\"SearchScreen filtersRole must be narrow or scope\");\n if (twoStep && phase !== \"results\") return false;\n return cause !== \"query\" || role === \"scope\";\n}\n\n/**\n * Index to focus after removing item `removedIndex` from a list that now has `remaining` items:\n * the item that slid into its place, else the new last item, else -1 (move to the fallback target).\n * Removing a focused chip or row would otherwise drop focus to <body>.\n */\nexport function resolveFocusAfterRemoval(removedIndex: number, remaining: number): number {\n if (!Number.isInteger(removedIndex) || removedIndex < 0 || !Number.isInteger(remaining) || remaining < 0) {\n throw new RangeError(\"Removal indexes must be nonnegative integers\");\n }\n return remaining === 0 ? -1 : Math.min(removedIndex, remaining - 1);\n}\n\n/** Recipe for the SearchScreen phase sections; geometry stays on shared spacing tokens. */\nexport const searchScreenRecipe = {\n /** Debounced typing delay; explicit submit remains a separate host action. */\n debounceMs: 300,\n /** Recent searches shown before the user types; reference services show 5–10 and let the rest scroll away. */\n recentVisible: 5,\n /** Suggestions under the \"search for ‘q’\" row. */\n suggestionVisible: 6,\n /** Skeleton rows while the first page of results loads. */\n loadingRows: 4,\n /** Between idle sections (recent, suggested). */\n sectionGap: spacing.xl,\n /** Section title to its list, and results header to applied filters. */\n headerGap: spacing.sm,\n /** Between chips in applied filters and suggested queries. */\n chipGap: spacing.xs,\n} as const;\n\n/** Keep finite negative delays at zero; reject values whose timer behavior varies by host. */\nexport function resolveSearchDebounceMs(value: number = searchScreenRecipe.debounceMs): number {\n if (!Number.isFinite(value)) throw new RangeError(\"Search debounceMs must be finite\");\n return Math.max(0, value);\n}\n\n/** Horizontal intent preserves vertical scrolling and uses one reveal distance across renderers. */\nexport function timestampRevealOffset(dx: number, dy: number): number {\n // A 12px dead zone and 2:1 direction bias avoid turning taps/list scrolling into a time gesture.\n return dx > 12 && dx > Math.abs(dy) * 2 ? Math.min(dx, 88) : 0;\n}\n\n/** Notification previews need an actor/type badge and a separate subject thumbnail.\n * Keep this geometry shared so inboxes do not recreate the same row per product. */\nexport const notificationRowRecipe = {\n gap: 12, contentGap: 4, avatarSize: 44, avatarBadgeSize: 20,\n thumbnailSize: 44, unreadSize: 6, previewLines: 2, paddingBlock: 12, paddingInline: 16,\n} as const;\n\n/** Shared profile identity geometry; product owns the avatar content. */\nexport const profileSummaryRecipe = Object.freeze({\n avatarSize: 72,\n /**\n * ProfileScreen `editPlacement=\"summary\"` draws the edit action first in the summary's action row. A framed small\n * button reads as the card's own action; the below-summary placement keeps its ghost button (2026-10-15 utilverse\n * account decision: the edit action belongs inside the profile card).\n */\n editAction: Object.freeze({ tone: \"secondary\", size: \"small\" } as const),\n});\n\n/** Launcher artwork stays above its caption; product overlays may extend beyond the icon frame. */\n/**\n * Frame of an IconTile icon (2026-10-18 Utilverse launcher). `surface` draws the shared filled, hairlined frame;\n * `none` leaves product artwork that is already a complete launcher icon unframed. `shape` follows the product's\n * icon theme (squircle on iPhone shells, square on Windows/Linux shells); `size: \"fill\"` lets the icon span its\n * grid cell between `fillMinSize` and `fillMaxSize` instead of a product computing the cell size.\n */\nexport type IconTileFrame = \"surface\" | \"none\";\nexport type IconTileShape = \"squircle\" | \"square\" | \"circle\";\nexport type IconTileSize = \"fixed\" | \"fill\";\nexport const iconTileRecipe = Object.freeze({ iconSize: 64, labelMaxWidth: 72, multilineLabelMaxWidth: 84, multilineLabelMinHeight: 44, ghostOpacity: 0.55, gap: 6, iconRadius: \"22%\",\n /** Corner radius as a fraction of the icon size; `squircle` keeps the original 22%. */\n shapes: Object.freeze({ squircle: 0.22, square: 0, circle: 0.5 }) as Readonly<Record<IconTileShape, number>>,\n fillMinSize: 48, fillMaxSize: 68,\n defaults: Object.freeze({ frame: \"surface\" as IconTileFrame, shape: \"squircle\" as IconTileShape, size: \"fixed\" as IconTileSize }) });\n\n/**\n * What an IconTile passes to an `icon` render function: the rendered icon edge (`iconSize` for `fixed`, the measured\n * cell-clamped edge for `fill`). Fixed-size artwork (images, generated launcher art) sizes itself from it; Utilverse\n * re-computed the 48–68 clamp from its grid width to size its artwork (2026-10-18).\n */\nexport type IconTileArtwork = Readonly<{ edge: number }>;\n\n/** The edge a `size=\"fill\"` icon takes in a cell of `cellWidth`, for layout estimates made before the tile measures. */\nexport function resolveIconTileFillEdge(cellWidth: number): number {\n if (!Number.isFinite(cellWidth) || cellWidth < 0) throw new RangeError(\"IconTile cell width must be a non-negative finite number\");\n return Math.min(iconTileRecipe.fillMaxSize, Math.max(iconTileRecipe.fillMinSize, cellWidth));\n}\n"]}
package/dist/version.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  /** Package release shown by documentation surfaces. Kept in sync by a test. */
2
- export declare const designSystemVersion: "1.32.0";
2
+ export declare const designSystemVersion: "1.33.0";
3
3
  //# sourceMappingURL=version.d.ts.map
package/dist/version.js CHANGED
@@ -1,3 +1,3 @@
1
1
  /** Package release shown by documentation surfaces. Kept in sync by a test. */
2
- export const designSystemVersion = "1.32.0";
2
+ export const designSystemVersion = "1.33.0";
3
3
  //# sourceMappingURL=version.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,MAAM,CAAC,MAAM,mBAAmB,GAAG,QAAiB,CAAC","sourcesContent":["/** Package release shown by documentation surfaces. Kept in sync by a test. */\nexport const designSystemVersion = \"1.32.0\" as const;\n"]}
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,MAAM,CAAC,MAAM,mBAAmB,GAAG,QAAiB,CAAC","sourcesContent":["/** Package release shown by documentation surfaces. Kept in sync by a test. */\nexport const designSystemVersion = \"1.33.0\" as const;\n"]}
@@ -1,6 +1,6 @@
1
1
  # Component maturity and evidence manifest
2
2
 
3
- > Generated from `@hjmds/design-contracts` 1.32.0. Do not edit this file directly; run `pnpm contracts:sync`.
3
+ > Generated from `@hjmds/design-contracts` 1.33.0. Do not edit this file directly; run `pnpm contracts:sync`.
4
4
 
5
5
  This document is the human-readable projection of the same catalog and Showcase manifest consumed by product CI.
6
6
 
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "designSystemVersion": "1.32.0",
3
+ "designSystemVersion": "1.33.0",
4
4
  "source": "packages/design-contracts/docs/generated/showcase-manifest.json",
5
5
  "surfaces": {
6
6
  "web": {
7
7
  "packageName": "@hjmds/react",
8
- "packageVersion": "1.32.0",
8
+ "packageVersion": "1.33.0",
9
9
  "activeComponents": 130,
10
10
  "claimedComponents": 130,
11
11
  "completeScenarioComponents": 129,
@@ -6999,7 +6999,7 @@
6999
6999
  },
7000
7000
  "native": {
7001
7001
  "packageName": "@hjmds/react-native",
7002
- "packageVersion": "1.32.0",
7002
+ "packageVersion": "1.33.0",
7003
7003
  "activeComponents": 112,
7004
7004
  "claimedComponents": 112,
7005
7005
  "completeScenarioComponents": 109,
@@ -1,6 +1,6 @@
1
1
  # Renderer evidence coverage
2
2
 
3
- > Generated for HJM 1.32.0. Do not edit directly; run `pnpm evidence:sync`.
3
+ > Generated for HJM 1.33.0. Do not edit directly; run `pnpm evidence:sync`.
4
4
 
5
5
  This projection joins the canonical surface maturity manifest with first-party renderer claims. Missing scenarios are explicit beta promotion debt; stable surfaces are blocked by CI until none remain.
6
6
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
- "designSystemVersion": "1.32.0",
3
+ "designSystemVersion": "1.33.0",
4
4
  "packageName": "@hjmds/design-contracts",
5
5
  "components": [
6
6
  {
@@ -6,7 +6,7 @@
6
6
  버튼 위치에서 시작해 구성·컴포넌트 지침으로 내려가고, 값은 토큰 지침에서 고른다.
7
7
  [선택 기준](SELECTION.md)으로 그대로 사용·공개 슬롯 확장·공통 API 보완·제품 고유 기능 연결을 고른다. 설치 버전의 지침을 본다: `node_modules/@hjmds/design-contracts/docs/usage/`.
8
8
  제공 기준은 확인한 기본 API 버전이며 최초 출시 버전이 아니다. 구성·화면은 각 API 지침의 제공 버전을 확인한다.
9
- API 안정성은 1.31.0의 생성 manifest에서 읽은 정규(canonical) API 기준이다. 동반·선택형 확장은 개별 계약을 확인한다.
9
+ API 안정성은 1.32.1의 생성 manifest에서 읽은 정규(canonical) API 기준이다. 동반·선택형 확장은 개별 계약을 확인한다.
10
10
  Storybook 배포/실험은 예제 분류이며 npm 게시·기기 검증·제품 반영을 의미하지 않는다.
11
11
  `분류`는 Storybook 제목 `<배포|실험>/<단계>/<분류>/<항목>`의 셋째 마디이고, 토큰·구성·화면은 Storybook 메뉴와 같은 순서다.
12
12
 
@@ -57,7 +57,7 @@ Storybook 배포/실험은 예제 분류이며 npm 게시·기기 검증·제품
57
57
  | [Carousel](components/carousel.md) | 데이터 표시 | 한 번에 카드 하나만 보이고 사용자가 순서대로 넘겨 보는 유한한 묶음에 쓴다. | 1.12.1 | Web · Native | Web stable / Native stable | 배포 |
58
58
  | [Celebration](components/celebration.md) | 구성/직접 조작과 모션 | 목표 달성, 첫 완료처럼 드물게 일어나는 성공 순간에 한 번 터지는 색종이 효과에 쓴다. | 1.12.1 | Web · Native | 개별 API 계약 참조 | 배포 |
59
59
  | [Chart](components/chart.md) | 데이터 표시 | 수치의 흐름·비교·몫·모양·관계를 그림으로 읽게 할 때 쓴다. | 1.21.0 | Web · Native | Web beta / Native beta | 배포 |
60
- | [ChatDateSeparator](components/chat-date-separator.md) | 데이터 표시 | 대화 타임라인에서 날짜(또는 오래 쉰 뒤의 시각)가 바뀌는 자리에 가운데 정렬 구분 줄을 둘 때 쓴다. | 미게시(1.30.0 이후) | Web · Native | Web beta / Native beta | 실험 |
60
+ | [ChatDateSeparator](components/chat-date-separator.md) | 데이터 표시 | 대화 타임라인에서 날짜(또는 오래 쉰 뒤의 시각)가 바뀌는 자리에 가운데 정렬 구분 줄을 둘 때 쓴다. | 1.31.0 | Web · Native | Web beta / Native beta | 배포 |
61
61
  | [ChatMessage](components/chat-message.md) | 구성/정보 표시 | DM·대화 타임라인의 메시지 한 개에 쓴다. | 1.17.1 | Web · Native | Web stable / Native stable | 배포 |
62
62
  | [ChatScreen](components/chat-screen.md) | 화면/소통 | DM·대화방처럼 헤더, 메시지 타임라인, 하단 작성창으로 이루어진 화면 한 장에 쓴다. | 1.17.1 | Web · Native | Web stable / Native stable | 배포 |
63
63
  | [Checkbox](components/checkbox.md) | 입력 | 독립된 예/아니오 하나를 고르는 항목에 쓴다. | 1.12.1 | Web · Native | Web stable / Native stable | 배포 |
@@ -75,7 +75,7 @@ Storybook 배포/실험은 예제 분류이며 npm 게시·기기 검증·제품
75
75
  | [Container](components/container.md) | 레이아웃 | 화면 본문의 최대 폭과 좌우(논리 방향) 여백을 맞출 때 쓴다. | 1.12.1 | Web · Native | Web stable / Native stable | 배포 |
76
76
  | [ContentTransition](components/content-transition.md) | 시각 효과 | 같은 자리의 내용이 상태에 따라 바뀔 때(필터 결과 패널, 단계별 본문) 새 내용이 짧게 나타나도록 감싼다. | 1.12.1 | Web · Native | 개별 API 계약 참조 | 배포 |
77
77
  | [ContextMenu](components/context-menu.md) | 탐색 | Web에서 제품이 소유한 영역(카드·목록 행·캔버스)의 우클릭·길게 누르기·Shift+F10에 명령 목록을 띄울 때 쓴다. | 1.12.1 | Web · Native | Web stable / Native unsupported | 배포 |
78
- | [ConversationRow](components/conversation-row.md) | 화면/소통 | 대화 목록에서 상대, 마지막 메시지, 상대 시각, 안 읽음 점을 표시한다. | 1.24.0 | Web · Native | 개별 API 계약 참조 | 실험 |
78
+ | [ConversationRow](components/conversation-row.md) | 화면/소통 | 대화 목록에서 상대, 마지막 메시지, 상대 시각, 안 읽음 점을 표시한다. | 1.24.0 | Web · Native | 개별 API 계약 참조 | 배포 |
79
79
  | [ConversionFunnel](components/conversion-funnel.md) | 구성/정보 표시 | 순차 단계(열기 → 작성 → 저장 등)의 인원을 가로 막대로 보여 주고, 단계를 골라 전체 대비·직전 대비 전환율과 이탈 수를 문구로 읽을 때 쓴다. | 1.21.0 | Web · Native | 개별 API 계약 참조 | 배포 |
80
80
  | [CorrelationMatrix](components/correlation-matrix.md) | 구성/정보 표시 | 여러 지표 사이의 상관계수(-1~1)를 정사각 대칭 표와 양·음 두 방향 heat 색으로 보여 줄 때 쓴다. | 1.21.0 | Web · Native | 개별 API 계약 참조 | 배포 |
81
81
  | [CounterBadge](components/counter-badge.md) | 데이터 표시 | 읽지 않은 알림·메시지·장바구니 수처럼 **셀 수 있는 개수**를 아이콘·행 옆에 작게 보일 때 쓴다. | 1.12.1 | Web · Native | Web stable / Native stable | 배포 |
@@ -135,7 +135,7 @@ Storybook 배포/실험은 예제 분류이며 npm 게시·기기 검증·제품
135
135
  | [OnboardingScreen](components/onboarding-screen.md) | 화면/소개 | 첫 실행 소개·초기 설정처럼 **몇 단계를 차례로 넘기는 화면**에 쓴다. | 1.14.0 | Web · Native | Web stable / Native stable | 배포 |
136
136
  | [OtpField](components/otp-field.md) | 입력 | 문자·메일로 받은 **숫자 인증번호**를 칸 모양으로 입력받을 때 쓴다. | 1.12.1 | Web · Native | Web stable / Native stable | 배포 |
137
137
  | [OverviewScreen](components/overview-screen.md) | 레이아웃 | 같은 데이터와 기능을 유지하면서 테마별 행·카드·격자와 도구 배치를 선택하는 목록 화면에 쓴다. | 1.15.0 | Web · Native | 개별 API 계약 참조 | 배포 |
138
- | [PageIndicator](components/page-indicator.md) | 탐색 | 제품이 직접 만든 가로 페이저(Native 페이징 `FlatList`, Web scroll-snap 줄) 아래에 지금 몇 번째 페이지인지 점으로 보여 주고, 점을 눌러 그 페이지로 옮길 때 쓴다. | 미게시(1.30.0 이후) | Web · Native | Web beta / Native beta | 실험 |
138
+ | [PageIndicator](components/page-indicator.md) | 탐색 | 제품이 직접 만든 가로 페이저(Native 페이징 `FlatList`, Web scroll-snap 줄) 아래에 지금 몇 번째 페이지인지 점으로 보여 주고, 점을 눌러 그 페이지로 옮길 때 쓴다. | 1.31.0 | Web · Native | Web beta / Native beta | 배포 |
139
139
  | [Pagination](components/pagination.md) | 탐색 | 총 개수(또는 총 페이지 수)가 정해진 결과 집합에서 사용자가 **임의의 페이지로 바로 이동**해야 할 때 Web·Native에서 쓴다. | 1.12.1 | Web · Native | Web stable / Native beta | 배포 |
140
140
  | [PasswordField](components/password-field.md) | 입력 | 비밀번호를 입력받고, 필요할 때만 값을 눈으로 확인하게 할 때 쓴다. | 1.12.1 | Web · Native | Web stable / Native stable | 배포 |
141
141
  | [PasswordRulesField](components/password-rules-field.md) | 구성/입력과 작성 | 새 비밀번호 입력 아래에 제품이 정한 규칙의 충족 개수와 규칙별 상태를 함께 보여 줄 때 쓴다. | 1.21.0 | Web · Native | 개별 API 계약 참조 | 배포 |
@@ -205,7 +205,7 @@ Storybook 배포/실험은 예제 분류이며 npm 게시·기기 검증·제품
205
205
  | [TransferList](components/transfer-list.md) | 입력 | 한 항목 집합을 두 목록으로 나누고 사용자가 항목을 오가게 할 때 쓴다. | 1.12.1 | Web · Native | Web stable / Native stable | 배포 |
206
206
  | [TransferTask](components/transfer-task.md) | 구성/피드백과 복구 | 업로드·다운로드·동기화처럼 실제 전송 계층이 진행량을 알려 주는 작업에 시작·취소·진행률·결과를 한 묶음으로 붙인다. | 1.21.0 | Web · Native | 개별 API 계약 참조 | 배포 |
207
207
  | [Tree](components/tree.md) | 데이터 표시 | 깊이가 정해지지 않은 계층 데이터를 펼치고 접으며 탐색하고, 그 안에서 하나 또는 여럿을 고를 때 쓴다. | 1.12.1 | Web | Web stable / Native unsupported | 배포 |
208
- | [TypingIndicator](components/typing-indicator.md) | 상태와 알림 | 대화 타임라인에서 상대가 지금 입력 중임을 보여 줄 때 쓴다. | 미게시(1.30.0 이후) | Web · Native | Web beta / Native beta | 실험 |
208
+ | [TypingIndicator](components/typing-indicator.md) | 상태와 알림 | 대화 타임라인에서 상대가 지금 입력 중임을 보여 줄 때 쓴다. | 1.31.0 | Web · Native | Web beta / Native beta | 배포 |
209
209
  | [UploadItem](components/upload-item.md) | 데이터 표시 | 사용자가 고른 파일 **한 개**의 업로드 상태(대기·전송 중·완료·실패)를 한 행으로 보여 줄 때 쓴다. | 1.12.1 | Web · Native | Web stable / Native stable | 배포 |
210
210
  | [VirtualList](components/virtual-list.md) | 데이터 표시 | 행 높이가 **모두 같은** 긴 목록(수백~수천 행)을 정해진 높이 안에서 스크롤할 때 쓴다. | 1.12.1 | Web · Native | Web stable / Native stable | 배포 |
211
211
  | [VisuallyHidden](components/visually-hidden.md) | 기반 기능 | Web에서 화면에는 보이지 않지만 스크린 리더는 읽어야 하는 **문맥 문구**를 덧붙일 때 쓴다. | 1.12.1 | Web | Web stable / Native unsupported | 배포 |
@@ -305,7 +305,7 @@ Storybook 배포/실험은 예제 분류이며 npm 게시·기기 검증·제품
305
305
  | [사진 선택과 업로드](screens/flow-media.md) | 콘텐츠 | MediaSelectionScreen을 사용해 사진 선택과 업로드 흐름을 구성한다. | 구성 API 지침 참조 | Web · Native | 구성 API별 | 배포 |
306
306
  | [작성과 수정](screens/flow-editor.md) | 콘텐츠 | EditorScreen을 사용해 작성과 수정 흐름을 구성한다. | 구성 API 지침 참조 | Web · Native | 구성 API별 | 배포 |
307
307
  | [저장한 항목](screens/common-saved.md) | 콘텐츠 | SavedItemsScreen을 사용해 저장한 항목 흐름을 구성한다. | 구성 API 지침 참조 | Web · Native | 구성 API별 | 배포 |
308
- | [대화 목록](screens/common-conversation-list.md) | 소통 | DM·채팅방 목록에서 상대·마지막 메시지·시각·읽지 않음을 훑고, 행을 눌러 대화로 들어가며, 길게 눌러 행 단위 동작(읽지 않음 표시·알림 해제·차단·삭제)을 고른다. | 구성 API 지침 참조 | Web · Native | 구성 API별 | 실험 |
308
+ | [대화 목록](screens/common-conversation-list.md) | 소통 | DM·채팅방 목록에서 상대·마지막 메시지·시각·읽지 않음을 훑고, 행을 눌러 대화로 들어가며, 길게 눌러 행 단위 동작(읽지 않음 표시·알림 해제·차단·삭제)을 고른다. | 구성 API 지침 참조 | Web · Native | 구성 API별 | 배포 |
309
309
  | [댓글](screens/common-comments.md) | 소통 | CommentThreadScreen을 사용해 댓글 흐름을 구성한다. | 구성 API 지침 참조 | Web · Native | 구성 API별 | 배포 |
310
310
  | [신고와 차단](screens/flow-moderation.md) | 소통 | ModerationScreen을 사용해 신고와 차단 흐름을 구성한다. | 구성 API 지침 참조 | Web · Native | 구성 API별 | 배포 |
311
311
  | [알림함](screens/common-inbox.md) | 소통 | NotificationInboxScreen을 사용해 알림함 흐름을 구성한다. | 구성 API 지침 참조 | Web · Native | 구성 API별 | 배포 |
@@ -46,7 +46,7 @@
46
46
  | 화면 나가기(뒤로·닫기·설정 나가기) | `ScreenLayout`(과 이를 쓰는 화면)·`AuthScreenLayout`의 `exit` — 헤더 시작 쪽 아이콘 버튼, 글자 없음. 단계형 작성의 앞 단계는 `EditorScreen` `stepBack`(1.31.0) | 본문·하단의 "나가기" 글자 버튼, 화면마다 다른 화살표, 로그인·작성 화면 위에 따로 얹은 `TopBar` |
47
47
  | 버튼 묶음 간격 | 세로로 쌓으면 `gap="xs"`(8), 가로로 나란하면 `gap="sm"`(12) — `actionGroupRecipe` | 세로 버튼 사이 12 이상 |
48
48
  | 간격 | `Stack`·`ScreenLayout`·recipe 간격 | 임의 `margin`·빈 `View` |
49
- | 화면 진입 시 입력 포커스 | `ScreenLayout`(과 이를 쓰는 화면)·`AuthScreenLayout`·`Sheet`·`Dialog`의 `initialFocus`, 주 입력 지정은 칸의 `initialFocusTarget`([진입 포커스](components/screen-layout.md#진입-포커스)) | 앱별 `autoFocus`·지연 `ref.focus()`·단계 전환·검색 열기 뒤 `ref.focus()`, 대화·댓글 화면의 작성창 자동 포커스 |
49
+ | 페이지 진입 시 입력 포커스 | `ScreenLayout`·`AuthScreenLayout`의 기본 `first-input`, 주 입력 지정은 `initialFocusTarget`. 하단 시트는 자동 입력 포커스 없음([진입 포커스](components/screen-layout.md#진입-포커스), 1.33.0 정책 변경) | 앱별 `autoFocus`·지연 `ref.focus()`·단계 전환·시트 열기 뒤 자동 포커스 |
50
50
 
51
51
  리뷰 기준: 위 "쓰지 않을 것" 패턴이 보이면 예제 조립 문제인지 컴포넌트 recipe 문제인지 먼저 가리고, 원인 쪽(예제 또는 HJM 컴포넌트)을 고친다.
52
52
 
@@ -84,7 +84,7 @@ import { AuthScreenFooter, AuthScreenHero, AuthScreenLayout } from "@hjmds/react
84
84
  | `pendingLabel` | 문자열 | — | 주면 진행 상태다. 공백만 있으면 `TypeError`. 제거하면 원래 상태로 돌아온다 |
85
85
  | Web `as` | `main` · `section` | `main` | 최대 폭은 416이다 |
86
86
  | `layoutStyle` | margin·width·flex·`alignSelf` | — | 바깥 배치 전용(Web·Native 모두) |
87
- | `initialFocus` | `first-input` · `none` | `none` | 1.28.2. [ScreenLayout 진입 포커스](screen-layout.md#진입-포커스)와 같은 규칙. 로그인은 제공자 버튼이 주 행동이라 기본 `none`, 가입 닉네임 단계처럼 입력이 주 행동인 `main`만 `first-input`. 닉네임 단계가 같은 화면에서 나중에 나타나면 그 칸에 `initialFocusTarget`을 준다 |
87
+ | `initialFocus` | `first-input` · `none` | `first-input` | 페이지 진입 시 첫 유효 입력 또는 `initialFocusTarget` 칸에 한 번 포커스한다. 기본값·진입 한정 정책 변경은 1.33.0부터 제공. 진입 종료 뒤 단계 변경·입력 표시로 자동 포커스를 다시 시작하지 않으며 하단 시트 안에서는 자동 포커스하지 않는다. [진입 포커스](screen-layout.md#진입-포커스) |
88
88
  | Native `testID` | `string` | — | 본문 `ScrollView` 식별(머리 띠가 있어도 같은 `ScrollView`) |
89
89
  | Native `safeAreaBottom` | 0 이상의 유한수 | `0` | 화면이 닿는 창 아래 가장자리의 safe area(`useSafeAreaInsets().bottom`, 이미 아래를 들인 host 안이면 0). 아래 여백이 `max(paddingBlock, inset)` — `regular` max(40, inset), `compact` max(20, inset) — 으로 한 번만 들어간다(ScreenLayout과 같은 규칙, 합산 없음). 키보드가 열리면 paddingBlock만. 음수·무한대는 `RangeError` |
90
90
  | `AuthScreenHero` `title` · `eyebrow` · `description` · `mark` · `headingLevel` | 문자열 · `ReactNode` · `1`~`3` | `headingLevel` 1 | 제목은 필수이고 빈 문자열이면 `TypeError`. 밀도는 바깥 `AuthScreenLayout`을 따른다 |
@@ -1,12 +1,12 @@
1
1
  # ChatDateSeparator
2
2
 
3
3
  - 단계: 컴포넌트
4
- - 상태: 실험
4
+ - 상태: 배포
5
5
  - 지원: Web · Native
6
- - 적용: 미게시(1.30.0 이후)
6
+ - 적용: 1.31.0
7
7
  - 검토일: 2026-10-18
8
8
  - 근거: [반복 화면 조합](../../screen-patterns.md), `src/screen-patterns.ts`의 `chatDateSeparatorRecipe`; Web·Native `src/screens.tsx`
9
- - 스토리북: `실험/컴포넌트/데이터 표시/대화 날짜 구분`
9
+ - 스토리북: `배포/컴포넌트/데이터 표시/대화 날짜 구분`
10
10
 
11
11
  ## 언제 쓰나
12
12
 
@@ -29,7 +29,7 @@ DM·대화방처럼 헤더, 메시지 타임라인, 하단 작성창으로 이
29
29
  | --- | --- | --- | --- |
30
30
  | `ChatScreen` | 화면 조합 | `/screens` | `/screens` |
31
31
  | `ChatMessage`, `MessageComposer`, `ScreenLayout` | 함께 쓰는 조각 | `/screens` | `/screens` |
32
- | [`TypingIndicator`](typing-indicator.md), [`ChatDateSeparator`](chat-date-separator.md) | 타임라인 조각(입력 중 표시, 날짜 구분). 미게시(1.30.0 이후) | `/screens` | `/screens` |
32
+ | [`TypingIndicator`](typing-indicator.md), [`ChatDateSeparator`](chat-date-separator.md) | 타임라인 조각(입력 중 표시, 날짜 구분). 1.31.0 | `/screens` | `/screens` |
33
33
 
34
34
  루트 barrel에는 없다. `/screens`는 optional native peer를 요구하지 않는다.
35
35
 
@@ -80,7 +80,7 @@ import { ChatScreen, MessageComposer } from "@hjmds/react-native/screens";
80
80
 
81
81
  | prop | 값 | 기본값 | 설명 |
82
82
  | --- | --- | --- | --- |
83
- | `initialFocus` | `first-input` · `none` | `none` | 1.28.1. 대화는 기록을 읽는 것이 먼저라 기본으로 작성창에 포커스하지 않는다 — 키보드가 올라오면 읽던 메시지를 가린다. 새 대화 시작처럼 쓰기가 먼저인 화면만 `first-input` |
83
+ | `initialFocus` | `first-input` · `none` | `first-input` | 페이지 진입 시 첫 유효 입력 또는 `initialFocusTarget` 칸에 한 번 포커스한다. 기본값·진입 한정 정책 변경은 1.33.0부터 제공. 진입 종료 뒤 단계 변경·입력 표시로 자동 포커스를 다시 시작하지 않으며 하단 시트 안에서는 자동 포커스하지 않는다. [진입 포커스](screen-layout.md#진입-포커스) |
84
84
  | `composer` | `ReactNode` | 필수 | footer에 고정되는 작성창. `ready`와 `empty`에서 보인다(빈 대화의 첫 메시지 입력) |
85
85
  | `headerAlignment` | `"center"` · `"start"` | `"center"` | 뒤로·도구 사이에 상대 이름을 가운데 둔다(카카오톡·인스타그램 실측) |
86
86
  | `scroll` | `"content"` · `"screen"` | `"content"` | 가상화 목록이 스크롤을 소유하므로 작은 예제 외에는 바꾸지 않는다 |
@@ -119,7 +119,7 @@ threadHeader도 댓글과 함께 숨겨지므로 항상 보여야 하는 안내
119
119
 
120
120
  ## 꼭 지킬 것
121
121
 
122
- - 진입 포커스(`initialFocus`)는 기본 `none`이다(1.28.1). 댓글은 읽는 것이 먼저라 작성창 키보드가 스레드를 가리면 안 된다. 답글 쓰기로 바로 들어가는 화면만 `initialFocus="first-input"`을 준다.
122
+ - 진입 포커스(`initialFocus`)는 페이지에서 기본 `first-input`이다(1.33.0 정책 변경). 하단 시트 안에서는 중첩 화면·작성창 모두 자동 포커스하지 않는다. [진입 포커스](screen-layout.md#진입-포커스)를 따른다.
123
123
 
124
124
  - id는 비어 있지 않고 유일해야 하며, 답글의 `parentId`는 `items` 안의 **최상위** 댓글이어야 한다. 아니면 렌더 중
125
125
  `TypeError`가 난다. 답글의 답글은 표현하지 않으므로 제품이 최상위로 평탄화한다.
@@ -1,12 +1,12 @@
1
1
  # ConversationRow
2
2
 
3
3
  - 단계: 컴포넌트
4
- - 상태: 실험
4
+ - 상태: 배포
5
5
  - 지원: Web · Native
6
6
  - 적용: 1.24.0
7
7
  - 검토일: 2026-10-17
8
8
  - 근거: `src/conversation-row.ts`의 `conversationRowRecipe`
9
- - 스토리북: `실험/화면/소통/대화 목록`
9
+ - 스토리북: `배포/화면/소통/대화 목록`
10
10
 
11
11
  ## 언제 쓰나
12
12
 
@@ -71,7 +71,7 @@ import { ConversationRow } from "@hjmds/react-native/conversation-row";
71
71
 
72
72
  메시지가 길어도 시각과 읽지 않음 점은 유지하고 미리보기만 말줄임한다. 앱은 별도 행 padding을 넣지 않는다.
73
73
 
74
- 길게 눌러 여는 행 메뉴(읽지 않음 표시·알림 해제·차단·삭제 등)는 행 호스트를 [ContextMenu](context-menu.md)로 감싼다. 위험 항목은 `tone: "danger"`이며 목록 끝에 둔다. 예제: `실험/화면/소통/대화 목록`.
74
+ 길게 눌러 여는 행 메뉴(읽지 않음 표시·알림 해제·차단·삭제 등)는 행 호스트를 [ContextMenu](context-menu.md)로 감싼다. 위험 항목은 `tone: "danger"`이며 목록 끝에 둔다. 예제: `배포/화면/소통/대화 목록`.
75
75
 
76
76
  ## 꼭 지킬 것
77
77
 
@@ -114,7 +114,7 @@ await handle.closed; // portal 제거·초점 복구 뒤. 다음 오버레이는
114
114
 
115
115
  | prop | 값 | 기본값 | 설명 |
116
116
  | --- | --- | --- | --- |
117
- | `initialFocus` | `single-input` · `first-input` · `none` | `single-input` | 1.28.1. 열린 뒤(Native: 표시·진입 애니메이션 뒤, Web: 포커스 이동 시점) 포커스할 텍스트 입력. 기본은 텍스트 입력이 **하나뿐일 때만** 그 칸(이름 바꾸기·메모 한 줄), 여럿이면 기존 규칙(Web 첫 포커스 가능 요소, Native 접근성 포커스) 그대로. `first-input`은 `initialFocusTarget` 칸, 없으면 첫 칸. `initialFocusRef`(Web)가 있으면 그쪽이 이긴다. 열 때마다 다시 고른다. 1.28.2: 열린 뒤 칸이 늦게 나타나도 조작 전이면 고르고, 단계형 내용은 각 단계 칸에 `initialFocusTarget`을 주면 단계가 바뀔 때 그 칸을 포커스한다(앱의 단계별 `ref.focus()` 대신). 위에 다른 오버레이가 열려 있는 동안은 고르지 않는다 |
117
+ | `initialFocus` | `single-input` · `first-input` · `none` | `single-input` | 1.28.1. 열린 뒤(Native: 표시·진입 애니메이션 뒤, Web: 포커스 이동 시점) 포커스할 텍스트 입력. 기본은 텍스트 입력이 **하나뿐일 때만** 그 칸(이름 바꾸기·메모 한 줄), 여럿이면 기존 규칙(Web 첫 포커스 가능 요소, Native 접근성 포커스) 그대로. `first-input`은 `initialFocusTarget` 칸, 없으면 첫 칸. `initialFocusRef`(Web)가 있으면 그쪽이 이긴다. 열 때마다 다시 고른다. 1.28.2: 열린 뒤 칸이 늦게 나타나도 조작 전이면 고르고, 진입 종료 뒤 단계가 바뀌거나 입력이 새로 나타나도 자동 포커스를 다시 시작하지 않는다(1.33.0 정책 변경). 위에 다른 오버레이가 열려 있는 동안은 고르지 않는다 |
118
118
  | `size` | `small` · `medium` · `large` | `medium` | Native recipe 최대 폭 320 · 420 · 640. Web 최대 폭 28rem · 36rem · 48rem(아래 배치 표) |
119
119
  | `dismissible` | `true` · `false` | `true` | `false`면 닫기 버튼과 바깥·Escape/back 닫기가 사라진다 |
120
120
  | `busy` | `true` · `false` | `false` | 비동기 작업 중에는 `open`을 유지하고 `busy`로 반복 행동과 닫기를 막는다 |
@@ -91,7 +91,7 @@ import { TextField } from "@hjmds/react-native/inputs";
91
91
 
92
92
  | prop | 값 | 기본값 | 설명 |
93
93
  | --- | --- | --- | --- |
94
- | `initialFocus` | `first-input` · `none` | `first-input` | 1.28.1. 작성·편집이 화면의 목적이라 진입 후 첫 입력(또는 `initialFocusTarget` 칸)에 한 번 포커스한다. 읽고 고칠 내용이 먼저인 편집은 `none`. 1.28.2: 단계형 작성(제목 → 본문)은 각 단계 칸에 `initialFocusTarget`을 주면 단계가 바뀔 때 그 칸을 포커스한다 |
94
+ | `initialFocus` | `first-input` · `none` | `first-input` | 페이지 진입 시 첫 유효 입력 또는 `initialFocusTarget` 칸에 한 번 포커스한다. 기본값·진입 한정 정책 변경은 1.33.0부터 제공. 진입 종료 뒤 단계 변경·입력 표시로 자동 포커스를 다시 시작하지 않으며 하단 시트 안에서는 자동 포커스하지 않는다. [진입 포커스](screen-layout.md#진입-포커스) |
95
95
  | `dirty` | `boolean` | 필수 | `true`에서 닫기를 누르면 `discard` 확인창을 띄우고, 확인하면 `cancel.onAction`을 부른다. `false`면 바로 부른다 |
96
96
  | `submit` | `ScreenFlowAction`(`{ label, onAction(), disabled?, pending? }`) | 필수 | `pending`이면 loading·비활성. 그동안 닫기 버튼도 비활성. 본문 `state`가 `ready`가 아니면 저장 비활성 |
97
97
  | `cancel` | `ScreenFlowAction` | 필수 | 헤더 시작 쪽 × 아이콘 닫기 버튼(`ScreenLayout` `exit` `close`). `label`은 보조기술 이름이다. 2026-10-17부터 글자 버튼 대신 아이콘 |
@@ -70,7 +70,7 @@ import { Field } from "@hjmds/react-native/forms";
70
70
 
71
71
  | prop | 값 | 기본값 | 설명 |
72
72
  | --- | --- | --- | --- |
73
- | `initialFocusTarget` | `boolean` | — | 1.28.1. 둘러싼 화면·시트가 진입 포커스를 켰을 때 `true`면 이 칸을 고르고, `false`면 절대 고르지 않는다. 생략하면 트리 순서대로 후보다. 비활성·읽기 전용 칸은 고르지 않는다. 1.28.2부터 `true` 칸이 진입 뒤에 나타나면(검색 열기·단계 이동) 모드와 무관하게 그때 포커스한다. 앱별 `autoFocus`·지연 `ref.focus()` 대신 쓴다([진입 포커스](screen-layout.md#진입-포커스)). TextField·TextArea·SearchField·PasswordField 공통 |
73
+ | `initialFocusTarget` | `boolean` | — | 페이지 진입 시 `true`인 칸을 우선 선택하며 `false`인 칸은 제외한다. 생략하면 첫 유효 입력을 고른다. 비활성·읽기 전용은 제외한다. 1.33.0 정책 변경: 진입 종료 뒤 새로 나타난 칸과 하단 시트 입력은 자동 포커스하지 않는다([진입 포커스](screen-layout.md#진입-포커스)). TextField·TextArea·SearchField·PasswordField 공통 |
74
74
  | `variant`(`TextField`) | `surface` · `inset` | `surface` | — |
75
75
  | `shape`(`TextField`) | `medium` · `large` · `full` | `medium` | — |
76
76
  | `align`(`TextField`) | `start` · `center` | `start` | `center`는 닉네임·코드처럼 짧은 한 값 |
@@ -1,12 +1,12 @@
1
1
  # PageIndicator
2
2
 
3
3
  - 단계: 컴포넌트
4
- - 상태: 실험
4
+ - 상태: 배포
5
5
  - 지원: Web · Native
6
- - 적용: 미게시(1.30.0 이후)
6
+ - 적용: 1.31.0
7
7
  - 검토일: 2026-10-18
8
8
  - 근거: [Carousel](../../carousel.md), contract `src/carousel.ts`(`resolvePageIndicator`·`getPageIndicatorTarget`), recipe `carouselRecipe.dot`
9
- - 스토리북: `실험/컴포넌트/탐색/페이지 위치 표시`
9
+ - 스토리북: `배포/컴포넌트/탐색/페이지 위치 표시`
10
10
 
11
11
  ## 언제 쓰나
12
12
 
@@ -87,7 +87,7 @@ import { ScreenLayout } from "@hjmds/react-native/screens";
87
87
  | `contentInset` | `"default"` · `"none"` · `"body-edge"` | `"default"` | `none`은 헤더·notice·본문·footer padding을 0으로(Native는 footer 위 테두리도 뺀다). host가 모든 여백을 이미 줄 때 쓴다. `body-edge`는 헤더·notice·footer 여백(16)과 footer 테두리를 유지하고 **본문 좌우 padding만 0**으로 한다. 자기 좌우 여백을 가진 전체 폭 목록 행(ConversationRow·NotificationRow 등 행 padding 16)을 본문에 바로 둘 때 쓴다(미게시). ListRow·NotificationItem은 `default`에 둔다. `body-bleed`(미게시, 1.31.0 이후)는 같은 여백을 유지하고 **본문 네 방향 padding을 모두 0**으로 해 지도·캔버스가 헤더(또는 notice)·화면 좌우·footer에 바로 닿는다. footer가 없으면 본문이 마지막 영역이라 여백 16 없이 `safeAreaBottom`(Native)과 겹친 하단 탐색 높이만 비운다. 홈 인디케이터 아래까지 그리려면 `safeAreaBottom`을 넘기지 않는다. `body-edge`·`body-bleed`에서도 로딩·빈 상태·오류 안내 묶음은 자기 여백이 없으므로 좌우 16을 유지한다(1.32.0 수정). 값 목록은 계약 `ScreenContentInset`·`screenContentInsets` |
88
88
  | `presentation` | `"dashboard"` · `"editorial"` · `"landscape"` | 가장 가까운 profile.screens.overview / 없으면 기존 골격 | [디자인 프로필](../../design-profile.md)의 제공 범위를 따른다. 명시값이 프로필을 이긴다. 헤더 배치·읽기 폭만 바꾸고 상태·입력·행동 슬롯은 유지한다 |
89
89
  | `as` (Web) | `"main"` · `"section"` | `"main"` | 제품 셸에 이미 `<main>`이 있으면 `section` |
90
- | `initialFocus` | `first-input` · `none` | `none` | 1.28.1. 진입 시 텍스트 입력 하나에 한 번 포커스한다. 아래 [진입 포커스](#진입-포커스) |
90
+ | `initialFocus` | `first-input` · `none` | `first-input` | 페이지 진입 시 입력 하나에 한 번 포커스한다. 기본값 변경은 1.33.0부터 제공. 하단 시트 안에서는 자동 포커스하지 않는다. 아래 [진입 포커스](#진입-포커스) |
91
91
  | `safeAreaBottom` (Native) | 0 이상의 유한수 | `0`(미게시) | 화면이 닿는 창 아래 가장자리의 safe area(`useSafeAreaInsets().bottom`, 탭바 위·이미 아래를 들인 host 안이면 0). 마지막 영역(footer, 없으면 본문)이 아래 여백을 `max(16, inset)`으로 한 번만 준다. 넘길 때는 host의 아래 SafeAreaView edge를 빼고, 둘을 같이 쓰지 않는다. 음수·무한대는 `RangeError` |
92
92
  | `layoutStyle` | `HjmCompositionStyleProp` | 없음 | 화면 루트 배치 전용(예: 분할 화면의 `flex`·`width`). ScreenLayout 위에 만든 화면은 모두 이 prop을 루트까지 넘긴다 |
93
93
  | `className` (Web) | `string` | 없음 | 식별·배치 보조. 색·여백을 덮지 않는다 |
@@ -106,25 +106,28 @@ import { ScreenLayout } from "@hjmds/react-native/screens";
106
106
 
107
107
  ## 진입 포커스
108
108
 
109
+ 2026-10-18 후속 사용자 지시: “페이지 진입에만 적용하고 하단 시트에는 폐지해 포커스”. 일반·대화·인증 페이지의 기존 `none`과 시트의 단일 입력 자동 포커스를 이 기준으로 교체한다. **1.33.0에서 제공한다.**
110
+
109
111
  2026-10-18 사용자 요청("input이 있는 화면에 진입하면 포커스되도록, 웹앱 둘 다"). 앱마다 `autoFocus`·지연 `ref.focus()`를 붙이던 것을 화면 골격이 맡는다. 입력 칸(TextField·TextArea·SearchField·PasswordField, 작성창의 TextArea)은 가장 가까운 화면·시트 범위에 자신을 등록하고, 범위가 진입 뒤 한 칸을 고른다. 규칙의 정본은 contracts `resolveInitialFocusTarget`·`resolveEntryFocus`·`initialFocusDefaults`(`screen-patterns`)다.
110
112
 
111
113
  | 구성 | 기본값 | 이유 |
112
114
  | --- | --- | --- |
113
- | `ScreenLayout`(일반 화면) | `none` | 입력이 화면의 주 행동인지 화면이 알 수 없다. 주 입력 화면은 `initialFocus="first-input"` |
115
+ | `ScreenLayout`(일반 화면) | `first-input` | 페이지 진입 시 입력이 있으면 첫 유효 입력 선택 |
114
116
  | `SearchScreen` | `first-input`(검색어) | 검색이 화면의 목적 |
115
117
  | `EditorScreen` | `first-input` | 작성·편집이 화면의 목적 |
116
- | `ChatScreen`·`CommentThreadScreen` | `none` | 입력은 읽기의 보조. 키보드가 기록을 가린다. 같은 prop으로 켤 수 있다 |
117
- | `Sheet`·`Dialog`·`AlertDialog` | `single-input` | 텍스트 입력이 하나뿐일 때만(이름 바꾸기 등). 여럿이면 기존 포커스 규칙 |
118
- | `AuthScreenLayout` | `none` | 로그인 화면은 제공자 버튼이 주 행동. 가입 닉네임 단계만 `initialFocus="first-input"`(1.28.2) |
118
+ | `ChatScreen`·`CommentThreadScreen` | `first-input` | 별도 페이지로 진입하면 작성창도 같은 규칙 적용 |
119
+ | 하단 `Sheet` | 자동 입력 포커스 없음 | 입력 개수·`initialFocus`·중첩 화면·`initialFocusTarget`과 무관하게 자동 포커스하지 않음. Web은 모달 컨테이너에 초점 유지 |
120
+ | `Dialog`·`AlertDialog`·측면 `Sheet` | 기존 모달 규칙 | `single-input` 등 기존 모달 포커스 계약 유지 |
121
+ | `AuthScreenLayout` | `first-input` | 페이지 진입 시 텍스트 입력이 있으면 선택. 제공자 버튼만 있으면 입력 포커스 없음 |
119
122
 
120
123
  - 고르는 순서: `initialFocusTarget` 칸 → 등록(트리) 순서 첫 칸. 비활성·읽기 전용 칸, `initialFocusTarget={false}` 칸은 고르지 않는다.
121
- - 시점: Native는 화면·시트 진입 전환이 끝난 뒤(`InteractionManager`, 시트는 표시 뒤) — 키보드 애니메이션이 전환과 겹치지 않는다. Web은 mount 직후, `preventScroll`.
124
+ - 시점: Native 페이지는 진입 전환이 끝난 뒤(`InteractionManager`) — 키보드 애니메이션이 전환과 겹치지 않는다. Web은 mount 직후, `preventScroll`.
122
125
  - 빼앗지 않는다: 사용자가 이미 입력 칸에 있으면 그대로 둔다(Native 포커스된 입력, Web 편집 가능한 요소).
123
126
  - 오버레이 아래(1.28.2, Web·Native): 위에 HJM `Dialog`·`AlertDialog`·`Sheet`가 열려 있으면 아래 화면·시트는 고르지 않는다. Web은 제품 자체 모달(`aria-modal`·`dialog[open]`)도 막는다. Native는 HJM 오버레이만 알 수 있으므로 화면을 덮는 모달은 HJM 오버레이로 만든다.
124
- - 오버레이 안의 화면(1.31.0, Web·Native): 화면을 **감싸는** 모달은 그 화면을 막지 않는다. `Dialog`·`Sheet` 본문에 둔 `ScreenLayout`(또는 화면 구성)은 오버레이가 열린 뒤(Web: 모달 활성화, Native: 표시·진입 애니메이션 뒤) 오버레이와 함께 진입해 자기 `initialFocus` 규칙으로 칸을 고른다. 오버레이 자신의 칸이 있으면 그 칸이 먼저이고, 오버레이에 `initialFocusRef`(Web)가 있으면 그쪽이 이긴다. Web 제품 모달(`aria-modal`)도 화면의 입력 칸을 품고 있으면 화면을 감싸는 것으로 보고 막지 않는다. 화면 밖에 열린 제품 모달은 그대로 막는다.
127
+ - 오버레이 안의 화면(Web·Native, 하단 시트 제외): 화면을 **감싸는** 모달은 그 화면을 막지 않는다. `Dialog`·측면 `Sheet` 본문에 둔 `ScreenLayout`(또는 화면 구성)은 오버레이가 열린 뒤(Web: 모달 활성화, Native: 표시·진입 애니메이션 뒤) 오버레이와 함께 진입해 자기 `initialFocus` 규칙으로 칸을 고른다. 오버레이 자신의 칸이 있으면 그 칸이 먼저이고, 오버레이에 `initialFocusRef`(Web)가 있으면 그쪽이 이긴다. Web 제품 모달(`aria-modal`)도 화면의 입력 칸을 품고 있으면 화면을 감싸는 것으로 보고 막지 않는다. 화면 밖에 열린 제품 모달은 그대로 막는다.
125
128
  - 진입 창(1.28.2): 진입 뒤 아직 포커스하지 못한 범위는 사용자가 그 범위를 직접 조작(Native 터치·스크롤, Web 포인터·키·휠·터치)하기 전까지 열려 있다. 그 동안 ① 칸이 새로 나타나면(데이터 로드 뒤 드러나는 확인 칸) ② 진입 때 위에 있던 오버레이가 닫히면(첫 가입 약관 동의) 다시 고른다. 오버레이 안의 조작(동의 탭)은 아래 화면의 조작으로 세지 않는다. 오버레이가 닫힌 시점은 그 오버레이의 반환 포커스와 같은 시점(Native: 퇴장·dismiss 뒤, Web: 반환 포커스 뒤)이다.
126
- - 나중에 드러나는 주 입력(1.28.2): 진입이 끝난 뒤 `initialFocusTarget`(`true`) 칸이 새로 mount되면 그 칸을 포커스한다. `initialFocus` 모드와 무관하다 — 검색 버튼으로 여는 검색 칸(`none` 화면), 단계형 시트·`EditorScreen`의 단계 칸(각 단계 칸에 `initialFocusTarget`)이 이 경우다. 다른 입력 칸에 초점이 있거나 위에 오버레이가 있으면 하지 않는다. 스스로 다시 mount되는 칸(가상화 목록 행)에는 주지 않는다.
127
- - 다시 진입: 화면은 mount당 한 번이다. 스택에서 돌아온 화면(이미 mount)은 다시 포커스하지 않는다 — 돌아온 맥락을 키보드가 가리고, HJM은 내비게이션 포커스 신호를 모른다. 시트·대화상자는 열 때마다 다시 고른다.
129
+ - 진입 종료 뒤 입력: 사용자 조작이나 첫 포커스 이후 새로 나타난 입력은 `initialFocusTarget`이 있어도 자동 포커스하지 않는다. 검색 열기·단계 이동은 새 페이지 진입이 아니다.
130
+ - 다시 진입: 화면은 mount당 한 번이다. 스택에서 돌아온 화면(이미 mount)은 다시 포커스하지 않는다 — 돌아온 맥락을 키보드가 가리고, HJM은 내비게이션 포커스 신호를 모른다. 하단 시트는 자동 입력 포커스를 하지 않고, 대화상자·측면 시트는 기존 모달 규칙으로 다시 고른다.
128
131
  - 모션 감소는 애니메이션에만 적용하고 포커스는 그대로 한다.
129
132
  - 제품은 `autoFocus`·`setTimeout(() => ref.focus())`·단계 전환 뒤 `ref.focus()`를 쓰지 않고 `initialFocus`·`initialFocusTarget`으로 표현한다. 검증 실패 칸으로 옮기는 포커스(제출 오류)는 제품의 동작이라 그대로 둔다.
130
133
 
@@ -78,7 +78,7 @@ import { Sheet } from "@hjmds/react-native/overlays";
78
78
 
79
79
  | prop | 값 | 기본값 | 설명 |
80
80
  | --- | --- | --- | --- |
81
- | `initialFocus` | `single-input` · `first-input` · `none` | `single-input` | 1.28.1. 열린 뒤(Native: 표시·진입 애니메이션 뒤, Web: 포커스 이동 시점) 포커스할 텍스트 입력. 기본은 텍스트 입력이 **하나뿐일 때만** 그 칸(이름 바꾸기·메모 한 줄), 여럿이면 기존 규칙(Web 첫 포커스 가능 요소, Native 접근성 포커스) 그대로. `first-input`은 `initialFocusTarget` 칸, 없으면 첫 칸. `initialFocusRef`(Web)가 있으면 그쪽이 이긴다. 열 때마다 다시 고른다. 1.28.2: 열린 뒤 칸이 늦게 나타나도 조작 전이면 고르고, 단계형 내용은 각 단계 칸에 `initialFocusTarget`을 주면 단계가 바뀔 때 그 칸을 포커스한다(앱의 단계별 `ref.focus()` 대신). 위에 다른 오버레이가 열려 있는 동안은 고르지 않는다 |
81
+ | `initialFocus` | `single-input` · `first-input` · `none` | 하단: 자동 포커스 없음 / 측면: `single-input` | 1.33.0 정책 변경. 하단 시트는 이 prop·`initialFocusRef`·중첩 화면·`initialFocusTarget`으로 입력 자동 포커스를 켤 수 없다. Web은 모달 컨테이너로 초점을 옮겨 focus trap을 유지하고 Native는 키보드를 열지 않는다. 측면 시트는 기존 Dialog 계약을 따른다. 사용자가 직접 입력칸을 누르는 동작은 유지한다 |
82
82
  | `placement` | `bottom` · `start` · `end` | `bottom` | 나오는 가장자리 |
83
83
  | `size` | `auto` · `medium`(60%) · `large`(85%) · `full`(100%) | `auto` | `auto`는 내용 높이. 비율은 화면 높이 기준(Web `dvh`), 두 플랫폼 모두 위 safe area(Native는 키보드도)를 뺀 남은 높이를 넘지 않는다. `maxHeightRatio` 0.9 상한은 `auto`에만 걸린다 |
84
84
  | `open` · `defaultOpen` | `boolean` | 비제어 `false` | Web은 제어하면 `onOpenChange` 필수, 비제어면 `trigger` 필수 |
@@ -1,12 +1,12 @@
1
1
  # TypingIndicator
2
2
 
3
3
  - 단계: 컴포넌트
4
- - 상태: 실험
4
+ - 상태: 배포
5
5
  - 지원: Web · Native
6
- - 적용: 미게시(1.30.0 이후)
6
+ - 적용: 1.31.0
7
7
  - 검토일: 2026-10-18
8
8
  - 근거: [반복 화면 조합](../../screen-patterns.md), `src/screen-patterns.ts`의 `typingIndicatorRecipe`·`chatMessageRecipe`; Web·Native `src/screens.tsx`
9
- - 스토리북: `실험/컴포넌트/상태와 알림/입력 중 표시`
9
+ - 스토리북: `배포/컴포넌트/상태와 알림/입력 중 표시`
10
10
 
11
11
  ## 언제 쓰나
12
12
 
@@ -1,12 +1,12 @@
1
1
  # 대화 목록
2
2
 
3
3
  - 단계: 화면
4
- - 상태: 실험
4
+ - 상태: 배포
5
5
  - 지원: Web · Native
6
6
  - 적용: 구성 API 지침 참조
7
7
  - 검토일: 2026-10-17
8
8
  - 근거: 2026-10-16 사용자가 제공한 인스타그램 iOS DM 목록·길게 누르기 캡처. 행은 [ConversationRow](../components/conversation-row.md), 행 메뉴는 [ContextMenu](../components/context-menu.md)(Native canonical은 같은 날 추가, 미게시)
9
- - 스토리북: `실험/화면/소통/대화 목록`
9
+ - 스토리북: `배포/화면/소통/대화 목록`
10
10
 
11
11
  공통 구조·배치·상태·배지의 소유권은 [화면과 구성 선택](../SELECTION.md#소유권)을 따른다. 앱은 데이터·문구·브랜드·추가 기능을 공개 슬롯과 콜백으로 연결한다. 아래 코드 골격을 앱별 기본 구조 복제의 근거로 사용하지 않는다.
12
12
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hjmds/design-contracts",
3
- "version": "1.32.0",
3
+ "version": "1.33.0",
4
4
  "description": "Renderer-neutral design contracts, tokens, recipes, and behaviors shared by HJM products.",
5
5
  "license": "MIT",
6
6
  "repository": {