<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>개발세발</title>
    <link>https://3onk.tistory.com/</link>
    <description>실무 FE 삽질 &amp;middot; Next.js &amp;middot; AI 코딩 노트</description>
    <language>ko</language>
    <pubDate>Thu, 8 Oct 2026 03:37:46 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>3onK</managingEditor>
    <image>
      <title>개발세발</title>
      <url>https://tistory1.daumcdn.net/tistory/5765666/attach/36541e31a9d14f1d976eb0273521e08d</url>
      <link>https://3onk.tistory.com</link>
    </image>
    <item>
      <title>Flutter `Scrollable.ensureVisible` &amp;mdash; 포커스&amp;middot;에러 필드를 키보드 뒤에 두지 않기</title>
      <link>https://3onk.tistory.com/entry/flutter-scrollable-ensure-visible-form-focus</link>
      <description>&lt;p&gt;&lt;strong&gt;한 줄 요약:&lt;/strong&gt; 긴 약관·배송·설정 폼에서 &lt;strong&gt;다음 필드 &lt;code&gt;requestFocus()&lt;/code&gt;만&lt;/strong&gt; 하면 소프트키보드에 가려 &lt;strong&gt;사용자가 어디를 치는지 모른다&lt;/strong&gt;. Flutter &lt;strong&gt;&lt;code&gt;Scrollable.ensureVisible(context, …)&lt;/code&gt;&lt;/strong&gt; 로 &lt;strong&gt;포커스/에러 위젯을 스크롤 뷰포트 안&lt;/strong&gt;으로 가져오는 것이 계약이다. FocusNode·TextInputAction(#57)·TextFormField validator sync(#98)·Form autovalidate(#35)·MediaQuery.viewInsetsOf(#102)·ScrollController(#44)·ListView itemExtent(#173)과 겹치지 않게, 이번엔 &lt;strong&gt;ensureVisible 스크롤 정렬&lt;/strong&gt;만 판다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;버전 가정:&lt;/strong&gt; Flutter 안정 채널의 &lt;code&gt;Scrollable.ensureVisible&lt;/code&gt; (&lt;code&gt;BuildContext&lt;/code&gt; · &lt;code&gt;duration&lt;/code&gt; · &lt;code&gt;curve&lt;/code&gt; · &lt;code&gt;alignment&lt;/code&gt; · &lt;code&gt;alignmentPolicy&lt;/code&gt; 등). &lt;code&gt;ScrollController.animateTo&lt;/code&gt;로 offset을 직접 계산하는 패턴·패키지 &lt;code&gt;scroll_to_index&lt;/code&gt; 심화는 범위 밖. 코드는 최소 골격.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;교차 링크(재탕 금지):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;FocusNode · autofocus · TextInputAction → draft 57&lt;/li&gt;
&lt;li&gt;TextFormField validator · focus sync → draft 98 (&lt;strong&gt;포커스 계약; 스크롤은 위임&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;Form · autovalidateMode · GlobalKey → draft 35&lt;/li&gt;
&lt;li&gt;MediaQuery.viewInsetsOf · sizeOf → draft 102&lt;/li&gt;
&lt;li&gt;ScrollController · NotificationListener → draft 44&lt;/li&gt;
&lt;li&gt;ListView itemExtent · prototypeItem → draft 173&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;1차 소스: Flutter API &lt;code&gt;Scrollable.ensureVisible&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;이 글에서 다루는 것&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;증상 → 언제 ensureVisible&lt;/li&gt;
&lt;li&gt;포커스 후 / 검증 실패 후 최소 패턴&lt;/li&gt;
&lt;li&gt;alignment · duration 감각&lt;/li&gt;
&lt;li&gt;AI 안티패턴 · FAQ · 체크리스트&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;1. 증상과 원인 — 답부터&lt;/h2&gt;
&lt;h3&gt;흔한 증상&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;“다음”을 눌렀더니 커서는 아래 필드로 갔는데 &lt;strong&gt;화면은 그대로&lt;/strong&gt; · 키보드는 필드를 덮음&lt;/li&gt;
&lt;li&gt;제출 실패 후 첫 에러에 &lt;code&gt;requestFocus()&lt;/code&gt;만 함 → &lt;strong&gt;에러 보더가 뷰 밖&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;AI가 &lt;code&gt;ScrollController.jumpTo(9999)&lt;/code&gt; 또는 &lt;code&gt;primaryFocus&lt;/code&gt;만 생성&lt;/li&gt;
&lt;li&gt;NestedScrollView·탭 안 폼에서 &lt;strong&gt;잘못된 Scrollable&lt;/strong&gt;을 스크롤&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;원인 한 줄&lt;/h3&gt;
&lt;p&gt;포커스는 &lt;strong&gt;포커스 트리&lt;/strong&gt; 문제이고, 보이는지는 &lt;strong&gt;스크롤 가능 조상&lt;/strong&gt; 문제다. &lt;strong&gt;&lt;code&gt;Scrollable.ensureVisible&lt;/code&gt;&lt;/strong&gt; 는 해당 &lt;code&gt;context&lt;/code&gt;가 속한 Scrollable들을 올라가며 &lt;strong&gt;그 위젯이 보이게&lt;/strong&gt; 맞춘다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;상황&lt;/th&gt;
&lt;th&gt;처방&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;필드 이동 후 가림&lt;/td&gt;
&lt;td&gt;focus 후 &lt;code&gt;ensureVisible(fieldContext)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;검증 실패 첫 에러&lt;/td&gt;
&lt;td&gt;&lt;code&gt;requestFocus&lt;/code&gt; + &lt;code&gt;ensureVisible&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;키보드 inset만 알고 싶음&lt;/td&gt;
&lt;td&gt;&lt;code&gt;viewInsetsOf&lt;/code&gt;(#102) — &lt;strong&gt;스크롤 명령과 별개&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;offset을 내가 계산&lt;/td&gt;
&lt;td&gt;최후 · ensureVisible으로 부족한 티켓만&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;2. 최소 패턴 — 포커스 다음&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-dart&quot;&gt;Future&amp;lt;void&amp;gt; focusAndReveal(FocusNode node, BuildContext fieldContext) async {
  node.requestFocus();
  await Future&amp;lt;void&amp;gt;.delayed(Duration.zero); // 포커스·키보드 레이아웃 한 프레임
  if (!fieldContext.mounted) return;
  await Scrollable.ensureVisible(
    fieldContext,
    duration: const Duration(milliseconds: 250),
    curve: Curves.easeOut,
    alignment: 0.1, // 상단에 살짝 여유 (0.0=맨 위, 1.0=맨 아래 쪽)
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;TextFormField에 연결:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-dart&quot;&gt;TextFormField(
  focusNode: phoneFocus,
  textInputAction: TextInputAction.next,
  onFieldSubmitted: (_) {
    focusAndReveal(addressFocus, addressKey.currentContext!);
  },
  // ...
)&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;GlobalKey&lt;/code&gt; 또는 필드 루트 &lt;code&gt;Builder&lt;/code&gt;의 context&lt;/strong&gt;를 넘긴다. &lt;code&gt;requestFocus&lt;/code&gt;만 하고 context가 다른 위젯이면 엉뚱한 곳이 스크롤된다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;mounted&lt;/code&gt; / &lt;code&gt;context.mounted&lt;/code&gt;&lt;/strong&gt; 체크는 async gap 글(#8)과 같은 습관.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;alignment: 0.1&lt;/code&gt; 정도는 키보드·앱바에 가리지 않게 &lt;strong&gt;위쪽 여유&lt;/strong&gt;를 주는 감각값 — 제품 UI에 맞춰 조정.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;3. 검증 실패 첫 에러&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-dart&quot;&gt;Future&amp;lt;void&amp;gt; revealFirstError(GlobalKey&amp;lt;FormState&amp;gt; formKey, List&amp;lt;FocusNode&amp;gt; order) async {
  if (formKey.currentState?.validate() ?? false) return;
  for (final node in order) {
    final ctx = node.context;
    if (ctx == null) continue;
    // 에러 있는 필드만 — validator/autovalidate와 팀 계약에 맞게 조건 보강
    node.requestFocus();
    await Scrollable.ensureVisible(
      ctx,
      duration: const Duration(milliseconds: 250),
      curve: Curves.easeOut,
      alignment: 0.15,
    );
    break;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;#35·#98이 &lt;strong&gt;검증·포커스 순서&lt;/strong&gt;를 담당한다. 이 글은 그 다음 한 줄 — &lt;strong&gt;보이게 스크롤&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;4. alignment · alignmentPolicy 감각&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;인자&lt;/th&gt;
&lt;th&gt;실무 감각&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;alignment&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.0에 가까울수록 타깃이 &lt;strong&gt;뷰포트 상단&lt;/strong&gt; 쪽. 키보드에 가리면 0.0~0.2에서 시작&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;duration&lt;/code&gt; / &lt;code&gt;curve&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0ms jump는 어지러움 · 200–300ms easeOut이 무난&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;alignmentPolicy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;이미 충분히 보이면 스크롤 생략 등 정책 — 과도한 점프 티켓에 검토&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Nested·커스텀 스크롤에서는 &lt;strong&gt;필드 context가 진짜로 그 스크롤 트리 안인지&lt;/strong&gt;를 먼저 확인한다(#54·#113 감각). ensureVisible은 마법이 아니라 &lt;strong&gt;조상 Scrollable 탐색&lt;/strong&gt;이다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;5. AI 안티패턴&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;안티패턴&lt;/th&gt;
&lt;th&gt;대신&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;jumpTo(double.infinity)&lt;/code&gt; / 매직 offset&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ensureVisible(fieldContext)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;focus만 하고 QA에 “가림” 재오픈&lt;/td&gt;
&lt;td&gt;focus + ensureVisible 한 함수로 묶기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;매 keypress마다 ensureVisible&lt;/td&gt;
&lt;td&gt;submitted·next·validate 실패 시에만&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;viewInsets 패딩 없이 ensureVisible만 무한 조정&lt;/td&gt;
&lt;td&gt;폼 패딩/&lt;code&gt;viewInsetsOf&lt;/code&gt;(#102)와 역할 분리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#98 포커스 글에 스크롤 장문 합치기&lt;/td&gt;
&lt;td&gt;티켓 분리 — 포커스 vs 가시성&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;FAQ&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Q. &lt;code&gt;ScrollController.animateTo&lt;/code&gt;가 더 정확한 거 아닌가?&lt;/strong&gt;&lt;br&gt;A. offset을 &lt;strong&gt;당신이 아는&lt;/strong&gt; 목록(고정 extent 등)이면 가능. 폼·가변 높이·중첩에서는 ensureVisible이 기본값.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q. ListView.builder 재활용에 context가 죽지 않나?&lt;/strong&gt;&lt;br&gt;A. 화면에 없는 항목은 context가 없을 수 있다. &lt;strong&gt;에러/포커스 대상이 빌드돼 있는지&lt;/strong&gt;가 전제. 필요 시 해당 인덱스로 먼저 스크롤(#173 extent 힌트와 별 티켓).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q. MediaQuery.viewInsetsOf(#102)랑 동시에?&lt;/strong&gt;&lt;br&gt;A. inset은 &lt;strong&gt;하단 패딩·리사이즈&lt;/strong&gt;, ensureVisible은 &lt;strong&gt;스크롤 위치&lt;/strong&gt;. 둘 다 필요할 수 있고 한 API로 합치지 말 것.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q. TextFormField 글(#98)에 이미 있지 않나?&lt;/strong&gt;&lt;br&gt;A. #98은 포커스·validator sync. ensureVisible은 &lt;strong&gt;범위 밖으로 위임&lt;/strong&gt;해 둔 항목을 이번에만 판다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;체크리스트&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; next/에러 경로에 &lt;code&gt;requestFocus&lt;/code&gt; + &lt;code&gt;Scrollable.ensureVisible&lt;/code&gt; 묶음&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 필드 전용 &lt;code&gt;BuildContext&lt;/code&gt;/&lt;code&gt;GlobalKey&lt;/code&gt;를 넘기는가&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; async 후 &lt;code&gt;mounted&lt;/code&gt; 가드&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; alignment·duration을 키보드+앱바 QA로 확인&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; NestedScroll 폼이면 조상 Scrollable 소유 확인&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; #57·#98·#102와 역할이 문서/PR에 분리돼 있는가&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;자체 검수&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;기술:&lt;/strong&gt; ensureVisible 인자·포커스 후 스크롤만. offset 매직·패키지 강제 없음. #98·#57·#102와 분리.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;용어/맞춤법:&lt;/strong&gt; Scrollable.ensureVisible / FocusNode / viewInsets 통일. 본문 H1 없음 · &lt;strong&gt;한 줄 요약:&lt;/strong&gt;부터 시작.&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>Flutter, 모바일</category>
      <category>ensureVisible</category>
      <category>flutter</category>
      <category>FocusNode</category>
      <category>form</category>
      <category>scrollable</category>
      <category>viewInsets</category>
      <category>모바일</category>
      <category>키보드</category>
      <author>3onK</author>
      <guid isPermaLink="true">https://3onk.tistory.com/185</guid>
      <comments>https://3onk.tistory.com/entry/flutter-scrollable-ensure-visible-form-focus#entry185comment</comments>
      <pubDate>Tue, 29 Sep 2026 11:56:54 +0900</pubDate>
    </item>
    <item>
      <title>Ember-1(Fireworks) on AI Gateway(2026-09-27) &amp;mdash; 짧은 reasoning 트레이스 FE 데스크 노트</title>
      <link>https://3onk.tistory.com/entry/ember-1-fireworks-ai-gateway-sep2026</link>
      <description>&lt;p&gt;&lt;strong&gt;한 줄 요약:&lt;/strong&gt; 2026-09-29 기준, late-Sep FE·에이전트 데스크가 열어볼 &lt;strong&gt;공식 공지&lt;/strong&gt;는 Vercel Changelog &lt;strong&gt;&lt;a href=&quot;https://vercel.com/changelog/ember-1-from-fireworks-now-available-on-ai-gateway&quot;&gt;Ember-1 from Fireworks now available on AI Gateway&lt;/a&gt;&lt;/strong&gt;(Published &lt;strong&gt;September 27, 2026&lt;/strong&gt; · Zachary Chen, Jerilyn Zheng)다. 요지(원문 고정): (1) 모델 id &lt;strong&gt;&lt;code&gt;fireworks/ember-1&lt;/code&gt;&lt;/strong&gt; (2) &lt;strong&gt;Kimi K3 기반 research preview&lt;/strong&gt; reasoning 모델 · 코딩·에이전트 워크플로 (3) Fireworks 평가에서 Kimi K3 대비 &lt;strong&gt;약 40% 적은 generated tokens&lt;/strong&gt;(comparable quality) — 반복 호출 시 &lt;strong&gt;출력 비용·후속 컨텍스트&lt;/strong&gt; 감소 서술 (4) &lt;strong&gt;1M-token&lt;/strong&gt; 컨텍스트 · text/image 입력 · tool calling · &lt;strong&gt;implicit prompt caching&lt;/strong&gt; (5) Fireworks 엔드포인트 &lt;strong&gt;Zero Data Retention · No Prompt Training&lt;/strong&gt; (6) &lt;strong&gt;research preview · 초기 2주 윈도우&lt;/strong&gt; (7) AI SDK &lt;code&gt;streamText&lt;/code&gt; · Claude Code/Codex/fx · Chat Completions + &lt;code&gt;npx vercel ai-gateway setup&lt;/code&gt; / &lt;code&gt;/model&lt;/code&gt;. Sonnet 5.5(#167)·Pixel Canary(#158)·Opus 5.5(#130)·FlashX(#111)와 &lt;strong&gt;제품 면이 다름&lt;/strong&gt; — 이번엔 &lt;strong&gt;Ember-1 Gateway 롤아웃 FE 함의&lt;/strong&gt;만. 원문에 없는 단가표·벤치 재현·“Kimi를 대체한다” 단정은 적지 않는다.&lt;/p&gt;
&lt;p&gt;교차 링크(재탕 금지):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Claude Sonnet 5.5 Gateway → draft 167&lt;/li&gt;
&lt;li&gt;Pixel Canary stealth → draft 158&lt;/li&gt;
&lt;li&gt;Claude Opus 5.5 Gateway → draft 130&lt;/li&gt;
&lt;li&gt;GPT-6 Sol/Luna → draft 134&lt;/li&gt;
&lt;li&gt;GLM 5.3 FlashX → draft 111&lt;/li&gt;
&lt;li&gt;Managed Agents auto → draft 122&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;1차 확인(2026-09-29):&lt;/strong&gt; &lt;a href=&quot;https://vercel.com/changelog/ember-1-from-fireworks-now-available-on-ai-gateway&quot;&gt;Vercel Changelog 2026-09-27&lt;/a&gt;만. 배포 전 URL·preview 윈도우 잔여 일수 재확인.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;hr&gt;
&lt;h2&gt;이 글에서 다루는 것&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;공지가 말한 것 / 안 말한 것&lt;/li&gt;
&lt;li&gt;왜 FE 데스크 뉴스인가 (짧은 트레이스·반복 루프)&lt;/li&gt;
&lt;li&gt;지금 / 스테이징 / 안 함&lt;/li&gt;
&lt;li&gt;FAQ · 체크리스트&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;1. 답 먼저 — 왜 FE 뉴스인가&lt;/h2&gt;
&lt;p&gt;에이전트 루프는 &lt;strong&gt;같은 모델 호출을 N번&lt;/strong&gt; 한다. 원문이 강조하는 포인트는 “더 똑똑한 새 플래그십”이 아니라, Fireworks 평가 기준 &lt;strong&gt;생성 토큰이 약 40% 적다&lt;/strong&gt;는 것과 그것이 &lt;strong&gt;출력 비용·이후 스텝에 들고 가는 컨텍스트&lt;/strong&gt;에 미치는 영향이다. FE 데스크에서는 &lt;strong&gt;툴 루프·리뷰 봇·짧은 패치 에이전트&lt;/strong&gt; 슬롯 후보로 읽으면 된다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;역할&lt;/th&gt;
&lt;th&gt;Changelog 요지&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;모델 ID&lt;/td&gt;
&lt;td&gt;&lt;code&gt;fireworks/ember-1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;성격(원문)&lt;/td&gt;
&lt;td&gt;research preview · Kimi K3 기반 reasoning · coding/agentic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;효율(원문)&lt;/td&gt;
&lt;td&gt;~40% fewer generated tokens vs Kimi K3 (Fireworks evals, comparable quality)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;창·입력&lt;/td&gt;
&lt;td&gt;1M context · text + image · tool calling · implicit prompt caching&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;컴플라이언스(원문)&lt;/td&gt;
&lt;td&gt;Fireworks endpoint: ZDR · No Prompt Training&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;기간&lt;/td&gt;
&lt;td&gt;research preview · &lt;strong&gt;initial two-week window&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;연결면&lt;/td&gt;
&lt;td&gt;AI SDK · Chat Completions · Claude Code / Codex / fx + &lt;code&gt;vercel ai-gateway setup&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;슬랙 한 줄:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;Ember-1 Gateway(&lt;code&gt;fireworks/ember-1&lt;/code&gt;) 올림. 짧은 reasoning 트레이스 후보. research preview 2주. Sonnet/Opus/Pixel 슬롯과 섞지 말 것.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;hr&gt;
&lt;h2&gt;2. Changelog가 말한 것 / 안 말한 것&lt;/h2&gt;
&lt;h3&gt;말한 것&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;AI Gateway에 Ember-1 가용, id &lt;code&gt;fireworks/ember-1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Kimi K3 기반 research preview reasoning · coding/agentic&lt;/li&gt;
&lt;li&gt;Fireworks: ~40% fewer generated tokens at comparable quality (their evaluations)&lt;/li&gt;
&lt;li&gt;반복 모델 호출에서 짧은 reasoning trace → 출력 비용·후속 컨텍스트 감소 &lt;strong&gt;서술&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;1M context · text/image · tool calling · implicit prompt caching&lt;/li&gt;
&lt;li&gt;Fireworks endpoint ZDR · No Prompt Training&lt;/li&gt;
&lt;li&gt;research preview · initial two-week window&lt;/li&gt;
&lt;li&gt;&lt;code&gt;streamText({ model: &amp;#39;fireworks/ember-1&amp;#39;, ... })&lt;/code&gt; 예시&lt;/li&gt;
&lt;li&gt;Claude Code / Codex / fx: setup 후 &lt;code&gt;/model&lt;/code&gt;(Codex는 &lt;code&gt;codex --model fireworks/ember-1&lt;/code&gt;도)&lt;/li&gt;
&lt;li&gt;Chat Completions 예시(Gateway base URL)&lt;/li&gt;
&lt;li&gt;Gateway 단일 API · usage/cost · budgets · routing&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;안 말한 것 (추측 금지)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;토큰 단가표·우리 계정 쿼터 배수&lt;/li&gt;
&lt;li&gt;“Sonnet/Opus/Pixel을 대체한다” / Cursor 기본값 변경&lt;/li&gt;
&lt;li&gt;40% 수치의 독립 재현·벤치 리더보드 순위&lt;/li&gt;
&lt;li&gt;preview 종료 후 GA 일정·가격 확정&lt;/li&gt;
&lt;li&gt;Gateway 전체 ZDR이 Fireworks 엔드포인트와 동일 설정 UI라는 단정(원문은 &lt;strong&gt;Fireworks endpoint&lt;/strong&gt; 기준으로 ZDR/No Prompt Training)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;3. FE 데스크 관점 — 슬롯을 나누는 법&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;슬롯&lt;/th&gt;
&lt;th&gt;후보(기존 글)&lt;/th&gt;
&lt;th&gt;이번 공지&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;일상 PR·문서&lt;/td&gt;
&lt;td&gt;Sonnet 5.5 (#167)&lt;/td&gt;
&lt;td&gt;비대상(비교 단정 금지)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;긴 에이전트·고난도&lt;/td&gt;
&lt;td&gt;Opus 5.5 (#130)&lt;/td&gt;
&lt;td&gt;비대상&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;스텔스/실험 FE 디자인&lt;/td&gt;
&lt;td&gt;Pixel Canary (#158)&lt;/td&gt;
&lt;td&gt;비대상&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;고속 서빙&lt;/td&gt;
&lt;td&gt;FlashX (#111)&lt;/td&gt;
&lt;td&gt;비대상&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;짧은 reasoning · 반복 툴 루프 실험&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;(팀 표)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Ember-1&lt;/strong&gt; 후보&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;실무 규칙 초안:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;스테이징 에이전트 1대&lt;/strong&gt;에만 &lt;code&gt;fireworks/ember-1&lt;/code&gt;을 올려 flake·과잉/과소 추론을 1주 관찰(또는 preview 잔여 기간).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;preview 2주 윈도우&lt;/strong&gt;를 캘린더에 적고, 종료 후 슬롯을 되돌릴 rollback id를 표에 고정.&lt;/li&gt;
&lt;li&gt;ZDR/No Prompt Training이 &lt;strong&gt;필요한 레포&lt;/strong&gt;는 원문대로 Fireworks endpoint 옵션·팀 Gateway 콘솔을 진실로 확인 후 적용.&lt;/li&gt;
&lt;li&gt;Sonnet(#167)·Opus(#130)·Pixel(#158) 경로의 프롬프트/thinking 가드를 &lt;strong&gt;그대로 복사·삭제하지 말 것&lt;/strong&gt; — 모델 계약이 다름.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;npx vercel ai-gateway setup&lt;/code&gt; 후 &lt;code&gt;/model&lt;/code&gt;에 id가 보이는지 확인.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;4. 원문 코드 골격 (그대로 읽기)&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;import { streamText } from &amp;#39;ai&amp;#39;;

const result = streamText({
  model: &amp;#39;fireworks/ember-1&amp;#39;,
  prompt: &amp;#39;Write a retry helper with exponential backoff.&amp;#39;,
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;에이전트:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx vercel ai-gateway setup
# Claude Code / Codex / fx 에서 /model → Ember-1
# Codex: codex --model fireworks/ember-1&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;5. 지금 / 스테이징 / 안 함&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;상태&lt;/th&gt;
&lt;th&gt;조건&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;지금&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Gateway 카탈로그에 id 노출 확인 · 내부 툴 루프 PoC 1경로&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;스테이징&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;반복 호출 많은 리뷰/패치 봇 1대 · 토큰·품질·preview 만료일 관찰&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;안 함&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;프로덕션 기본 모델을 preview 기간에 전면 교체 · Sonnet/Opus 설정과 뭉뚱그려 교체 · 원문 없는 단가로 ROI 확정&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;FAQ&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Q. “40% fewer tokens”를 우리 대시보드 숫자로 써도 되나?&lt;/strong&gt;&lt;br&gt;A. 원문은 &lt;strong&gt;Fireworks evaluations&lt;/strong&gt; 기준. 내부 측정 전에는 슬랙/위키에 “벤더 주장”으로만 인용.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q. Sonnet 5.5 글(#167)이랑 같이 읽나?&lt;/strong&gt;&lt;br&gt;A. 일상 슬롯이면 #167. 짧은 reasoning·반복 루프 실험이면 이 글. 한 PR에 모델을 섞어 쓰지 말 것.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q. preview 2주가 지나면?&lt;/strong&gt;&lt;br&gt;A. 원문에 GA 일정 없음. 만료 전 rollback id를 표에 적어 둔다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q. ZDR이 Gateway 기본인가?&lt;/strong&gt;&lt;br&gt;A. 원문은 Fireworks endpoint의 ZDR · No Prompt Training. &lt;strong&gt;설정 방식·기본 on 여부는 팀 콘솔·문서가 진실.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;체크리스트&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; Changelog URL·날짜(2026-09-27)·모델 id &lt;code&gt;fireworks/ember-1&lt;/code&gt; 재확인&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; research preview · two-week window 만료일을 팀 캘린더에 기록&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 반복 툴 루프 슬롯 vs Sonnet/Opus/Pixel 분리표 업데이트&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; ZDR/No Prompt Training 필요 레포 · Fireworks endpoint 옵션 확인&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; &lt;code&gt;vercel ai-gateway setup&lt;/code&gt; + &lt;code&gt;/model&lt;/code&gt; 피커 확인&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 스테이징 1대 관찰 후 전면 여부 결정(preview 만료 전)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;자체 검수&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;사실/날짜/출처:&lt;/strong&gt; Vercel Changelog 2026-09-27만 인용. 단가·독립 벤치·타 모델 대체 단정 없음. 40%는 Fireworks evals 인용으로만.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;용어/맞춤법:&lt;/strong&gt; Ember-1 / &lt;code&gt;fireworks/ember-1&lt;/code&gt; / Fireworks / Kimi K3 / Gateway 표기 통일. #167·#158·#130·#111과 각도 분리. 본문 H1 없음.&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>AI 뉴스&amp;middot;최신소식</category>
      <category>2026-09-27</category>
      <category>AI Gateway</category>
      <category>AI 뉴스</category>
      <category>coding agents</category>
      <category>Ember-1</category>
      <category>fireworks</category>
      <category>Kimi K3</category>
      <category>Reasoning</category>
      <category>vercel</category>
      <author>3onK</author>
      <guid isPermaLink="true">https://3onk.tistory.com/184</guid>
      <comments>https://3onk.tistory.com/entry/ember-1-fireworks-ai-gateway-sep2026#entry184comment</comments>
      <pubDate>Tue, 29 Sep 2026 11:51:41 +0900</pubDate>
    </item>
    <item>
      <title>CSS `light-dark()` &amp;mdash; 라이트/다크 색을 미디어쿼리 복붙으로 끝내지 않기</title>
      <link>https://3onk.tistory.com/entry/css-light-dark-theme-tokens-fe</link>
      <description>&lt;p&gt;&lt;strong&gt;한 줄 요약:&lt;/strong&gt; 라이트/다크마다 &lt;code&gt;:root&lt;/code&gt; 변수를 &lt;code&gt;@media (prefers-color-scheme: dark)&lt;/code&gt;에 &lt;strong&gt;통째로 복붙&lt;/strong&gt;하면 토큰 표가 두 벌이 되고 AI가 한쪽만 고친다. CSS &lt;strong&gt;&lt;code&gt;light-dark(light, dark)&lt;/code&gt;&lt;/strong&gt; 는 &lt;strong&gt;한 속성에서 두 값을&lt;/strong&gt; 넘기고, 문서/&lt;code&gt;color-scheme&lt;/code&gt;이 고른 쪽에 맞춰 고른다. &lt;code&gt;color-mix&lt;/code&gt;·relative color(#163)·&lt;code&gt;:has&lt;/code&gt;(#148)·Container Queries(#143)·&lt;code&gt;prefers-reduced-motion&lt;/code&gt;(#108)·&lt;code&gt;field-sizing&lt;/code&gt;(#169)과 겹치지 않게, 이번엔 &lt;strong&gt;라이트/다크 한 줄 선택&lt;/strong&gt;만 판다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;버전 가정:&lt;/strong&gt; CSS Color Module의 &lt;code&gt;light-dark()&lt;/code&gt;. 사용 전제: 루트(또는 해당 요소)에 &lt;strong&gt;&lt;code&gt;color-scheme: light dark&lt;/code&gt;&lt;/strong&gt;(또는 강제 &lt;code&gt;light&lt;/code&gt;/&lt;code&gt;dark&lt;/code&gt;)가 잡혀 있어야 엔진이 어느 쪽을 고를지 안다. 지원 표·폴백은 배포 주 기준으로 팀 caniuse/MDN 재확인. 디자인 시스템 전체 마이그레이션 가이드 아님.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;교차 링크(재탕 금지):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;color-mix · relative color 파생 → draft 163 (&lt;strong&gt;파생 ≠ 라이트/다크 선택&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;prefers-reduced-motion → draft 108&lt;/li&gt;
&lt;li&gt;:has → draft 148&lt;/li&gt;
&lt;li&gt;Container Queries → draft 143&lt;/li&gt;
&lt;li&gt;field-sizing → draft 169&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;1차 감각 소스: MDN &lt;code&gt;light-dark()&lt;/code&gt; · CSS Color. 지원율 숫자는 글에 박제하지 않는다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;이 글에서 다루는 것&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;답 먼저 — 미디어쿼리 이중 토큰 vs &lt;code&gt;light-dark&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;최소 CSS · &lt;code&gt;color-scheme&lt;/code&gt; 계약&lt;/li&gt;
&lt;li&gt;토큰 표에 넣는 법 · 폴백&lt;/li&gt;
&lt;li&gt;AI 안티패턴 · FAQ · 체크리스트&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;1. 증상과 원인 — 답부터&lt;/h2&gt;
&lt;h3&gt;흔한 증상&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--bg&lt;/code&gt; / &lt;code&gt;--fg&lt;/code&gt;를 라이트 블록·다크 블록에 &lt;strong&gt;같은 이름 두 번&lt;/strong&gt; 선언 → PR마다 한쪽만 수정&lt;/li&gt;
&lt;li&gt;컴포넌트 SCSS에 &lt;code&gt;@media (prefers-color-scheme: dark)&lt;/code&gt;가 &lt;strong&gt;산발 복붙&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;사용자 토글(클래스 &lt;code&gt;.dark&lt;/code&gt;)과 OS prefers가 &lt;strong&gt;서로 다른 진실&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;AI가 다크 hex만 바꾸고 라이트 대비를 깨먹음&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;원인 한 줄&lt;/h3&gt;
&lt;p&gt;라이트/다크를 &lt;strong&gt;선택&lt;/strong&gt;하는 문제와 &lt;strong&gt;파생&lt;/strong&gt;(호버·disabled)하는 문제를 한 미디어쿼리 덩어리에 섞으면 표가 폭발한다. &lt;strong&gt;&lt;code&gt;light-dark()&lt;/code&gt;&lt;/strong&gt; 는 선택만 담당한다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;도구&lt;/th&gt;
&lt;th&gt;역할&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;light-dark(a, b)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;라이트값 / 다크값 &lt;strong&gt;한 줄 선택&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;color-mix&lt;/code&gt; / &lt;code&gt;oklch(from …)&lt;/code&gt;(#163)&lt;/td&gt;
&lt;td&gt;베이스에서 &lt;strong&gt;파생&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@media (prefers-color-scheme)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;레거시·미지원 폴백 · 토글과 충돌 시 신중&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.dark&lt;/code&gt; 클래스 토글&lt;/td&gt;
&lt;td&gt;제품이 OS와 무관하게 강제할 때 — &lt;code&gt;color-scheme&lt;/code&gt;과 같이 설계&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;2. 최소 CSS&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;:root {
  color-scheme: light dark;
  --bg: light-dark(#f7f7f8, #121214);
  --fg: light-dark(#111113, #ececf1);
  --border: light-dark(#e4e4e7, #2a2a2e);
}

body {
  background: var(--bg);
  color: var(--fg);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;핵심:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;color-scheme: light dark&lt;/code&gt;&lt;/strong&gt; — 없으면 &lt;code&gt;light-dark()&lt;/code&gt;가 기대대로 안 고른다(엔진·문서 계약).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;토큰 값은 &lt;code&gt;light-dark&lt;/code&gt;로&lt;/strong&gt;, 소비는 &lt;code&gt;var(--bg)&lt;/code&gt; 한 경로.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;강제 다크 페이지&lt;/strong&gt;(마케팅 히어로만 다크 등)는 그 서브트리에 &lt;code&gt;color-scheme: dark&lt;/code&gt;를 좁게.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;사용자 토글 예시(패턴만):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;:root[data-theme=&amp;quot;light&amp;quot;] { color-scheme: light; }
:root[data-theme=&amp;quot;dark&amp;quot;]  { color-scheme: dark; }
:root[data-theme=&amp;quot;system&amp;quot;] { color-scheme: light dark; }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;토글은 &lt;strong&gt;&lt;code&gt;color-scheme&lt;/code&gt;만 바꾸고&lt;/strong&gt;, &lt;code&gt;--bg&lt;/code&gt; 선언은 &lt;code&gt;light-dark&lt;/code&gt; 한 곳을 유지한다. 다크용 hex를 클래스마다 다시 쓰지 말 것.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;3. 파생(#163)과 같이 쓸 때&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;:root {
  color-scheme: light dark;
  --brand: light-dark(#4f46e5, #818cf8);
  --brand-hover: color-mix(in oklab, var(--brand) 85%, black);
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;선택&lt;/strong&gt; = &lt;code&gt;light-dark&lt;/code&gt;로 브랜드 베이스 두 값.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;파생&lt;/strong&gt; = &lt;code&gt;color-mix&lt;/code&gt; / relative color로 hover·muted.&lt;/li&gt;
&lt;li&gt;다크에서 hover를 &lt;code&gt;color-mix(..., white)&lt;/code&gt;로 올리다 &lt;strong&gt;네온&lt;/strong&gt;이 되면 #163 FAQ처럼 베이스를 먼저 재조정.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;4. 점진적 향상&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;:root {
  --bg: #f7f7f8;
  --fg: #111113;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #121214;
    --fg: #ececf1;
  }
}

@supports (color: light-dark(black, white)) {
  :root {
    color-scheme: light dark;
    --bg: light-dark(#f7f7f8, #121214);
    --fg: light-dark(#111113, #ececf1);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;@supports&lt;/code&gt; 안에서는 &lt;strong&gt;미디어쿼리 이중 선언을 제거&lt;/strong&gt;하는 방향이 이상적이다. 지원 브라우저에 두 체계를 남기면 또 한쪽만 고치는 사고가 난다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;5. AI 안티패턴&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;안티패턴&lt;/th&gt;
&lt;th&gt;대신&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;컴포넌트마다 dark 미디어쿼리 복붙&lt;/td&gt;
&lt;td&gt;루트 토큰 + &lt;code&gt;light-dark&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;light-dark&lt;/code&gt;만 넣고 &lt;code&gt;color-scheme&lt;/code&gt; 생략&lt;/td&gt;
&lt;td&gt;루트에 &lt;code&gt;light dark&lt;/code&gt; 명시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#163 파생 글과 섞어 “light-dark가 호버를 계산”&lt;/td&gt;
&lt;td&gt;선택은 light-dark, 파생은 color-mix&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;지원율 %를 README에 박제&lt;/td&gt;
&lt;td&gt;배포 주 재확인 칸&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;시스템 토글 없이 OS만 / OS 무시 클래스만&lt;/td&gt;
&lt;td&gt;&lt;code&gt;data-theme&lt;/code&gt; + &lt;code&gt;color-scheme&lt;/code&gt; 계약표&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;FAQ&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Q. &lt;code&gt;prefers-color-scheme&lt;/code&gt;만으로  충분하지 않나?&lt;/strong&gt;&lt;br&gt;A. 동작은 한다. 문제는 &lt;strong&gt;토큰 이중 관리&lt;/strong&gt;. &lt;code&gt;light-dark&lt;/code&gt;는 선언을 한 줄로 접는다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q. Tailwind &lt;code&gt;dark:&lt;/code&gt;와?&lt;/strong&gt;&lt;br&gt;A. 유틸 클래스 전략과 병행 가능. &lt;strong&gt;원시 토큰&lt;/strong&gt;은 CSS 변수+&lt;code&gt;light-dark&lt;/code&gt;로 두고 유틸은 그 변수를 소비하는 편이 AI 패치에 안전.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q. color-mix 글(#163)이랑 뭐가 다르나?&lt;/strong&gt;&lt;br&gt;A. #163은 &lt;strong&gt;한 테마 안에서 파생&lt;/strong&gt;. 이 글은 &lt;strong&gt;라이트 vs 다크 선택&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;체크리스트&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; &lt;code&gt;:root&lt;/code&gt;에 &lt;code&gt;color-scheme: light dark&lt;/code&gt;(또는 토글 계약)&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 핵심 토큰을 &lt;code&gt;light-dark(light, dark)&lt;/code&gt;로 접었는가&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 컴포넌트 산발 &lt;code&gt;@media dark&lt;/code&gt; 중복 제거&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 호버·disabled 파생은 #163 도구로 분리&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; &lt;code&gt;@supports&lt;/code&gt; 폴백 전략·팀 지원 표 날짜 기록&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;자체 검수&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;기술:&lt;/strong&gt; &lt;code&gt;light-dark&lt;/code&gt; + &lt;code&gt;color-scheme&lt;/code&gt; 계약만. 지원율 숫자 발명 없음. #163과 역할 분리 명시.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;용어/맞춤법:&lt;/strong&gt; light-dark / color-scheme / prefers-color-scheme 통일. 본문 H1 없음 · &lt;strong&gt;한 줄 요약:&lt;/strong&gt;부터 시작.&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>실무 프론트 팁</category>
      <category>color-scheme</category>
      <category>css</category>
      <category>light-dark</category>
      <category>prefers-color-scheme</category>
      <category>다크모드</category>
      <category>실무프론트팁</category>
      <category>테마토큰</category>
      <author>3onK</author>
      <guid isPermaLink="true">https://3onk.tistory.com/183</guid>
      <comments>https://3onk.tistory.com/entry/css-light-dark-theme-tokens-fe#entry183comment</comments>
      <pubDate>Tue, 29 Sep 2026 11:47:33 +0900</pubDate>
    </item>
    <item>
      <title>Flutter `ListView.builder` &amp;mdash; `itemExtent` &amp;middot; `prototypeItem` 으로 스크롤 점프 줄이기</title>
      <link>https://3onk.tistory.com/entry/flutter-listview-itemextent-prototypeitem</link>
      <description>&lt;p&gt;&lt;strong&gt;한 줄 요약:&lt;/strong&gt; 카드 높이가 &lt;strong&gt;거의 고정&lt;/strong&gt;인 피드인데 &lt;code&gt;ListView.builder&lt;/code&gt; 만 쓰면, 스크롤 중 &lt;strong&gt;미측정 자식&lt;/strong&gt; 때문에 점프·바운스가 난다. 높이를 아는 목록은 &lt;strong&gt;&lt;code&gt;itemExtent&lt;/code&gt;&lt;/strong&gt;(고정) 또는 &lt;strong&gt;&lt;code&gt;prototypeItem&lt;/code&gt;&lt;/strong&gt;(동일 레이아웃 샘플)으로 &lt;strong&gt;extent를 미리 알려&lt;/strong&gt; 레이아웃 추정 비용을 줄이는 것이 계약이다. AnimatedList(#152)·ReorderableListView(#124)·RepaintBoundary(#170)·AutomaticKeepAlive(#47)·nested scroll(#113)과 겹치지 않게, 이번엔 &lt;strong&gt;builder 목록의 extent 힌트&lt;/strong&gt;만 판다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;버전 가정:&lt;/strong&gt; Flutter 3.x · &lt;code&gt;ListView.builder&lt;/code&gt; / &lt;code&gt;ListView.separated&lt;/code&gt; · &lt;code&gt;itemExtent&lt;/code&gt; · &lt;code&gt;prototypeItem&lt;/code&gt; (둘 다 쓰면 안 되는 조합 — API 계약상 &lt;strong&gt;상호 배타&lt;/strong&gt;에 가깝게 쓴다). 완전 가변 높이(채팅 버블·접이식)는 &lt;code&gt;itemExtent&lt;/code&gt; 금지. Sliver 심화·CustomScrollView 튜토리얼 아님.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;교차 링크(재탕 금지):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AnimatedList insert/remove → draft 152&lt;/li&gt;
&lt;li&gt;ReorderableListView → draft 124&lt;/li&gt;
&lt;li&gt;RepaintBoundary 페인트 → draft 170 (&lt;strong&gt;페인트 ≠ layout extent&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;AutomaticKeepAlive / IndexedStack → draft 47&lt;/li&gt;
&lt;li&gt;nested ScrollNotification → draft 113&lt;/li&gt;
&lt;li&gt;PageView onboarding → draft 140&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;1차 소스: &lt;a href=&quot;https://api.flutter.dev/flutter/widgets/ListView-class.html&quot;&gt;ListView&lt;/a&gt; (&lt;code&gt;itemExtent&lt;/code&gt;, &lt;code&gt;prototypeItem&lt;/code&gt;)&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;이 글에서 다루는 것&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;증상 → extent를 언제 넘기는가&lt;/li&gt;
&lt;li&gt;&lt;code&gt;itemExtent&lt;/code&gt; vs &lt;code&gt;prototypeItem&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;separated · 가변 높이 경계&lt;/li&gt;
&lt;li&gt;AI 안티패턴 · FAQ · 체크리스트&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;1. 증상과 원인 — 답부터&lt;/h2&gt;
&lt;h3&gt;흔한 증상&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;빠른 플링 후 &lt;strong&gt;스크롤바가 떨리거나&lt;/strong&gt; 위치가 살짝 점프&lt;/li&gt;
&lt;li&gt;이미지·한 줄 텍스트 카드인데도 스크롤이 “무거운” 느낌&lt;/li&gt;
&lt;li&gt;하단으로 점프(&lt;code&gt;jumpTo&lt;/code&gt; / 초기 &lt;code&gt;initialScrollOffset&lt;/code&gt;) 시 &lt;strong&gt;오차&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;AI가 모든 리스트를 기본 &lt;code&gt;builder&lt;/code&gt; 로만 생성 · extent 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;원인 한 줄&lt;/h3&gt;
&lt;p&gt;빌더는 &lt;strong&gt;보이는 근처만&lt;/strong&gt; 만드므로, 엔진은 아직 안 만든 자식의 높이를 &lt;strong&gt;추정&lt;/strong&gt;한다. 추정이 빗나가면 스크롤 offset 보정이 점프로 보인다. &lt;strong&gt;extent를 알려 주면&lt;/strong&gt; 추측이 줄어든다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;목록 특성&lt;/th&gt;
&lt;th&gt;힌트&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;높이 완전 고정(예: 72)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;itemExtent: 72&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;위젯 구조는 동일, 숫자만 쓰기 싫음&lt;/td&gt;
&lt;td&gt;&lt;code&gt;prototypeItem: …&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;행마다 높이 천차만별&lt;/td&gt;
&lt;td&gt;extent 넣지 말 것 · 설계 재고&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;2. &lt;code&gt;itemExtent&lt;/code&gt; — 숫자가 있을 때&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-dart&quot;&gt;ListView.builder(
  itemCount: items.length,
  itemExtent: 72, // 고정 행 높이(논리 픽셀)
  itemBuilder: (context, index) {
    final item = items[index];
    return OrderRow(item: item); // 내부도 72에 맞게
  },
)&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;행 위젯 실제 높이&lt;/strong&gt;와 &lt;code&gt;itemExtent&lt;/code&gt; 가 어긋나면 overflow/클리핑이 난다. 패딩·타이틀 줄 수까지 포함해 맞출 것.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ListView.separated&lt;/code&gt; 는 separator 높이까지 고려해야 해서 &lt;strong&gt;단순 고정 행&lt;/strong&gt;보다 설계가 지저분해진다 — 고정 피드를 우선 &lt;code&gt;builder&lt;/code&gt; + extent로.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;3. &lt;code&gt;prototypeItem&lt;/code&gt; — 샘플 한 칸&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-dart&quot;&gt;ListView.builder(
  itemCount: items.length,
  prototypeItem: const OrderRow.skeleton(), // 실제와 같은 레이아웃 골격
  itemBuilder: (context, index) =&amp;gt; OrderRow(item: items[index]),
)&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;프로토타입은 &lt;strong&gt;레이아웃 측정용&lt;/strong&gt;. 화면에 그 인스턴스가 리스트 아이템으로 반복 삽입되는 그림이 아니다(문맥: extent 계산).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;const&lt;/code&gt; 가능한 골격이면 좋다. &lt;strong&gt;데이터 의존 높이&lt;/strong&gt;(여러 줄 말줄임 해제)면 prototype과 실행이 어긋난다 → 그때는 extent 계열을 포기하거나 행 디자인을 고정 높이로.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;itemExtent&lt;/code&gt; 와 &lt;code&gt;prototypeItem&lt;/code&gt; 을 &lt;strong&gt;동시에 넣지 않는다&lt;/strong&gt;. 팀 컨벤션: 숫자 확실하면 extent, 위젯으로 맞춰야 하면 prototype.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;4. 가변 높이·다른 최적화와 경계&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;하고 싶은 것&lt;/th&gt;
&lt;th&gt;이 글&lt;/th&gt;
&lt;th&gt;다른 글&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;고정/유사 높이 추정&lt;/td&gt;
&lt;td&gt;✅ extent / prototype&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;insert 애니메이션&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;#152 AnimatedList&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;드래그 재정렬&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;#124 Reorderable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;부모 setState 페인트 격리&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;#170 RepaintBoundary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;탭 상태 유지&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;#47 KeepAlive&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;이미지가 늦게 들어와 &lt;strong&gt;행 높이가 커지는&lt;/strong&gt; 피드는 &lt;code&gt;itemExtent&lt;/code&gt; 가 독이 될 수 있다. &lt;strong&gt;플레이스홀더 높이를 고정&lt;/strong&gt;하거나, 가변을 인정하고 extent를 뺀다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;5. AI 안티패턴&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;안티패턴&lt;/th&gt;
&lt;th&gt;대신&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;모든 builder에 &lt;code&gt;itemExtent: 50&lt;/code&gt; 매직&lt;/td&gt;
&lt;td&gt;디자인 토큰·실제 행 높이 측정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;채팅 버블에 itemExtent&lt;/td&gt;
&lt;td&gt;가변 높이 — 제거&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;extent + prototype 동시&lt;/td&gt;
&lt;td&gt;하나만&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;“성능 = 무조건 RepaintBoundary”&lt;/td&gt;
&lt;td&gt;layout extent와 paint 경계 분리(#170)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Separated에 extent 대충&lt;/td&gt;
&lt;td&gt;separator 높이 포함해 재계산 또는 builder로&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;FAQ&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Q. &lt;code&gt;itemExtentBuilder&lt;/code&gt;(가능 버전)는?&lt;/strong&gt;&lt;br&gt;A. 인덱스별 다른 고정 높이일 때. 팀 Flutter 버전·API 안정성을 확인하고, &lt;strong&gt;완전 가변&lt;/strong&gt; 대체재로 오해하지 말 것. 이 글은 공통인 &lt;code&gt;itemExtent&lt;/code&gt; / &lt;code&gt;prototypeItem&lt;/code&gt; 중심.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q. GridView도?&lt;/strong&gt;&lt;br&gt;A. &lt;code&gt;gridDelegate&lt;/code&gt; 가 extent 역할을 상당 부분 한다. 리스트 행 계약과 별개.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q. &lt;code&gt;cacheExtent&lt;/code&gt; 만 키우면?&lt;/strong&gt;&lt;br&gt;A. 미리 만들 범위일 뿐 &lt;strong&gt;점프의 근본(미지 높이)&lt;/strong&gt; 을 안 없앤다. 직교 옵션.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;체크리스트&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 행 높이가 고정/유사인지 제품과 합의&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 고정이면 &lt;code&gt;itemExtent&lt;/code&gt; 수치 = 실제 행(패딩 포함)&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 위젯 샘플이면 &lt;code&gt;prototypeItem&lt;/code&gt; 만 (extent와 배타)&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 가변 콘텐츠면 extent 제거 · 점프 QA&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 플링·jumpTo·초기 오프셋 수동 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;자체 검수&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;기술:&lt;/strong&gt; itemExtent/prototypeItem 배타 사용·가변 높이 금지 명시. AnimatedList/RepaintBoundary와 축 분리. itemExtentBuilder는 버전 확인 톤.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;용어/맞춤법:&lt;/strong&gt; ListView.builder / itemExtent / prototypeItem 통일. #152·#170 재탕 없음.&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>Flutter, 모바일</category>
      <category>builder</category>
      <category>flutter</category>
      <category>itemExtent</category>
      <category>listview</category>
      <category>prototypeItem</category>
      <category>scroll</category>
      <category>모바일</category>
      <category>성능</category>
      <author>3onK</author>
      <guid isPermaLink="true">https://3onk.tistory.com/182</guid>
      <comments>https://3onk.tistory.com/entry/flutter-listview-itemextent-prototypeitem#entry182comment</comments>
      <pubDate>Tue, 29 Sep 2026 11:42:52 +0900</pubDate>
    </item>
    <item>
      <title>Next.js `Link` `scroll={false}` &amp;mdash; 필터&amp;middot;탭 URL 교체 후 스크롤 점프 막기</title>
      <link>https://3onk.tistory.com/entry/nextjs-link-scroll-false-filter-tabs</link>
      <description>&lt;p&gt;&lt;strong&gt;한 줄 요약:&lt;/strong&gt; 목록 위에서 필터·정렬·탭만 바꿨는데 &lt;strong&gt;스크롤이 맨 위로 튀면&lt;/strong&gt; 사용자는 “페이지가 리로드된 것”으로 느낀다. App Router &lt;code&gt;&amp;lt;Link&amp;gt;&lt;/code&gt; 기본값은 내비게이션 후 &lt;strong&gt;스크롤을 상단으로&lt;/strong&gt; 옮긴다. &lt;strong&gt;같은 목록 UX&lt;/strong&gt;면 &lt;code&gt;scroll={false}&lt;/code&gt; 가 계약이다. &lt;code&gt;searchParams&lt;/code&gt; Promise(#34)·Router Cache/prefetch(#114)·&lt;code&gt;useLinkStatus&lt;/code&gt;(#86)·&lt;code&gt;router.refresh&lt;/code&gt;(#151)·&lt;code&gt;loading.tsx&lt;/code&gt;(#38)와 겹치지 않게, 이번엔 &lt;strong&gt;Link 스크롤 동작&lt;/strong&gt;만 판다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;버전 가정:&lt;/strong&gt; Next.js App Router · &lt;code&gt;next/link&lt;/code&gt; 의 &lt;code&gt;scroll&lt;/code&gt; boolean(기본 &lt;code&gt;true&lt;/code&gt;). &lt;code&gt;useRouter().push(href, { scroll: false })&lt;/code&gt; 동일 감각. hash 앵커·브라우저 기본 스크롤 복원 심화·Pages Router 전용 API는 범위 밖.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;교차 링크(재탕 금지):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;searchParams await → draft 34&lt;/li&gt;
&lt;li&gt;Router Cache · staleTimes · prefetch → draft 114 (&lt;strong&gt;캐시 ≠ 스크롤 계약&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;useLinkStatus pending → draft 86&lt;/li&gt;
&lt;li&gt;router.refresh / revalidate → draft 151&lt;/li&gt;
&lt;li&gt;loading.tsx → draft 38&lt;/li&gt;
&lt;li&gt;draftMode disable + prefetch 주의 → draft 58 (교차만)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;1차 소스: &lt;a href=&quot;https://nextjs.org/docs/app/api-reference/components/link&quot;&gt;Link&lt;/a&gt; (&lt;code&gt;scroll&lt;/code&gt; prop)&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;이 글에서 다루는 것&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;증상 → 언제 &lt;code&gt;scroll={false}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Link · router.push 최소 패턴&lt;/li&gt;
&lt;li&gt;prefetch와 같이 쓸 때&lt;/li&gt;
&lt;li&gt;AI 안티패턴 · FAQ · 체크리스트&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;1. 증상과 원인 — 답부터&lt;/h2&gt;
&lt;h3&gt;흔한 증상&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;?status=open&lt;/code&gt; 만 바꿨는데 긴 테이블 스크롤이 0으로&lt;/li&gt;
&lt;li&gt;탭 UI가 URL과 동기화될 때마다 &lt;strong&gt;헤더 아래로 튕김&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;router.push&lt;/code&gt;로 쿼리 갱신 후 동일&lt;/li&gt;
&lt;li&gt;AI가 필터 칩을 전부 기본 &lt;code&gt;&amp;lt;Link&amp;gt;&lt;/code&gt; 로 생성 → QA에서 “리로드 같다” 티켓&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;원인 한 줄&lt;/h3&gt;
&lt;p&gt;App Router는 라우트 전환 후 &lt;strong&gt;기본적으로 스크롤을 최상단으로&lt;/strong&gt; 맞춘다. &lt;strong&gt;문서(페이지)가 바뀌는 이동&lt;/strong&gt;에는 맞고, &lt;strong&gt;같은 목록의 쿼리·탭 교체&lt;/strong&gt;에는 거슬린다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;전환&lt;/th&gt;
&lt;th&gt;scroll&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;목록 → 상세 · 다른 섹션&lt;/td&gt;
&lt;td&gt;기본 &lt;code&gt;true&lt;/code&gt; (상단)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;같은 page에서 &lt;code&gt;?q&lt;/code&gt;·탭만 변경&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;false&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;hash로 섹션 점프&lt;/td&gt;
&lt;td&gt;제품에 맞게 별도(기본 상단 리셋과 혼동 금지)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;2. 최소 패턴&lt;/h2&gt;
&lt;h3&gt;Link&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import Link from &amp;#39;next/link&amp;#39;

export function StatusChip({ status, label }: { status: string; label: string }) {
  return (
    &amp;lt;Link
      href={`/orders?status=${status}`}
      scroll={false}
      // 목록이 자주 바뀌면 draft 114 감각으로 prefetch={false}도 검토
      className=&amp;quot;chip&amp;quot;
    &amp;gt;
      {label}
    &amp;lt;/Link&amp;gt;
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;router.push&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;&amp;#39;use client&amp;#39;

import { useRouter } from &amp;#39;next/navigation&amp;#39;

export function useOrderFilter() {
  const router = useRouter()
  return (status: string) =&amp;gt; {
    router.push(`/orders?status=${status}`, { scroll: false })
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;서버 컴포넌트 칩&lt;/strong&gt;은 &lt;code&gt;&amp;lt;Link scroll={false}&amp;gt;&lt;/code&gt; 로 충분.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;클라이언트 핸들러&lt;/strong&gt;(셀렉트·디바운스 검색)는 &lt;code&gt;push&lt;/code&gt;/&lt;code&gt;replace&lt;/code&gt; 옵션.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;replace&lt;/code&gt; 로 히스토리를 얇게 가져갈 때도 &lt;strong&gt;&lt;code&gt;scroll: false&lt;/code&gt;&lt;/strong&gt; 를 같이 준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;3. prefetch · soft navigation과 역할 분리&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;관심사&lt;/th&gt;
&lt;th&gt;담당&lt;/th&gt;
&lt;th&gt;이 글&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;스크롤 점프&lt;/td&gt;
&lt;td&gt;&lt;code&gt;scroll={false}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;오래된 RSC 페이로드&lt;/td&gt;
&lt;td&gt;staleTimes · refresh(#114·#151)&lt;/td&gt;
&lt;td&gt;교차&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;클릭 pending UI&lt;/td&gt;
&lt;td&gt;useLinkStatus(#86)&lt;/td&gt;
&lt;td&gt;교차&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;쿼리 타입·dynamic&lt;/td&gt;
&lt;td&gt;searchParams(#34)&lt;/td&gt;
&lt;td&gt;교차&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;필터 칩에 &lt;code&gt;scroll={false}&lt;/code&gt; 를 깜빡여도, &lt;strong&gt;Router Cache가 옛 목록을 보여 주는 문제&lt;/strong&gt;는 별개다. 스크롤만 잠그고 데이터는 #114·#151로 간다.&lt;/p&gt;
&lt;p&gt;관리 화면 긴 링크 리스트면 draft 114처럼 &lt;strong&gt;&lt;code&gt;prefetch={false}&lt;/code&gt;&lt;/strong&gt; 를 스크롤 옵션과 &lt;strong&gt;직교&lt;/strong&gt;로 검토한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;4. AI 안티패턴&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;안티패턴&lt;/th&gt;
&lt;th&gt;대신&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;모든 Link에 &lt;code&gt;scroll={false}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;목록 내 쿼리·탭만&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;window.scrollTo&lt;/code&gt; 로 복구&lt;/td&gt;
&lt;td&gt;원인인 Link/push 옵션부터&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;필터를 &lt;code&gt;window.location&lt;/code&gt; 하드 리로드&lt;/td&gt;
&lt;td&gt;soft nav + scroll false&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;scroll false인데 상세 진입 Link에도 동일&lt;/td&gt;
&lt;td&gt;상세는 기본 true&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;“CSS scroll-behavior만 있으면 됨”&lt;/td&gt;
&lt;td&gt;라우터 스크롤 리셋과 별개&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;FAQ&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Q. &lt;code&gt;replace&lt;/code&gt; 만 쓰면 스크롤이 안 튀나?&lt;/strong&gt;&lt;br&gt;A. 아니다. &lt;strong&gt;히스토리 치환&lt;/strong&gt;과 &lt;strong&gt;스크롤 리셋&lt;/strong&gt;은 다른 축. &lt;code&gt;replace&lt;/code&gt; + &lt;code&gt;scroll: false&lt;/code&gt; 조합이 흔하다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q. &lt;code&gt;loading.tsx&lt;/code&gt; 가 뜨면?&lt;/strong&gt;&lt;br&gt;A. 세그먼트가 갈리면 로딩 UI·스크롤 경험이 달라질 수 있다. &lt;strong&gt;같은 page·searchParams만&lt;/strong&gt; 바꾸는 구조인지도 #38과 함께 본다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q. hash &lt;code&gt;#section&lt;/code&gt; 은?&lt;/strong&gt;&lt;br&gt;A. 제품이 앵커 이동을 원하면 기본 상단 리셋과 싸운다. 이 글 범위는 &lt;strong&gt;필터·탭 쿼리&lt;/strong&gt;. hash는 팀 패턴으로 따로.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q. Pages Router &lt;code&gt;Router.push(url, as, { scroll })&lt;/code&gt;?&lt;/strong&gt;&lt;br&gt;A. App Router 옵션 객체 형태와 다르다. 이 글은 App &lt;code&gt;next/link&lt;/code&gt; · &lt;code&gt;next/navigation&lt;/code&gt; 기준.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;체크리스트&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 필터·정렬·탭 Link에 &lt;code&gt;scroll={false}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 클라이언트 &lt;code&gt;push&lt;/code&gt;/&lt;code&gt;replace&lt;/code&gt;에도 &lt;code&gt;{ scroll: false }&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 목록→상세 Link는 기본 스크롤(true) 유지&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 관리 목록 prefetch 정책은 #114와 별도 점검&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; QA: 스크롤 중간에서 칩 클릭 → 위치 유지&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;자체 검수&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;기술:&lt;/strong&gt; Link &lt;code&gt;scroll&lt;/code&gt; 기본 true · router.push 두 번째 인자 옵션. prefetch/refresh와 역할 분리. Pages API 혼동 없음.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;용어/맞춤법:&lt;/strong&gt; scroll={false} / soft navigation 통일. #34·#86·#114·#151 재탕 없음.&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>Next.js, React 삽질</category>
      <category>app router</category>
      <category>Link</category>
      <category>next.js</category>
      <category>prefetch</category>
      <category>scroll</category>
      <category>searchparams</category>
      <category>Soft Navigation</category>
      <category>프론트엔드</category>
      <author>3onK</author>
      <guid isPermaLink="true">https://3onk.tistory.com/181</guid>
      <comments>https://3onk.tistory.com/entry/nextjs-link-scroll-false-filter-tabs#entry181comment</comments>
      <pubDate>Tue, 29 Sep 2026 11:39:47 +0900</pubDate>
    </item>
    <item>
      <title>AI 코딩 &amp;mdash; 허용 파일 화이트리스트로 패치 범위를 잠그는 FE 루틴</title>
      <link>https://3onk.tistory.com/entry/ai-whitelist-file-scope-patch-fe</link>
      <description>&lt;p&gt;&lt;strong&gt;한 줄 요약:&lt;/strong&gt; “이 티켓 고쳐줘”만 던지면 에이전트가 &lt;strong&gt;인접 유틸·배럴·테스트 스냅샷&lt;/strong&gt;까지 손을 댄다. FE 데스크에서는 &lt;strong&gt;허용 경로 화이트리스트 + 금지 글롭 + 새 파일 정책&lt;/strong&gt;을 프롬프트 맨 위에 고정한다. 테스트 fix 루프(#31)·Plan 스코프(#81)·스펙 프롬프트(#150)·eval 글롭(#162)·모노레포 Rules(#166)와 겹치지 않게, 이번엔 &lt;strong&gt;매 패치 세션의 파일 자물쇠&lt;/strong&gt;만 판다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;버전 가정:&lt;/strong&gt; Cursor Agent/Composer · Claude Code 등 &lt;strong&gt;멀티파일 패치&lt;/strong&gt; 도구 일반. 제품 UI 단축키는 바뀔 수 있으니 &lt;strong&gt;프롬프트 계약&lt;/strong&gt;이 진실. git worktree·모노레포 apps/* 가정. CI 게이트 전체 설계 아님.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;교차 링크(재탕 금지):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;테스트 깨짐 fix 루프 → draft 31 (&lt;strong&gt;실패 1건 루프 ≠ 파일 자물쇠&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;Plan mode 스코프 → draft 81&lt;/li&gt;
&lt;li&gt;스펙 드리븐 프롬프트 → draft 150&lt;/li&gt;
&lt;li&gt;eval allowed/forbidden globs → draft 162 (&lt;strong&gt;프롬프트 회귀 측정 ≠ 티켓 패치&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;모노레포 Rules 레이어링 → draft 166&lt;/li&gt;
&lt;li&gt;Composer PR 루틴 → draft 7&lt;/li&gt;
&lt;li&gt;cursorignore 예산 → draft 101&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;이 글에서 다루는 것&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;증상 → 화이트리스트 계약&lt;/li&gt;
&lt;li&gt;붙여넣기 템플릿 · 새 파일 정책&lt;/li&gt;
&lt;li&gt;검증(git status) · AI 안티패턴 · FAQ · 체크리스트&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;1. 증상과 원인 — 답부터&lt;/h2&gt;
&lt;h3&gt;흔한 증상&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;버튼 카피 수정인데 &lt;code&gt;packages/ui&lt;/code&gt; 배럴·토큰 파일까지 diff&lt;/li&gt;
&lt;li&gt;“타입만 맞춰줘”에 &lt;strong&gt;포매터·import 정렬·무관 리네임&lt;/strong&gt; 동반&lt;/li&gt;
&lt;li&gt;모노레포에서 &lt;code&gt;apps/web&lt;/code&gt; 티켓인데 &lt;code&gt;apps/api&lt;/code&gt; 스키마 제안(#166 감각)&lt;/li&gt;
&lt;li&gt;리뷰어가 “왜 이 파일이?”를 반복&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;한 줄 원인&lt;/h3&gt;
&lt;p&gt;모델은 &lt;strong&gt;관련성 휴리스틱&lt;/strong&gt;으로 이웃 파일을 연다. 범위는 채팅 톤이 아니라 &lt;strong&gt;경로 계약&lt;/strong&gt;으로만 잠긴다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;계약&lt;/th&gt;
&lt;th&gt;역할&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Allow&lt;/td&gt;
&lt;td&gt;이번 세션에 &lt;strong&gt;편집 가능&lt;/strong&gt;한 경로(파일 또는 글롭)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deny&lt;/td&gt;
&lt;td&gt;열람만 / 편집 금지(스냅샷·생성 코드·타 앱)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;New files&lt;/td&gt;
&lt;td&gt;&lt;code&gt;forbidden&lt;/code&gt; / &lt;code&gt;ask-first&lt;/code&gt; / &lt;code&gt;only-under: path/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;슬랙 한 줄:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;허용 목록 밖 diff는 머지 대상이 아니다. 되돌려라.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;hr&gt;
&lt;h2&gt;2. 붙여넣기 템플릿&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;## 패치 자물쇠 (필수)
- Allow (edit):
  - apps/web/app/(dashboard)/orders/page.tsx
  - apps/web/components/orders/OrderFilter.tsx
- Deny (no edit):
  - **/*.snap
  - **/generated/**
  - apps/api/**
  - packages/ui/src/index.ts
- New files: forbidden
- 목표: 주문 필터 쿼리키 `status` 반영. 리팩터·포맷만의 변경 금지.
- 끝낼 때: `git status` / `git diff --stat`가 Allow 안인지 보고.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;짧게 쓸 때:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;EDIT ONLY: src/components/SearchBox.tsx, src/lib/queryKeys.ts
NO NEW FILES. NO SNAPSHOTS. 최소 diff.&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;파일 단위&lt;/strong&gt;가 글롭보다 안전하다. 급하면 &lt;code&gt;apps/web/components/orders/**&lt;/code&gt; 정도.&lt;/li&gt;
&lt;li&gt;Deny에 &lt;strong&gt;스냅샷·codegen·타 앱&lt;/strong&gt;을 기본으로 넣는다.&lt;/li&gt;
&lt;li&gt;#150 스펙이 있으면 자물쇠 블록을 &lt;strong&gt;스펙 다음·코드 요청 전&lt;/strong&gt;에 둔다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;3. 새 파일 정책 세 가지&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;정책&lt;/th&gt;
&lt;th&gt;언제&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;forbidden&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;카피·버그·한 컴포넌트 props — 기본값&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ask-first&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;훅 추출이 필요할 수 있지만 확정 전&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;only-under: …/__tests__/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;테스트 추가가 티켓에 명시된 경우&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;“나중에 정리하자”로 &lt;strong&gt;유틸 신설&lt;/strong&gt;을 허용하면 화이트리스트가 빈 말록이 된다. 추출이 필요하면 &lt;strong&gt;사람이 경로를 Allow에 추가&lt;/strong&gt;한 뒤 재요청.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;4. 검증 — 패치 후 30초&lt;/h2&gt;
&lt;p&gt;사람이 돌린다(에이전트 말만 믿지 않음):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git status -sb
git diff --stat
git diff --name-only&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Allow 밖 경로가 있으면 &lt;strong&gt;revert 또는 세션 롤백&lt;/strong&gt; 후 자물쇠를 강화해 재실행.&lt;/li&gt;
&lt;li&gt;“포맷터만”도 범위 위반이면 되돌린다 — #31의 “초록이 목표”와 같이 &lt;strong&gt;최소 diff&lt;/strong&gt;가 계약.&lt;/li&gt;
&lt;li&gt;모노레포면 &lt;code&gt;git diff --name-only&lt;/code&gt;에 &lt;code&gt;apps/api&lt;/code&gt;가 보이는 순간 중단(#166).&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;5. AI 안티패턴&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;안티패턴&lt;/th&gt;
&lt;th&gt;대신&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;“관련 파일 알아서”&lt;/td&gt;
&lt;td&gt;Allow 목록 명시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deny 없이 “스냅샷 맞추지 마”만 문장&lt;/td&gt;
&lt;td&gt;&lt;code&gt;**/*.snap&lt;/code&gt; Deny&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;자물쇠를 채팅 맨 아래&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;맨 위&lt;/strong&gt; 고정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plan(#81)만 길고 패치 세션에 자물쇠 없음&lt;/td&gt;
&lt;td&gt;Plan 승인 후 &lt;strong&gt;패치마다&lt;/strong&gt; 재부착&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;eval 글롭(#162)을 티켓 프롬프트에 복붙&lt;/td&gt;
&lt;td&gt;측정용 ≠ 세션용. 티켓은 파일 목록이 더 낫다&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;새 파일 5개 + “구조 개선”&lt;/td&gt;
&lt;td&gt;New files: forbidden&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;FAQ&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Q. #31이랑 뭐가 다르나?&lt;/strong&gt;&lt;br&gt;A. #31은 &lt;strong&gt;실패 테스트 1건 루프&lt;/strong&gt;. 이 글은 &lt;strong&gt;어떤 티켓이든 편집 경로 자물쇠&lt;/strong&gt;. 같이 쓴다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q. #81 Plan이면 충분하지 않나?&lt;/strong&gt;&lt;br&gt;A. Plan은 범위 합의. 실행 세션에서 모델이 다시 넓어진다. &lt;strong&gt;패치 프롬프트마다&lt;/strong&gt; Allow를 붙인다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q. Rules(#15·#166)에 “최소 diff”를 넣으면?&lt;/strong&gt;&lt;br&gt;A. 전역 톤에는 도움. 티켓별 경로는 &lt;strong&gt;세션 자물쇠&lt;/strong&gt;가 이긴다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q. 읽기(참조)는?&lt;/strong&gt;&lt;br&gt;A. 읽기는 넓혀도 된다. 문제는 &lt;strong&gt;쓰기&lt;/strong&gt;. “Read OK / Edit only Allow”로 구분해도 좋다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;체크리스트&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 프롬프트 상단에 Allow / Deny / New files가 있는가&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 스냅샷·codegen·타 앱이 Deny에 있는가&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 패치 후 &lt;code&gt;git diff --name-only&lt;/code&gt;가 Allow 안인가&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 범위 밖이면 revert 후 자물쇠 강화 재실행인가&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; #150 스펙과 자물쇠를 한 덩어리로 붙였는가&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;자체 검수&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;기술:&lt;/strong&gt; 화이트리스트는 프롬프트 계약이지 IDE 강제 샌드박스가 아님을 전제. git으로 사람 검증. #31·#81·#162와 역할 분리.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;용어/맞춤법:&lt;/strong&gt; Allow/Deny/화이트리스트 통일. 모노레포 예시는 apps/web·apps/api.&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>AI 코딩 워크플로</category>
      <category>agent</category>
      <category>AI 코딩 워크플로</category>
      <category>Composer</category>
      <category>Cursor</category>
      <category>FE</category>
      <category>GIT</category>
      <category>체크리스트</category>
      <category>패치 스코프</category>
      <category>화이트리스트</category>
      <author>3onK</author>
      <guid isPermaLink="true">https://3onk.tistory.com/180</guid>
      <comments>https://3onk.tistory.com/entry/ai-whitelist-file-scope-patch-fe#entry180comment</comments>
      <pubDate>Tue, 29 Sep 2026 11:36:17 +0900</pubDate>
    </item>
    <item>
      <title>Flutter RepaintBoundary &amp;mdash; 깜빡이는 조상 setState에 전체가 다시 그려지지 않게</title>
      <link>https://3onk.tistory.com/entry/flutter-repaintboundary-layer-jank</link>
      <description>&lt;p&gt;&lt;strong&gt;한 줄 요약:&lt;/strong&gt; 타이머·오디오 미터·커서 깜빡임처럼 &lt;strong&gt;자주 변하는 위젯&lt;/strong&gt;이 큰 트리 위에 있으면, 조상 &lt;code&gt;setState&lt;/code&gt;·부모 paint가 &lt;strong&gt;카드 리스트 전체&lt;/strong&gt;를 다시 그린다. &lt;strong&gt;&lt;code&gt;RepaintBoundary&lt;/code&gt;&lt;/strong&gt; 로 &lt;strong&gt;리페인트 경계를 끊어&lt;/strong&gt; 레이어를 분리하는 것이 계약이다. AnimatedSwitcher(#156)·PageRouteBuilder(#160)·resume refresh(#165)·ValueListenableBuilder(#84)·AnimationController dispose(#76)와 겹치지 않게, 이번엔 &lt;strong&gt;페인트 경계·레이어&lt;/strong&gt;만 판다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;버전 가정:&lt;/strong&gt; Flutter 3.x · &lt;code&gt;RepaintBoundary&lt;/code&gt; · DevTools Performance / &lt;code&gt;debugProfilePaintsEnabled&lt;/code&gt; 감각. 무분별한 Boundary는 레이어·메모리 비용이 있다 — &lt;strong&gt;핫스팟에만&lt;/strong&gt;. Skia/Impeller 차이 심화·엔진 내부 튜토리얼 아님.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;교차 링크(재탕 금지):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AnimatedSwitcher → draft 156&lt;/li&gt;
&lt;li&gt;PageRouteBuilder → draft 160&lt;/li&gt;
&lt;li&gt;resume data refresh → draft 165&lt;/li&gt;
&lt;li&gt;ValueListenableBuilder → draft 84&lt;/li&gt;
&lt;li&gt;AnimationController dispose → draft 76&lt;/li&gt;
&lt;li&gt;AutomaticKeepAlive → draft 47 (&lt;strong&gt;스크롤 keepAlive ≠ 페인트 경계&lt;/strong&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;1차 소스: &lt;a href=&quot;https://api.flutter.dev/flutter/widgets/RepaintBoundary-class.html&quot;&gt;RepaintBoundary&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;이 글에서 다루는 것&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;증상 → 언제 Boundary인가&lt;/li&gt;
&lt;li&gt;최소 패턴 · 자식/부모 위치&lt;/li&gt;
&lt;li&gt;과용 비용&lt;/li&gt;
&lt;li&gt;AI 안티패턴 · FAQ · 체크리스트&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;1. 증상과 원인 — 답부터&lt;/h2&gt;
&lt;h3&gt;흔한 증상&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;한 줄 스피너만 도는데 리스트 스크롤이 같이 버벅&lt;/li&gt;
&lt;li&gt;PerformanceOverlay에 넓은 영역이 매 프레임 반짝&lt;/li&gt;
&lt;li&gt;“상태만 국소인데 왜 전체가?”&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;원인 한 줄&lt;/h3&gt;
&lt;p&gt;Flutter는 &lt;strong&gt;더티한 render object의 페인트가 조상/형제로 번질 수&lt;/strong&gt; 있다. &lt;strong&gt;&lt;code&gt;RepaintBoundary&lt;/code&gt;&lt;/strong&gt; 는 자손을 &lt;strong&gt;별 레이어&lt;/strong&gt;로 합성해, 안쪽 리페인트가 바깥을 덜 건드리게 한다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;상황&lt;/th&gt;
&lt;th&gt;처방&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;1Hz 미만 UI 갱신&lt;/td&gt;
&lt;td&gt;Boundary 없어도 됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;애니메이션·미터·타이머가 큰 리스트 옆&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;그 위젯을 Boundary로 감싸기&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;상태 소유가 조상에 있음&lt;/td&gt;
&lt;td&gt;상태를 내리거나 Listenable로 국소화(#84) &lt;strong&gt;후&lt;/strong&gt; Boundary&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;2. 최소 패턴&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-dart&quot;&gt;class HeaderWithClock extends StatelessWidget {
  const HeaderWithClock({super.key, required this.child});
  final Widget child;

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Expanded(child: child),
        const RepaintBoundary(child: _BlinkClock()),
      ],
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;자주 그리는 잎&lt;/strong&gt;을 Boundary &lt;strong&gt;안&lt;/strong&gt;에.&lt;/li&gt;
&lt;li&gt;리스트 &lt;strong&gt;아이템 전체&lt;/strong&gt;를 무조건 Boundary로 감싸지 말 것 — 프로파일 후 핫스팟만.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RepaintBoundary.wrap&lt;/code&gt; / 자동 경계가 생기는 스크롤 슬롯과 역할을 헷갈리지 말 것(스크롤 최적화 ≠ 이 글의 의도적 경계).&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;3. 상태 국소화가 먼저&lt;/h2&gt;
&lt;p&gt;Boundary는 &lt;strong&gt;밴드에이드&lt;/strong&gt;가 되기 쉽다. 우선순위:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;애니메이션 상태를 &lt;strong&gt;잎 위젯&lt;/strong&gt;으로 이동&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AnimatedBuilder&lt;/code&gt; / &lt;code&gt;ValueListenableBuilder&lt;/code&gt;로 &lt;strong&gt;빌드 범위 축소&lt;/strong&gt;(#84)&lt;/li&gt;
&lt;li&gt;그래도 이웃 페인트가 보이면 &lt;code&gt;RepaintBoundary&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;4. 과용 비용&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;레이어 수 ↑ → 메모리·합성 비용&lt;/li&gt;
&lt;li&gt;“전부 Boundary”는 AI가 좋아하는 거짓 최적화&lt;/li&gt;
&lt;li&gt;DevTools에서 &lt;strong&gt;전/후 프레임·레이어 수&lt;/strong&gt;를 보고 남길지 결정&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;AI 안티패턴&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;안티패턴&lt;/th&gt;
&lt;th&gt;대신&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;루트 Scaffold를 Boundary&lt;/td&gt;
&lt;td&gt;핫스팟 잎만&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;setState 조상 유지 + Boundary만 추가&lt;/td&gt;
&lt;td&gt;상태 국소화 먼저&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;KeepAlive와 혼동&lt;/td&gt;
&lt;td&gt;47은 스크롤 dispose 방지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hero/전환 이슈를 Boundary로 해결&lt;/td&gt;
&lt;td&gt;160·50 계약 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;FAQ&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Q. &lt;code&gt;Opacity&lt;/code&gt;·클립과 같이 쓰나?&lt;/strong&gt;&lt;br&gt;A. 일부 위젯은 자체로 레이어를 만든다. 중복 Boundary는 프로파일로 판단.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q. 웹(Flutter web)에서도?&lt;/strong&gt;&lt;br&gt;A. 개념은 같으나 합성 경로가 다름 — 모바일 프로파일 기준으로 먼저 검증.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q. resume refresh(#165)와?&lt;/strong&gt;&lt;br&gt;A. 데이터 무효화 이슈와 페인트 이슈는 별층.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;체크리스트&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; PerformanceOverlay / DevTools로 핫스팟 확인&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 상태 국소화 가능 여부 먼저 검토&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 잎에만 &lt;code&gt;RepaintBoundary&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 레이어 수·메모리 회귀 확인&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 불필요 Boundary 제거 PR 기준 문서화&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;자체 검수&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;기술:&lt;/strong&gt; RepaintBoundary 역할·과용 비용만. 엔진 내부 단정·만능 최적화 주장 없음.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;용어/맞춤법:&lt;/strong&gt; RepaintBoundary / layer / jank 통일. #156·#84·#47과 분리.&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>Flutter, 모바일</category>
      <category>flutter</category>
      <category>jank</category>
      <category>Layer</category>
      <category>PerformanceOverlay</category>
      <category>RepaintBoundary</category>
      <category>setState</category>
      <category>모바일</category>
      <author>3onK</author>
      <guid isPermaLink="true">https://3onk.tistory.com/179</guid>
      <comments>https://3onk.tistory.com/entry/flutter-repaintboundary-layer-jank#entry179comment</comments>
      <pubDate>Tue, 29 Sep 2026 11:33:08 +0900</pubDate>
    </item>
    <item>
      <title>CSS `field-sizing: content` &amp;mdash; textarea 높이 JS 측정 그만하기</title>
      <link>https://3onk.tistory.com/entry/css-field-sizing-content-textarea-fe</link>
      <description>&lt;p&gt;&lt;strong&gt;한 줄 요약:&lt;/strong&gt; 댓글·프롬프트 박스마다 &lt;code&gt;scrollHeight&lt;/code&gt;를 읽어 &lt;code&gt;style.height&lt;/code&gt;를 때리는 &lt;strong&gt;autosize 훅&lt;/strong&gt;은 접근성·IME·SSR에서 새는 지점이 많다. 모던 엔진의 &lt;strong&gt;&lt;code&gt;field-sizing: content&lt;/code&gt;&lt;/strong&gt; 로 &lt;strong&gt;필드가 콘텐츠만큼 커지게&lt;/strong&gt; 두는 것이 계약이다. &lt;code&gt;text-wrap&lt;/code&gt;(#155)·&lt;code&gt;color-mix&lt;/code&gt;(#163)·scroll-snap(#159)·&lt;code&gt;:has&lt;/code&gt;(#148)·Anchor(#139)와 겹치지 않게, 이번엔 &lt;strong&gt;폼 컨트롤 자동 크기&lt;/strong&gt;만 판다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;버전 가정:&lt;/strong&gt; CSS UI &lt;code&gt;field-sizing&lt;/code&gt; (&lt;code&gt;content&lt;/code&gt; | &lt;code&gt;fixed&lt;/code&gt;). Chromium 계열 중심으로 실무 도입 — &lt;strong&gt;지원 표는 배포 주 mid-2026+ 기준으로 팀 caniuse/MDN을 재확인&lt;/strong&gt;. 미지원 브라우저는 &lt;code&gt;fixed&lt;/code&gt;(기본 감각)로 두고 JS 폴백은 &lt;strong&gt;점진적 향상&lt;/strong&gt;. 디자인 시스템 전체 마이그레이션 가이드 아님.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;교차 링크(재탕 금지):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;text-wrap balance/pretty → draft 155&lt;/li&gt;
&lt;li&gt;color-mix · relative color → draft 163&lt;/li&gt;
&lt;li&gt;scroll-snap → draft 159&lt;/li&gt;
&lt;li&gt;:has → draft 148&lt;/li&gt;
&lt;li&gt;Anchor positioning → draft 139&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;1차 감각 소스: MDN &lt;code&gt;field-sizing&lt;/code&gt; · CSS UI 스펙. 버전·지원율 숫자는 글에서 고정하지 않고 &lt;strong&gt;팀 확인 칸&lt;/strong&gt;으로 둔다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;이 글에서 다루는 것&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;답 먼저 — JS autosize vs &lt;code&gt;field-sizing&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;최소 CSS · max-height와 조합&lt;/li&gt;
&lt;li&gt;폴백 전략&lt;/li&gt;
&lt;li&gt;AI 안티패턴 · FAQ · 체크리스트&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;1. 증상과 원인 — 답부터&lt;/h2&gt;
&lt;h3&gt;흔한 증상&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;한글 IME 조합 중 높이가 깜빡이거나 한 줄 늦게 따라옴&lt;/li&gt;
&lt;li&gt;SSR 마크업과 클라이언트 측정이 어긋나 CLS&lt;/li&gt;
&lt;li&gt;&lt;code&gt;onInput&lt;/code&gt;마다 layout thrash → INP 악화(#97 감각)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;원인 한 줄&lt;/h3&gt;
&lt;p&gt;높이를 &lt;strong&gt;스크립트 관측&lt;/strong&gt;으로 맞추면 입력 파이프라인과 레이아웃이 경쟁한다. &lt;strong&gt;&lt;code&gt;field-sizing: content&lt;/code&gt;&lt;/strong&gt; 는 엔진이 콘텐츠 기반으로 컨트롤 크기를 잡게 한다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;접근&lt;/th&gt;
&lt;th&gt;언제&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;field-sizing: content&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;지원 브라우저 · 댓글/프롬프트/짧은 메모&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;field-sizing: fixed&lt;/code&gt; + rows&lt;/td&gt;
&lt;td&gt;고정 줄 수가 제품 계약일 때&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JS autosize&lt;/td&gt;
&lt;td&gt;미지원 폴백만 · &lt;code&gt;@supports&lt;/code&gt; 밖&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;2. 최소 CSS&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.prompt {
  field-sizing: content;
  min-height: 2.75rem;
  max-height: 12rem; /* 무한 성장 방지 — 스크롤로 넘김 */
  width: 100%;
  resize: none; /* content 모드에서 수동 리사이즈와 역할 정리 */
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;textarea class=&amp;quot;prompt&amp;quot; name=&amp;quot;prompt&amp;quot; rows=&amp;quot;1&amp;quot; placeholder=&amp;quot;메시지를 입력&amp;quot;&amp;gt;&amp;lt;/textarea&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;min-height&lt;/code&gt; / &lt;code&gt;max-height&lt;/code&gt;&lt;/strong&gt;: 빈 상태·장문 폭주 가드.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;rows&lt;/code&gt;&lt;/strong&gt;: 폴백·접근성 힌트로 남겨도 됨(지원 엔진에서는 content가 우선하는 그림 — 팀으로 한번 확인).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;input&lt;/strong&gt;에도 동일 속성이 열리는 엔진이 있다. &lt;strong&gt;가로 성장&lt;/strong&gt;이 레이아웃을 깨면 &lt;code&gt;width&lt;/code&gt;/&lt;code&gt;max-width&lt;/code&gt;를 먼저 고정.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;3. 점진적 향상&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.prompt {
  /* 기본: 고정 감각 */
  field-sizing: fixed;
}

@supports (field-sizing: content) {
  .prompt {
    field-sizing: content;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;JS autosize는 &lt;code&gt;@supports&lt;/code&gt; 실패 구간에서만 로드. &lt;strong&gt;지원 브라우저에 JS를 이중 적용하지 말 것&lt;/strong&gt; — 높이 전쟁이 난다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;4. AI 안티패턴&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;안티패턴&lt;/th&gt;
&lt;th&gt;대신&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;모든 textarea에 &lt;code&gt;useAutosizeTextArea&lt;/code&gt; 강제&lt;/td&gt;
&lt;td&gt;&lt;code&gt;field-sizing&lt;/code&gt; 먼저&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;scrollHeight + 2px&lt;/code&gt; 매직&lt;/td&gt;
&lt;td&gt;max-height + 스크롤&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;resize: both + content 동시&lt;/td&gt;
&lt;td&gt;제품이 수동 리사이즈를 원할 때만 예외&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;지원율 숫자를 글에 박제&lt;/td&gt;
&lt;td&gt;배포 주 재확인 칸&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;FAQ&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Q. contenteditable div에도 되나?&lt;/strong&gt;&lt;br&gt;A. 이 글 범위는 &lt;strong&gt;form-associated 컨트롤&lt;/strong&gt; 중심. contenteditable은 별 계약.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q. React controlled value와 충돌?&lt;/strong&gt;&lt;br&gt;A. value는 그대로. 높이 측정을 제거하는 것이 목표. controlled 패턴 자체는 유지.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q. 디자인 토큰 색(#163)이랑?&lt;/strong&gt;&lt;br&gt;A. 직교. 크기는 field-sizing, 색은 color-mix.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;체크리스트&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 대상 textarea/input에 &lt;code&gt;field-sizing: content&lt;/code&gt; + min/max-height&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; &lt;code&gt;@supports&lt;/code&gt; 폴백 분기&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 기존 autosize 훅 이중 적용 제거&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; IME·장문·빈 값 수동 QA&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 팀 caniuse/MDN 지원 표 날짜 기록&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;자체 검수&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;기술:&lt;/strong&gt; field-sizing 값·점진적 향상만. 미지원율 숫자 발명 없음. JS autosize 완전 금지 단정 없음(폴백 허용).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;용어/맞춤법:&lt;/strong&gt; field-sizing / content / fixed 통일. #155·#163과 분리.&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>실무 프론트 팁</category>
      <category>Content</category>
      <category>css</category>
      <category>field-sizing</category>
      <category>input</category>
      <category>textArea</category>
      <category>실무프론트팁</category>
      <category>자동높이</category>
      <author>3onK</author>
      <guid isPermaLink="true">https://3onk.tistory.com/178</guid>
      <comments>https://3onk.tistory.com/entry/css-field-sizing-content-textarea-fe#entry178comment</comments>
      <pubDate>Tue, 29 Sep 2026 11:29:49 +0900</pubDate>
    </item>
    <item>
      <title>Next.js `viewport` &amp;middot; `generateViewport` &amp;mdash; themeColor를 metadata에 묶지 않기</title>
      <link>https://3onk.tistory.com/entry/nextjs-viewport-generate-viewport</link>
      <description>&lt;p&gt;&lt;strong&gt;한 줄 요약:&lt;/strong&gt; &lt;code&gt;metadata&lt;/code&gt; 안에 &lt;code&gt;themeColor&lt;/code&gt;·&lt;code&gt;viewport&lt;/code&gt;를 계속 넣으면 &lt;strong&gt;분리된 viewport 계약&lt;/strong&gt;을 놓치고, AI가 구버전 예시를 복붙한다. App Router는 &lt;strong&gt;&lt;code&gt;export const viewport&lt;/code&gt; / &lt;code&gt;generateViewport&lt;/code&gt;&lt;/strong&gt; 가 theme-color·초기 스케일을 담당한다. &lt;code&gt;generateMetadata&lt;/code&gt;(#56)·&lt;code&gt;sitemap.ts&lt;/code&gt;/&lt;code&gt;robots.ts&lt;/code&gt;(#164)·&lt;code&gt;template&lt;/code&gt; vs &lt;code&gt;layout&lt;/code&gt;(#157)·&lt;code&gt;searchParams&lt;/code&gt; Promise(#34)·&lt;code&gt;loading.tsx&lt;/code&gt;(#38)와 겹치지 않게, 이번엔 &lt;strong&gt;viewport 수출&lt;/strong&gt;만 판다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;버전 가정:&lt;/strong&gt; Next.js App Router · Server Components에서만 &lt;code&gt;viewport&lt;/code&gt; / &lt;code&gt;generateViewport&lt;/code&gt;. 같은 세그먼트에 &lt;strong&gt;둘 다 export 금지&lt;/strong&gt;. &lt;code&gt;metadata-to-viewport-export&lt;/code&gt; codemod 언급. 코드는 최소 골격. PWA 매니페스트 전체·브라우저별 theme-color 버그는 범위 밖.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;교차 링크(재탕 금지):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;generateMetadata / OG → draft 56&lt;/li&gt;
&lt;li&gt;sitemap · robots → draft 164&lt;/li&gt;
&lt;li&gt;template vs layout → draft 157&lt;/li&gt;
&lt;li&gt;searchParams await → draft 34&lt;/li&gt;
&lt;li&gt;loading.tsx → draft 38&lt;/li&gt;
&lt;li&gt;error / not-found → draft 21&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;1차 소스: &lt;a href=&quot;https://nextjs.org/docs/app/api-reference/functions/generate-viewport&quot;&gt;generateViewport&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;이 글에서 다루는 것&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;증상 → 원인 표&lt;/li&gt;
&lt;li&gt;static &lt;code&gt;viewport&lt;/code&gt; vs &lt;code&gt;generateViewport&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;metadata에서 옮기는 법 · codemod&lt;/li&gt;
&lt;li&gt;런타임 의존 시 주의&lt;/li&gt;
&lt;li&gt;AI 안티패턴 · FAQ · 체크리스트&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;1. 증상과 원인 — 답부터&lt;/h2&gt;
&lt;h3&gt;흔한 증상&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;다크 모드인데 모바일 브라우저 크롬(주소창) 색이 안 바뀜&lt;/li&gt;
&lt;li&gt;&lt;code&gt;metadata.themeColor&lt;/code&gt;를 고쳤는데 빌드 경고·타입 불만&lt;/li&gt;
&lt;li&gt;AI가 &lt;code&gt;export const metadata = { viewport: { ... }, themeColor }&lt;/code&gt; 구형을 계속 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;원인 한 줄&lt;/h3&gt;
&lt;p&gt;viewport 관련 필드는 &lt;strong&gt;&lt;code&gt;metadata&lt;/code&gt;가 아니라 &lt;code&gt;viewport&lt;/code&gt; / &lt;code&gt;generateViewport&lt;/code&gt; 계약&lt;/strong&gt;이다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;잘못된 습관&lt;/th&gt;
&lt;th&gt;계약&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;metadata.themeColor&lt;/code&gt;에만 의존&lt;/td&gt;
&lt;td&gt;&lt;code&gt;viewport.themeColor&lt;/code&gt; (또는 generateViewport 반환)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;같은 파일에 &lt;code&gt;viewport&lt;/code&gt; + &lt;code&gt;generateViewport&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;둘 중 하나&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;정적 색인데 &lt;code&gt;generateViewport&lt;/code&gt;에서 &lt;code&gt;cookies()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;불필요 동적 → 프리렌더/내비 비용&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;2. static &lt;code&gt;viewport&lt;/code&gt; — 기본값&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import type { Viewport } from &amp;#39;next&amp;#39;

export const viewport: Viewport = {
  themeColor: [
    { media: &amp;#39;(prefers-color-scheme: light)&amp;#39;, color: &amp;#39;#ffffff&amp;#39; },
    { media: &amp;#39;(prefers-color-scheme: dark)&amp;#39;, color: &amp;#39;#0a0a0a&amp;#39; },
  ],
  width: &amp;#39;device-width&amp;#39;,
  initialScale: 1,
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;런타임 값이 필요 없으면 &lt;strong&gt;이 형태가 기본&lt;/strong&gt;. 문서도 “runtime에 안 기대면 static”을 권한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;3. &lt;code&gt;generateViewport&lt;/code&gt; — 세그먼트에 따라 달라질 때&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import type { Viewport } from &amp;#39;next&amp;#39;

type Props = {
  params: Promise&amp;lt;{ brand: string }&amp;gt;
}

export async function generateViewport({ params }: Props): Promise&amp;lt;Viewport&amp;gt; {
  const { brand } = await params
  return {
    themeColor: brand === &amp;#39;night&amp;#39; ? &amp;#39;#0a0a0a&amp;#39; : &amp;#39;#ffffff&amp;#39;,
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Server Component 세그먼트에서만.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;params&lt;/code&gt; / &lt;code&gt;searchParams&lt;/code&gt;는 프레임워크 버전에 맞게 &lt;strong&gt;Promise await&lt;/strong&gt;(#34와 동일 감각).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;요청 쿠키·헤더에 실제로 묶여야 할 때만&lt;/strong&gt; 동적으로. 아니면 static으로 되돌린다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;Cache Components·프리렌더 경로에서는 &lt;code&gt;generateViewport&lt;/code&gt;가 런타임 API를 읽으면 &lt;strong&gt;초기 로드 viewport를 미룰 수 없어&lt;/strong&gt; 내비/프리렌더가 막힐 수 있다. 공식 메시지 &lt;a href=&quot;https://nextjs.org/docs/messages/blocking-prerender-viewport-runtime&quot;&gt;&lt;code&gt;blocking-prerender-viewport-runtime&lt;/code&gt;&lt;/a&gt; 계열을 팀 위키에 링크해 둔다. 해결 우선순위: (1) static으로 재작성 (2) 런타임 의존 제거 (3) 문서가 안내하는 세그먼트 opt-out — &lt;strong&gt;루트에 함부로 켜지 말 것&lt;/strong&gt;.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;hr&gt;
&lt;h2&gt;4. metadata에서 옮기기&lt;/h2&gt;
&lt;p&gt;구형:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;export const metadata = {
  title: &amp;#39;My App&amp;#39;,
  themeColor: &amp;#39;dark&amp;#39;,
  viewport: { width: &amp;#39;device-width&amp;#39; },
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;신형:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;export const metadata = { title: &amp;#39;My App&amp;#39; }

export const viewport = {
  width: &amp;#39;device-width&amp;#39;,
  themeColor: &amp;#39;dark&amp;#39;,
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;일괄: &lt;code&gt;npx @next/codemod@latest metadata-to-viewport-export .&lt;/code&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;5. AI 안티패턴&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;안티패턴&lt;/th&gt;
&lt;th&gt;대신&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;본문 &lt;code&gt;&amp;lt;meta name=&amp;quot;viewport&amp;quot;&amp;gt;&lt;/code&gt; 수동 삽입&lt;/td&gt;
&lt;td&gt;&lt;code&gt;viewport&lt;/code&gt; export&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;themeColor만 metadata에 남김&lt;/td&gt;
&lt;td&gt;viewport로 이동&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;모든 페이지 generateViewport + cookies&lt;/td&gt;
&lt;td&gt;정말 테마가 유저별로 다를 때만&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;layout과 page 둘 다 충돌 export&lt;/td&gt;
&lt;td&gt;세그먼트 소유권 하나로&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;FAQ&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Q. generateMetadata와 같이 쓰나?&lt;/strong&gt;&lt;br&gt;A. 예. 역할이 다름 — 문서/OG는 metadata, 뷰포트/theme-color는 viewport.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q. 클라이언트 컴포넌트에서 export?&lt;/strong&gt;&lt;br&gt;A. 문서: Server Components만. &lt;code&gt;&amp;#39;use client&amp;#39;&lt;/code&gt; 파일에 두지 말 것.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q. sitemap(#164)이랑 헷갈린다?&lt;/strong&gt;&lt;br&gt;A. 메타데이터 &lt;strong&gt;파일 라우트&lt;/strong&gt; vs &lt;strong&gt;세그먼트 export&lt;/strong&gt;. 층이 다르다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;체크리스트&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; &lt;code&gt;metadata&lt;/code&gt;에 남은 &lt;code&gt;themeColor&lt;/code&gt; / &lt;code&gt;viewport&lt;/code&gt; 키 검색·이전&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 세그먼트당 &lt;code&gt;viewport&lt;/code&gt; XOR &lt;code&gt;generateViewport&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 정적 색은 static export&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 다크/라이트 media 쿼리 themeColor 필요한지 제품과 합의&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; codemod 후 타입 &lt;code&gt;Viewport&lt;/code&gt; import&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 런타임 의존 시 프리렌더 경고 문서 링크&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;자체 검수&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;기술:&lt;/strong&gt; Next 공식 generateViewport 문서·codemod·blocking-prerender 메시지 각도만. 없는 API 발명 없음.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;용어/맞춤법:&lt;/strong&gt; viewport / generateViewport / themeColor 통일. #56·#164와 분리.&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>Next.js, React 삽질</category>
      <category>app router</category>
      <category>codemod</category>
      <category>generateViewport</category>
      <category>metadata</category>
      <category>next.js</category>
      <category>themeColor</category>
      <category>Viewport</category>
      <category>프론트엔드</category>
      <author>3onK</author>
      <guid isPermaLink="true">https://3onk.tistory.com/177</guid>
      <comments>https://3onk.tistory.com/entry/nextjs-viewport-generate-viewport#entry177comment</comments>
      <pubDate>Tue, 29 Sep 2026 11:25:29 +0900</pubDate>
    </item>
    <item>
      <title>Claude Sonnet 5.5 on AI Gateway(2026-09-28) &amp;mdash; FE 일상 코딩&amp;middot;문서 루프 데스크 노트</title>
      <link>https://3onk.tistory.com/entry/claude-sonnet-55-ai-gateway-sep2026</link>
      <description>&lt;p&gt;&lt;strong&gt;한 줄 요약:&lt;/strong&gt; 2026-09-29 기준, late-Sep FE·에이전트 데스크가 열어볼 &lt;strong&gt;공식 공지&lt;/strong&gt;는 Vercel Changelog &lt;strong&gt;&lt;a href=&quot;https://vercel.com/changelog/claude-sonnet-5-5-now-available-on-ai-gateway&quot;&gt;Claude Sonnet 5.5 now available on AI Gateway&lt;/a&gt;&lt;/strong&gt;(Published &lt;strong&gt;September 28, 2026&lt;/strong&gt; · Rohan Taneja, Zachary Chen, Jerilyn Zheng)다. 요지(원문 고정): (1) 모델 id &lt;strong&gt;&lt;code&gt;anthropic/claude-sonnet-5.5&lt;/code&gt;&lt;/strong&gt; (2) Sonnet 5 대비 &lt;strong&gt;잘 범위가 잡힌 일상 작업&lt;/strong&gt;(피처·버그픽스·문서/슬라이드/스프레드시트) 개선 (3) 코딩에서 &lt;strong&gt;여러 스텝을 이어가고 완료 전 자체 점검&lt;/strong&gt; 경향 (4) 문서·차트·데스크톱 앱 사용·기술 도면 읽기 개선 언급 (5) AI Gateway에서 &lt;strong&gt;Zero Data Retention 지원&lt;/strong&gt; (6) AI SDK · Chat Completions · Responses · Anthropic Messages · coding agents + &lt;code&gt;npx vercel ai-gateway setup&lt;/code&gt; / &lt;code&gt;/model&lt;/code&gt;. Opus 5.5 Gateway(#130)·Pixel Canary 스텔스(#158)·GPT-6 Sol/Luna(#134)·Managed Agents(#122)와 &lt;strong&gt;제품 면이 다름&lt;/strong&gt; — 이번엔 &lt;strong&gt;Sonnet 5.5 Gateway 롤아웃 FE 함의&lt;/strong&gt;만. 원문에 없는 단가표·벤치 승패·자동 기본모델 교체 단정은 적지 않는다.&lt;/p&gt;
&lt;p&gt;교차 링크(재탕 금지):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Claude Opus 5.5 Gateway → draft 130 (&lt;strong&gt;상위 에이전트 모델 ≠ 일상 Sonnet 슬롯&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;Pixel Canary stealth → draft 158&lt;/li&gt;
&lt;li&gt;GPT-6 Sol/Luna API → draft 134&lt;/li&gt;
&lt;li&gt;Managed Agents auto → draft 122&lt;/li&gt;
&lt;li&gt;Claude Code Projects → draft 96&lt;/li&gt;
&lt;li&gt;one Claude Docs/Slides → draft 87&lt;/li&gt;
&lt;li&gt;AI SDK harness → draft 75&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;1차 확인(2026-09-29):&lt;/strong&gt; &lt;a href=&quot;https://vercel.com/changelog/claude-sonnet-5-5-now-available-on-ai-gateway&quot;&gt;Vercel Changelog 2026-09-28&lt;/a&gt;만. 배포 전 URL 재확인.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;hr&gt;
&lt;h2&gt;이 글에서 다루는 것&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;공지가 말한 것 / 안 말한 것&lt;/li&gt;
&lt;li&gt;왜 FE 데스크 뉴스인가 (Opus 슬롯과 분리)&lt;/li&gt;
&lt;li&gt;지금 / 스테이징 / 안 함&lt;/li&gt;
&lt;li&gt;FAQ · 체크리스트&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;1. 답 먼저 — 왜 FE 뉴스인가&lt;/h2&gt;
&lt;p&gt;Opus 5.5(#130)가 “긴 에이전트·깨지는 요청 계약” 뉴스였다면, Sonnet 5.5는 &lt;strong&gt;매일 돌리는 PR·리뷰·문서 루프의 기본 슬롯&lt;/strong&gt; 후보다. 원문이 강조하는 면도 그쪽이다 — well-scoped everyday work, multi-step coding with self-check, polished docs/slides.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;역할&lt;/th&gt;
&lt;th&gt;Changelog 요지&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;모델 ID&lt;/td&gt;
&lt;td&gt;&lt;code&gt;anthropic/claude-sonnet-5.5&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;강점(원문)&lt;/td&gt;
&lt;td&gt;피처·버그픽스 · 문서/슬라이드/시트 · multi-step coding + self-check · 차트·기술도면·데스크톱 앱&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;컴플라이언스&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;ZDR 지원&lt;/strong&gt;(Gateway)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;연결면&lt;/td&gt;
&lt;td&gt;AI SDK · Chat Completions · Responses · Anthropic Messages · Claude Code/fx + &lt;code&gt;vercel ai-gateway setup&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;슬랙 한 줄:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;Sonnet 5.5 Gateway 올림. 일상 코딩 슬롯 후보. ZDR 켜는지 팀 표 확인. Opus 5.5 breaking(thinking/forced tool)과 혼동 금지.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;hr&gt;
&lt;h2&gt;2. Changelog가 말한 것 / 안 말한 것&lt;/h2&gt;
&lt;h3&gt;말한 것&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;AI Gateway에 Sonnet 5.5 가용, id &lt;code&gt;anthropic/claude-sonnet-5.5&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Sonnet 5 대비 일상 작업·문서 산출물·코딩 multi-step/self-check 개선 서술&lt;/li&gt;
&lt;li&gt;ZDR on AI Gateway&lt;/li&gt;
&lt;li&gt;&lt;code&gt;streamText({ model: &amp;#39;anthropic/claude-sonnet-5.5&amp;#39;, ... })&lt;/code&gt; 예시&lt;/li&gt;
&lt;li&gt;Claude Code·fx: setup 후 &lt;code&gt;/model&lt;/code&gt;로 선택&lt;/li&gt;
&lt;li&gt;Chat Completions / Anthropic Messages 예시 코드&lt;/li&gt;
&lt;li&gt;Gateway 단일 API·usage/cost·routing/retries/failover · Gateway key 또는 BYOK&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;안 말한 것 (추측 금지)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;토큰 단가 표·우리 계정 쿼터 배수&lt;/li&gt;
&lt;li&gt;“Opus를 대체한다 / Cursor 기본값을 바꾼다”&lt;/li&gt;
&lt;li&gt;Opus 5.5의 adaptive thinking·forced tool 폐기 계약이 Sonnet에도 동일하다는 단정(이 항목 원문에 없음 — Opus 글 #130과 분리)&lt;/li&gt;
&lt;li&gt;벤치 리더보드 숫자&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;3. FE 데스크 관점 — 슬롯을 나누는 법&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;슬롯&lt;/th&gt;
&lt;th&gt;후보(기존 글)&lt;/th&gt;
&lt;th&gt;이번 공지&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;긴 에이전트·고난도&lt;/td&gt;
&lt;td&gt;Opus 5.5 Gateway (#130)&lt;/td&gt;
&lt;td&gt;비대상&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;일상 PR·리뷰·짧은 피처&lt;/td&gt;
&lt;td&gt;(팀 표)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Sonnet 5.5&lt;/strong&gt; 후보&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;스텔스/실험 모델&lt;/td&gt;
&lt;td&gt;Pixel Canary (#158)&lt;/td&gt;
&lt;td&gt;비대상&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;저비용 대량&lt;/td&gt;
&lt;td&gt;Luna 등 (#134)&lt;/td&gt;
&lt;td&gt;비대상&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;실무 규칙 초안:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;기본 일상 슬롯&lt;/strong&gt;에 Sonnet 5.5를 올릴지 A/B(스테이징 에이전트 1대).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ZDR 필요 레포&lt;/strong&gt;는 Gateway ZDR 옵션·키 정책을 표로 고정 후 모델 변경.&lt;/li&gt;
&lt;li&gt;Opus 하니스의 thinking/tool_choice 가드는 &lt;strong&gt;Opus 경로에만&lt;/strong&gt; 유지(#130). Sonnet 경로에 복사·삭제하기 전 원문·SDK 릴리즈 노트 재확인.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;npx vercel ai-gateway setup&lt;/code&gt; 후 &lt;code&gt;/model&lt;/code&gt; 피커에 id가 보이는지 확인.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;4. 원문 코드 골격 (그대로 읽기)&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;import { streamText } from &amp;#39;ai&amp;#39;;

const result = streamText({
  model: &amp;#39;anthropic/claude-sonnet-5.5&amp;#39;,
  prompt: &amp;#39;Fix the failing test and verify the change.&amp;#39;,
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;에이전트:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx vercel ai-gateway setup
# Claude Code / fx 에서 /model → Claude Sonnet 5.5&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;5. 지금 / 스테이징 / 안 함&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;상태&lt;/th&gt;
&lt;th&gt;조건&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;지금&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Gateway 카탈로그에 id 노출 확인 · 문서/슬라이드 보조 봇에 한정 적용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;스테이징&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;일상 PR 에이전트 1대에 올려 flake·과잉편집 관찰 1주&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;안 함&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Opus 경로와 설정을 뭉뚱그려 교체 · ZDR 미확인 프로덕션 키에 즉시 전면 적용&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;FAQ&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Q. Opus 5.5 글을 다시 읽어야 하나?&lt;/strong&gt;&lt;br&gt;A. 긴 에이전트·HTTP 400 계약이면 #130. 일상 슬롯이면 이 글.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q. ZDR이 기본인가?&lt;/strong&gt;&lt;br&gt;A. 원문은 “supports Zero Data Retention” — &lt;strong&gt;opt-in/설정 방식은 팀 Gateway 문서·콘솔을 진실&lt;/strong&gt;로. 이 글에서 기본 on을 단정하지 않음.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q. Cursor 기본 모델이 바뀌나?&lt;/strong&gt;&lt;br&gt;A. 원문에 없음. Gateway/에이전트 &lt;code&gt;/model&lt;/code&gt; 선택 이슈로 취급.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;체크리스트&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; Changelog URL·날짜(2026-09-28)·모델 id 재확인&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 일상 슬롯 vs Opus 슬롯 분리표 업데이트&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; ZDR 필요 레포 키·옵션 확인&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; &lt;code&gt;vercel ai-gateway setup&lt;/code&gt; + &lt;code&gt;/model&lt;/code&gt; 피커 확인&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 스테이징 에이전트 1대 A/B 후 전면&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;자체 검수&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;사실/날짜/출처:&lt;/strong&gt; Vercel Changelog 2026-09-28만 인용. 단가·벤치·Opus breaking 동일 적용 단정 없음.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;용어/맞춤법:&lt;/strong&gt; Sonnet 5.5 / &lt;code&gt;anthropic/claude-sonnet-5.5&lt;/code&gt; / ZDR / Gateway 표기 통일. #130·#158·#134과 각도 분리.&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>AI 뉴스&amp;middot;최신소식</category>
      <category>2026-09-28</category>
      <category>AI Gateway</category>
      <category>ai sdk</category>
      <category>AI 뉴스</category>
      <category>Anthropic</category>
      <category>claude code</category>
      <category>Claude Sonnet 5.5</category>
      <category>vercel</category>
      <category>ZDR</category>
      <author>3onK</author>
      <guid isPermaLink="true">https://3onk.tistory.com/176</guid>
      <comments>https://3onk.tistory.com/entry/claude-sonnet-55-ai-gateway-sep2026#entry176comment</comments>
      <pubDate>Tue, 29 Sep 2026 11:20:35 +0900</pubDate>
    </item>
  </channel>
</rss>