개발 환경
본 문서는 프로젝트 구성 파일의 역할과 실행 명령을 정의합니다. 각 규칙의 근거는 해당 참조 문서가 원본이며, 본 문서는 그 규칙이 어디에서 강제되는지를 연결합니다.
1. 구성 파일
| 파일 | 역할 | 규칙의 원본 |
|---|---|---|
package.json |
의존성과 버전 | 본 문서에 버전을 중복 기재하지 않습니다 |
tsconfig.json |
컴파일러 옵션, 경로 별칭 | 패키지 배치와 참조 규칙 7.3절 |
angular.json |
빌드 설정, 번들 예산, 전역 스타일 | 성능 1절 |
steiger.config.ts |
FSD 계층 규칙 강제 | 패키지 배치와 참조 규칙 9절 |
eslint.config.js |
코드 규약, 전역 프로바이더 위치 제한, 임포트 제한, 템플릿 접근성 | 패키지 배치와 참조 규칙 7.5절, 보안 3절, 폼과 검증 1절 |
.postcssrc.json |
Tailwind 플러그인 등록 | 디자인 시스템과 토큰 |
.gitattributes |
줄바꿈 정책, 바이너리 지정 | 본 문서 3절 |
설정값을 문서에 옮겨 적지 않습니다. 두 벌이 되면 한쪽이 낡습니다. 문서는 규칙과 근거를 담고, 설정 파일이 그 규칙의 실행 형태를 담습니다.
2. 경로 별칭
// tsconfig.json
{ "compilerOptions": { "paths": { "@/*": ["./src/*"] } } }baseUrl은 지정하지 않습니다. TypeScript 6에서 폐기 예정으로 표시되어 TS5101 오류로 빌드가 실패합니다. 규칙의 원본은 패키지 배치와 참조 규칙 7.3절입니다.
계층 간 임포트에 상대 경로를 쓰는 것은 ESLint no-restricted-imports가 차단합니다. Steiger는 상대 경로로 작성된 위반도 검출하므로 별칭 규칙의 근거는 검출 가능성이 아니라 파일 이동 내성입니다. 슬라이스를 옮길 때 상대 경로는 전부 깨지지만 별칭은 유지됩니다.
3. 줄바꿈 정책
모든 텍스트 파일은 저장소와 체크아웃 양쪽에서 LF를 사용합니다. .gitattributes의 * text=auto eol=lf 선언이 개인의 core.autocrlf 설정보다 우선합니다.
선언하지 않으면 Windows 환경에서 체크아웃 시 CRLF로 변환되어 커밋마다 경고가 발생하고, 협업 시 줄바꿈만 다른 차이가 생깁니다.
| 대상 | 처리 |
|---|---|
| 텍스트 전반 | LF |
*.bat, *.cmd |
CRLF. cmd가 CRLF를 기대합니다 |
| 이미지·폰트·문서 바이너리 | 변환 금지 (-text) |
package-lock.json |
linguist-generated=true. 사람이 리뷰하지 않습니다 |
주의
OpenAPI 생성 타입은 linguist-generated로 표시하지 않습니다
생성물을 커밋하는 이유가 계약 변경이 PR 차이에 드러나는 것이기 때문입니다(API 계약 소비 1.1절). 차이 표시를 접으면 필드가 사라진 것을 병합 전에 발견할 수 없게 되어 그 이유가 무효화됩니다.
4. 디렉터리 골격
src/
├── app/
├── pages/
├── shared/
├── main.ts
├── main.server.ts
├── server.ts
└── index.htmlapp은 FSD app 계층이자 Angular 루트이며 전역 스타일을 포함합니다. pages는 라우트 단위 화면, shared는 비즈니스 로직이 없는 인프라입니다. 나머지 네 파일은 프레임워크 진입점이며 계층 밖입니다.
features와 entities는 재사용이 확인된 시점에 생성합니다. 빈 폴더를 미리 만들지 않습니다.
전역 스타일은 src/app/styles.css이며 angular.json의 styles 항목이 이 경로를 가리킵니다. Angular CLI 기본값인 src/styles.css에서 이동한 것으로, FSD가 전역 스타일을 app 계층에 두도록 정하고 있기 때문입니다.
주의
src/styles.css를 만들지 않습니다
Spartan CLI의 스타일 진입점 자동 탐지는 angular.json을 먼저 보지 않습니다. <sourceRoot>/styles.{css,scss,sass,less}가 존재하면 그 파일을 쓰고, 없을 때만 angular.json의 build.options.styles로 넘어갑니다. src/styles.css가 남아 있으면 angular.json이 가리키지 않는 파일에 테마가 기록되며 오류 없이 진행됩니다. 생성기를 실행할 때 --stylesEntryPoint=src/app/styles.css를 명시하면 탐지 순서와 무관하게 고정됩니다.
5. 명령
| 명령 | 용도 |
|---|---|
npm start |
개발 서버 |
npm run build |
운영 빌드. 번들 예산 초과 시 실패합니다 |
npm test |
단위·컴포넌트 테스트 |
npm run lint |
코드 규약 검사 |
npm run lint:fsd |
FSD 계층 규칙 검사 |
npm run check |
위 셋을 순서대로 실행 |
npm run docs:build |
docs/의 마크다운을 문서 사이트 생성물로 변환 |
npm run check를 통과하지 않은 상태로 병합하지 않습니다. CI는 이 명령 하나만 실행하면 됩니다.
docs:build는 prestart·prebuild·prelint·pretest가 자동으로 앞세우므로 직접 실행할 일은 드뭅니다. 생성물은 src/shared/markdown/generated/에 놓이며 커밋하지 않습니다. OpenAPI 생성 타입을 커밋하는 이유가 계약 변경을 PR 차이에 드러내는 것인데, 문서 HTML 은 원본 마크다운이 같은 저장소에 있어 그 근거가 적용되지 않습니다. 마크다운 차이를 보면 충분합니다.
5.1 문서의 위치와 주소
문서의 계층은 폴더 구조가 원본입니다. 프론트매터에 적지 않습니다. 두 벌이 되면 한쪽이 낡습니다.
docs/
├── architectures/
│ ├── index.md 영역 개요
│ ├── <domain>/<stack>/index.md 스택 개요
│ └── <domain>/<stack>/<group>/*.md 스택의 문서
└── posts/
└── <slug>/
├── index.md 글
└── assets/ 그 글의 이미지domain은 frontend·backend, group은 references·decisions입니다. 목록에 없는 이름을 쓰면 docs:build가 실패합니다.
주소는 /architectures/<stack>/<slug> 와 /posts/<slug> 로 조립합니다. domain과 group은 주소에 넣지 않습니다. 스택 이름이 이미 유일하고 계층은 사이드바가 보여주므로, 주소에까지 넣으면 길기만 합니다.
글은 폴더 하나가 글 하나입니다. 이미지를 본문 옆에 두어 자산이 문서와 함께 움직입니다. assets/는 문서 수집 대상이 아니므로 그 안에 마크다운을 넣어도 사이트에 나타나지 않습니다. 글의 슬러그는 폴더명이며 프론트매터에 적지 않습니다.
5.2 글의 프론트매터
글은 표준 문서보다 요구하는 값이 많습니다. 목록 카드가 날짜와 표지를 함께 그리기 때문입니다.
| 항목 | 필수 | 설명 |
|---|---|---|
title · description |
필수 | 표준 문서와 같습니다 |
date |
필수 | YYYY-MM-DD. 다른 형태면 빌드가 실패합니다 |
cover |
필수 | ./assets/ 아래의 상대 경로 |
published |
필수 | true가 아니면 사이트에 나가지 않습니다 |
tags · coverColor · order |
선택 | — |
5.3 이미지
docs:build가 sharp로 폭별 이미지를 만들어 public/posts/에 놓고 srcset을 심습니다. 생성물은 커밋하지 않습니다. 원본이 docs/posts/에 있어 문서 HTML과 같은 근거가 적용됩니다.
| 항목 | 처리 |
|---|---|
| 폭 | 320 · 640 · 1360px. 원본보다 큰 폭은 만들지 않습니다 |
| 형식 | webp(품질 82). 벡터(.svg)와 애니메이션(.gif)은 그대로 복사합니다 |
| 크기 속성 | 원본의 width·height를 심어 이미지 도착 전에 자리를 잡습니다 |
본 저장소 기준으로 원본 3.9MB가 생성물 0.1MB가 됩니다.
외부 주소를 참조하는 이미지는 변환하지 않고 그대로 둡니다. 크기를 알 수 없어 자리를 예약하지 못하므로, 그 이미지가 도착할 때 본문이 밀립니다.
5.4 프론트매터와 본문의 역할
문서마다 slug·title·description을 필수로 둡니다. 누락되면 docs:build가 실패합니다. slug는 주소의 마지막 조각이며 파일명이 한글이어도 주소가 ASCII로 유지되게 하는 수단입니다.
제목을 누가 소유하는지가 영역마다 다릅니다.
| 영역 | 제목의 소유 | 본문 # 제목 |
|---|---|---|
architectures/ |
본문 | 둡니다. 화면은 본문을 그대로 렌더링합니다 |
posts/ |
프론트매터 | 두지 않습니다. 화면이 표지·제목·날짜·태그를 헤더로 그립니다 |
표준 문서에서는 프론트매터의 값을 화면에 다시 표시하지 않습니다. 본문이 이미 제목을 갖고 있어 두 값이 같으면 같은 문장이 두 번 보입니다. 두 값이 반드시 같아야 하는 것도 아닙니다. ADR 은 목록용 title이 짧고 본문 제목이 결정 내용을 담은 긴 문장이며, 이 차이는 의도된 것입니다.
글은 반대입니다. 표지와 날짜, 태그가 프론트매터에만 있어 본문이 그것을 담을 수 없습니다. 제목만 본문에 두면 표지와 날짜는 화면이 그리고 제목은 본문이 그리게 되어 헤더가 두 곳으로 갈립니다.
| 위치 | 소비처 | 규격 |
|---|---|---|
프론트매터 title |
목록, 사이드바, 글의 헤더 | 목록에서 스캔 가능한 짧은 이름 |
프론트매터 description |
목록 카드 | 명사구로 종결 |
본문 최상위 제목(#) |
표준 문서의 화면과 마크다운 원본 | 문서가 다루는 내용을 서술 |
| 본문 첫 문단 | 표준 문서의 화면과 마크다운 원본 | 문서의 역할을 정의하는 완결 문장 |
5.5 강조 블록
규칙이 영역마다 다릅니다. 규격을 정의하는 문서와 생각을 풀어 쓰는 글은 강조의 성격이 다릅니다.
| 영역 | 허용 종류 | 한 절(##)당 개수 |
|---|---|---|
architectures/ |
WARNING·IMPORTANT 둘 |
1개. 넘기면 빌드가 실패합니다 |
posts/ |
NOTE·TIP·IMPORTANT·WARNING·CAUTION 다섯 |
제한 없음 |
표준 문서에서 쓰는 둘의 조건은 다음과 같습니다.
| 문법 | 화면 라벨 | 사용 조건 |
|---|---|---|
> [!WARNING] |
주의 | 위반 시 데이터 손상·인가 우회·기동 실패로 이어지는 제약 |
> [!IMPORTANT] |
중요 | 작업 전 반드시 확인해야 하는 선행 조건 |
표준 문서에서 [!NOTE]·[!TIP]·[!CAUTION] 을 쓰는 것을 금지합니다. 근거 설명과 환경별 예외는 본문 문장이나 표의 열로 흡수합니다. 종류를 늘리면 강조의 희소성이 사라져 정작 치명적인 경고가 묻힙니다.
빌드나 린터가 즉시 차단하는 규칙은 블록으로 두지 않습니다. 위반하면 그 자리에서 실패하므로 놓칠 수 없습니다. 블록은 조용히 실패하는 것을 위한 자리입니다. 지연 청크가 만들어지지 않거나, 스크롤 위치 복원이 동작하지 않거나, 하이드레이션이 어긋나는 경우처럼 오류 없이 결과만 달라지는 항목이 대상입니다.
블록 내부는 굵은 소제목 한 줄을 먼저 두고 빈 인용 줄로 본문과 나눕니다.
> [!WARNING]
> **한 줄로 끝나는 소제목**
>
> 본문을 서술합니다.한 절(##)에 하나를 원칙으로 합니다. 두 개를 넘기면 그 절의 구성을 재검토합니다.
5.6 디렉터리 구조 표기
계층은 tree 기호(├──·└──·│)로 표기합니다. 들여쓰기 폭에 의존하지 않아 계층 관계가 기호로 확정됩니다.
코드 블록 안에서 공백으로 열을 맞추는 것을 금지합니다. 본문 글꼴이 한글을 담지 않아 한글만 다른 글꼴로 떨어지며, 그 폭이 고정폭의 배수와 맞지 않아 정렬이 어긋납니다. 원본 마크다운에서만 가지런하고 화면에서는 깨집니다. 근거는 ADR-0014에 있습니다.
항목 설명은 블록 밖에 둡니다. 셋 이상이면 표로, 둘 이하면 본문 문장으로 서술합니다.
shared/auth/
├── session.ts
├── auth-interceptor.ts
└── index.ts5.7 문서 간 링크
저장소 안을 가리키는 링크는 문서 파일(.md)만 대상으로 합니다. docs:build가 대상 문서를 찾아 사이트 경로로 바꾸며, 찾지 못하면 실패합니다. 사이트 경로는 파일 위치에서 유도하므로 문서에 직접 적지 않습니다.
디렉터리에 링크를 거는 것을 금지합니다. 마크다운 원본에서는 동작하지만 사이트에는 그 경로가 없어 404가 됩니다. 양쪽에서 동시에 성립하지 않는 표기이므로 references/처럼 코드 표기로 언급만 합니다.
| 표기 | 판정 |
|---|---|
[테스트](테스트.md) |
허용. 사이트 경로로 변환됩니다 |
[ADR-0013](../decisions/0013-ADR-생성문서-HTML-신뢰.md) |
허용 |
[references/](references/) |
금지. 빌드가 실패합니다 |
`references/` |
허용. 링크 없이 언급합니다 |
[fsd.how](https://fsd.how/) |
허용. 외부 링크는 검사하지 않습니다 |
5.8 절 참조
절을 가리킬 때는 3.1절 형태로 씁니다. §3.1은 법률·학술 문서의 표기이며 한국어 기술 문서에서는 쓰지 않습니다.
링크를 손으로 만들지 않습니다. docs:build가 절 번호를 해당 절의 앵커로 연결합니다. 세 가지 형태를 인식합니다.
| 원본 표기 | 변환 결과 |
|---|---|
[개발 환경](개발-환경.md) 7절 |
링크 뒤의 번호가 그 문서의 해당 절로 이어집니다 |
[아키텍처 9절](../index.md) |
링크 자체에 프래그먼트가 붙습니다 |
3.1절을 그대로 지킵니다 |
같은 문서의 해당 절로 이어집니다 |
번호에 해당하는 절이 없으면 빌드가 실패합니다. 코드 블록과 인라인 코드 안의 절 표기는 변환하지 않습니다.
다른 저장소의 문서를 절 번호로 가리키지 않습니다. 우리 문서에 그 앵커가 없어 검증할 수 없습니다. 절 이름으로 서술합니다.
6. 강제 수단의 적용 범위
문서에 적힌 규칙 중 어느 것이 자동으로 차단되고 어느 것이 코드 리뷰에 남는지는 아키텍처 11절이 원본입니다.
요약하면 계층 임포트 방향·공개 API·크로스임포트·조기 추출·번들 예산은 자동 차단되고, shared의 비즈니스 로직 금지·적응형 두 모드 테스트·정적 생성 경로의 적응형 컴포넌트 금지는 수동 확인 대상입니다.
7. 실측으로 확인한 사항
본 표준의 규칙 중 실제로 검증한 항목입니다.
| 항목 | 결과 |
|---|---|
| Steiger가 경로 별칭을 인식하는가 | 인식합니다. @/pages/... 임포트를 계층으로 해석합니다 |
| Steiger가 위반을 실제로 잡는가 | shared → pages 임포트는 fsd/forbidden-imports, 공개 API 우회는 fsd/no-public-api-sidestep으로 차단됩니다 |
| 진입점 파일이 미분류로 경고되는가 | 경고되지 않습니다. 의도를 남기기 위해 ignores에 등재했습니다 |
| 배럴이 지연 청크 분리를 방해하는가 | 방해하지 않습니다. 미사용 재수출은 트리셰이킹됩니다 |
| 리졸버가 초기 번들에 포함되는가 | 포함됩니다. 같은 배럴의 컴포넌트는 지연 청크로 분리됩니다 |
baseUrl 사용 가능 여부 |
사용 불가입니다. TypeScript 6에서 TS5101 오류가 발생합니다 |
| Steiger가 상대 경로 위반을 잡는가 | 잡습니다. 별칭 규칙의 근거는 검출 가능성이 아니라 파일 이동 내성입니다 |
| ESLint 커스텀 규칙이 발화하는가 | 전역 프로바이더 위치, 계층 간 상대 경로, Injector 임포트, Reactive Forms, XSS 우회, console.log가 모두 차단됩니다 |
| 동적 토큰 주입을 구문 규칙으로 잡는가 | 잡지 못합니다. Map.get 등 정상 호출과 구분되지 않아 Injector 임포트 제한으로 대체했습니다 |
| 생성물 수정을 린터로 막는가 | 막지 못합니다. 린터는 편집을 차단하지 않으므로 CI 재생성 검사로 대체했습니다 |
| Spartan helm이 Signal Forms와 연결되는가 | 직접 연결됩니다. [formField]·[formRoot] 바인딩이며 compat 계층이 필요 없습니다 |
| 지연 로딩이 정적 생성 경로의 첫 표시를 늦추는가 | 늦추지 않습니다. 사전 렌더된 HTML에 지연 청크의 modulepreload 힌트가 포함되어 초기 번들과 병렬로 받습니다 |
| helm 사본의 업스트림 갱신 수단이 있는가 | @spartan-ng/cli:healthcheck --autoFix가 폐기 API를 조정합니다. 시각적 개선만 수동 대상입니다 |
| brain이 오버레이 위치 전략 교체를 허용하는가 | 허용합니다. BrnOverlay의 positionStrategy 입력이 기본 전략보다 우선하며, 앱 전역 기본값은 provideBrnOverlayDefaultOptions()로 지정합니다 |
| brain의 캘린더가 키보드와 ARIA를 제공하는가 | 제공합니다. 화살표·Home·End·PageUp·PageDown, 로빙 탭인덱스, role="grid", 셀의 aria-label·aria-selected·aria-disabled가 모두 구현되어 있습니다 |
Spartan CLI가 전역 스타일 경로로 src/styles.css를 가정하는가 |
가정하지만 조건부입니다. 해당 파일이 없으면 angular.json의 styles로 넘어가 src/app/styles.css를 찾아냅니다. 4절의 주의를 참조합니다 |
BreakpointObserver가 서버에서 예외를 발생시키는가 |
발생시키지 않습니다. MediaMatcher가 비브라우저 환경에서 noopMatchMedia로 대체되어 모든 쿼리에 matches: false를 즉시 방출합니다 |
| 비ASCII 경로를 정적 생성할 수 있는가 | 생성은 되지만 서빙되지 않습니다. 한글 경로의 index.html이 정확히 만들어지고 prerendered-routes.json에도 등재되나 요청 시 404가 반환됩니다. 같은 조건에서 ASCII 경로는 200과 ssg를 반환합니다. 4절의 URL 슬러그 규칙이 여기서 나옵니다 |
allowedHosts 기본값으로 SSR 서버가 동작하는가 |
동작하지 않습니다. angular.json의 security.allowedHosts가 빈 배열이면 모든 요청이 400으로 거부됩니다. 정적 생성만 확인하면 드러나지 않고 서버를 실제로 띄울 때 전부 막힙니다 |
Angular sanitizer가 [innerHTML]의 id를 유지하는가 |
제거합니다. 목차의 앵커 대상이 사라지며 오류 없이 이동만 실패합니다. 대응은 ADR-0013에 있습니다 |
| Steiger가 슬라이스 그룹 이름을 제한하는가 | 제한합니다. 그룹 이름이 shared의 세그먼트 이름과 겹치면 fsd/ambiguous-slice-names가 빌드를 실패시킵니다. 표준 세그먼트뿐 아니라 직접 만든 세그먼트도 대상입니다 |
배럴이 @defer의 의존성 분리를 방해하는가 |
방해합니다. 같은 배럴에서 즉시 쓰는 심볼과 @defer 안에서만 쓰는 심볼을 함께 가져오면 배럴 모듈이 정적으로 묶여 지연 청크가 만들어지지 않습니다. 임포트 경로만 컴포넌트별로 나눈 결과 초기 번들이 544kB에서 275kB로 줄었습니다 |
Tailwind preflight 가 button 의 text-align 을 되돌리는가 |
되돌리지 않습니다. 여백·테두리·글꼴·배경은 초기화하지만 정렬은 브라우저 기본값 center 가 남습니다. 버튼 안에서 폭이 내용만큼인 요소는 증상이 드러나지 않고 w-full 인 요소만 가운데로 가므로, 한 버튼 안에서 줄마다 정렬이 갈립니다 |
afterNextRender 가 정적 생성 시점에도 실행되는가 |
실행되지 않습니다. 브라우저 전용이라 그 안에서 화면 구성을 바꾸면 생성된 HTML 에 빠지고 하이드레이션 이후에야 나타납니다. 고정된 자리에 요소가 뒤늦게 끼어드는 형태로 드러나며, 개발 서버에서는 부팅이 빨라 알아채기 어렵습니다 |
| 내용에 따라 변하는 높이에 CSS 전환을 걸 수 있는가 | 걸 수 없습니다. 전환은 계산값이 바뀔 때 발화하는데 자식이 늘고 줄어도 height의 계산값은 auto로 같습니다. interpolate-size: allow-keywords는 0과 auto 사이를 잇는 수단이라 이 경우에 해당하지 않습니다. 높이를 전환하려면 ResizeObserver로 내용을 재서 픽셀로 지정해야 하며, 관찰 대상은 지정 대상이 아니라 그 안의 내용이어야 값이 자기 자신을 따라가지 않습니다 |
8. 규칙의 예외
| 대상 | 완화한 규칙 | 사유 |
|---|---|---|
src/server.ts, src/main.server.ts |
no-console |
Node 진입점은 stdout 이 표준 로깅 채널입니다. 브라우저 콘솔로의 개인정보 유출이라는 규칙의 근거가 적용되지 않습니다 |
src/pages/** |
Steiger excessive-slicing |
슬라이스가 하나뿐인 초기 단계에서는 과분할 지적이 유효하지 않습니다. 슬라이스가 셋 이상이 되면 제거합니다 |
src/shared/api/generated/** |
ESLint 전체 | 생성물에 대한 지적은 고칠 수 없으므로 잡음이 됩니다 |
src/shared/markdown/generated/** |
ESLint 전체 | 위와 같습니다. scripts/build-docs.mjs 의 산출물입니다 |
src/shared/ui/** |
ESLint 선택자 접두사 | helm 은 hlm 접두사가 Spartan 규약입니다. app 으로 바꾸면 재생성 때마다 되돌아옵니다 |
src/shared/ui/** |
ESLint Injector 임포트 제한 |
hlm.ts 의 Injector 는 runInInjectionContext 인자이며 규칙이 막으려는 Injector.get 이 아닙니다. 나머지 제한(폼 수단, 계층 간 상대 경로)은 유지합니다 |
src/shared/ui/** |
ESLint no-input-rename |
aria-label 처럼 표준 속성명을 그대로 받으려면 별칭이 필요합니다 |
src/shared/ui/** |
Steiger public-api, no-reserved-folder-names |
helm 의 폴더 구조를 생성기가 정합니다. 상세는 디자인 시스템과 토큰 1.1절에 있습니다 |
| 전역 | Steiger no-public-api-sidestep |
컴포넌트별 경로를 우회로 오판하며 규칙 단위 예외가 없습니다. ESLint no-restricted-imports 패턴이 대체하므로 슬라이스 내부 파일 임포트는 여전히 차단됩니다 |
예외를 추가할 때는 완화한 규칙의 근거가 왜 그 대상에 적용되지 않는지를 함께 적습니다. 사유 없는 예외는 규칙을 무력화합니다.
9. 미확인 항목
| 항목 | 확인할 내용 | 필요 조건 |
|---|---|---|
| helm 컴포넌트의 오버레이 입력 전달 | 복사된 helm이 positionStrategy를 brain으로 전달하는지. 전달하지 않으면 helm 사본을 고칩니다 |
해당 컴포넌트 추가 후 |
| 캘린더 라벨의 한국어화 | provideBrnCalendarI18n()으로 대체할 항목의 범위 |
날짜 선택기 구현 시 |
@defer와 컨텐츠 프로젝션 |
ng-content 전달 시 제약 |
적응형 컴포넌트 구현 시 |
validateHttp의 요청 취소 |
값이 빠르게 바뀔 때 이전 요청 취소 여부 | 폼 구현 시 |
| 대기 시간 기본값 | --wait-delay 200ms · --wait-min 400ms가 실제 응답 분포에 맞는지 |
실사용 데이터 확보 후 |