Files
llm-wiki/raw/branch-notes/feature-sample-feature-slice-contract-fixture.md

319 lines
34 KiB
Markdown

---
title: branch / feature-sample-feature-slice-contract-fixture
source_type: branch-note
status: raw
branch: feature-sample-feature-slice-contract-fixture
parent_branch:
related_projects: [ca-skeleton-frontend, ca-skeleton]
governing_docs: [raw/project-notes/ca-skeleton-frontend-operational-contract.md]
tags: [branch, ca-skeleton, frontend, testing, react, clean-architecture]
created: 2026-07-18
target_merge:
status_label: in-progress
id: BR-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-016
kind: project-work-item
project: ca-skeleton-frontend-operational-contract
work_item: WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-016
inherits: [DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-SAMPLE-FIXTURE-001@1]
refines: []
overrides: []
depends_on: [WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-005, WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-006, WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-007, WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-009, WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-010]
contract_packet: 1
contract_packet_sha256: ba88989473c8fb9032f91d0f50b2c7d1dfc9cb05ad0a6d0354e81d318256dee9
imports: [FE-GATE-006@1, FE-GATE-007@1, FE-GATE-008@1, FE-OC-002@1, FE-OC-005@1, FE-OC-007@1, FE-OC-011@1, FE-OC-012@1]
---
# branch: feature-sample-feature-slice-contract-fixture
> Layer: `raw/branch-notes/` — 단일 브랜치의 TODO·결정·진행 기록. 현재는 scaffolding 단계다.
<!-- section-id: branch-parent -->
## 부모 (필수)
- [[raw/project-notes/ca-skeleton-frontend-operational-contract]]
<!-- GENERATED: branch-contract:start -->
<!-- section-id: branch-contract-packet -->
## 브랜치 계약 패킷
- **생성 시 프로젝트 개정**: `1`
- **패킷 스키마**: `contract_packet: 1`
- **완료 조건**: full contract slice와 sample removal smoke test가 통과한다
<!-- section-id: inherited-project-decisions -->
### 상속한 프로젝트 결정
| Decision Ref | Project Summary | Branch Application | Source |
|---|---|---|---|
| `DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-SAMPLE-FIXTURE-001@1` | sample slice는 제거 가능한 contract fixture이며 product import를 금지한다 | Work Item 완료 조건에 적용 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] |
<!-- section-id: branch-local-decisions -->
### 브랜치 지역 결정
> 기존 branch-local 결정은 아래 `## Decision Evidence Map / 결정-근거 매핑`의 D-row가 소유하며 이 packet에서 복제하지 않는다.
| Decision ID | Decision | Relation | Supporting Claims | Status |
|---|---|---|---|---|
<!-- section-id: declared-overrides -->
### 선언한 예외
| Override ID | Overrides | Reason | Approval | Status |
|---|---|---|---|---|
<!-- GENERATED: branch-contract:end -->
<!-- section-id: branch-goal -->
## 목표
이 브랜치는 project-wide contract `FE-OC-024`(sample은 contract fixture이며 production feature가 의존하면 안 됨)를 *되묻지 않고 구현할 수 있는 명세*로 내린다. hub 결정 [[raw/project-notes/ca-skeleton-frontend-operational-contract]] FE-D025(sample slice = 제거 가능한 contract fixture, product import 금지)에 근거해, skeleton이 "새 feature도 같은 architecture·API failure language·runtime validation·async UI·server-state·quality gate를 재사용하는가"를 증명하는 **단일 end-to-end reference vertical**(API → schema → mapper → application → presentation, hub `FE-SC-002`)을 정의한다. 그리고 그 vertical이 언제든 통째로 제거돼도 production build/smoke가 깨지지 않음을 gate `FE-GATE-020`(`pnpm test:sample-removal`)으로 강제한다. 이 vertical은 여러 계약을 end-to-end로 **행사(exercise)** 하지만 각 계약의 메커니즘은 소유하지 않고 owner branch에 위임한다. frontend 구현 repository가 아직 식별되지 않았으므로 본 노트의 모든 구현 항목은 `planned` 등급이다.
- 이슈: 없음 (repository 미생성)
- PR: 없음
<!-- section-id: branch-scope -->
## 범위
### 포함 범위
- sample contract fixture slice의 **존재·격리·제거 가능성**`FE-OC-024`, `FE-GATE-020`.
- `FE-REG-API`의 2개 sample operation 행 **소유(등록 정의)**`LIST_SAMPLE_RESOURCES`, `CREATE_SAMPLE_RESOURCE` (hub §5.3).
- API → schema → mapper → application → presentation을 관통하는 **end-to-end reference vertical wiring**`FE-SC-002` (hub §20 measurable completion "full contract slice").
- **sample removal smoke test/gate** — `pnpm test:sample-removal``artifacts/tests/sample-removal.xml` (hub §14.3, `FE-GATE-020`).
- product/production 코드의 **sample import 금지 invariant** — FE-D025.
### 제외 범위
> 의도적으로 제외. 아래는 다른 owner branch가 소유하며 sample vertical은 이들을 *소비/행사* 만 한다 (CLAUDE.md §15.5 R3, OUT_OF_BRANCH_SCOPE 방지).
- shared HTTP client 내부(timeout/abort/retry/envelope parsing, idempotency key 생성) → [[raw/branch-notes/feature-api-client-response-envelope-contract]] `FE-OC-006`·`FE-OC-009` 소유.
- runtime schema 작성·검증 엔진 (Zod schema shape/validation) → [[raw/branch-notes/feature-runtime-schema-validation-contract]] `FE-OC-007` 소유.
- boundary mapper 메커니즘 (2-stage 배치 = stage 7 `DTO→application model`(`FLOW-FE-RESP-007@1`) 이후 application 이 view-model 로 투영, raw DTO 직접 사용 금지 규칙, mapper negative fixture, mapper 모듈 명명) → [[raw/branch-notes/feature-boundary-mapper-viewmodel-contract]] 소유 (`FE-OC-007`·`FE-OC-024` 기여 branch). 본 branch는 그 mapper가 산출할 sample view-model *필드 목록* 만 확정한다.
- styling 시연(디자인 token·arbitrary value policy·async 시각 primitive)의 내용과 화면 구성 → [[raw/branch-notes/feature-tailwind-design-token-styling-contract]] 소유 (`FE-OC-024` 협업 branch). **본 서브트리의 co-tenant 기여자** — 그 branch가 `src/sample/contract-fixture/` 안에 styling 시연 UI 를 놓는다(mapper branch와 동일 패턴). 본 branch는 그 시연부를 §1 제거 단위 *안에* 수용할 뿐 token 어휘·시각 primitive 를 정의하지 않는다.
- error 정규화 taxonomy/matrix → [[raw/branch-notes/feature-frontend-error-classification-boundary-contract]] `FE-OC-008` 소유.
- route registry schema·guard·param validation·redirect-loop 방지 → [[raw/branch-notes/feature-routing-navigation-guard-contract]] `FE-OC-005` 소유.
- `QueryCachePort` 정의·TanStack adapter·invalidation·stale 정책 → [[raw/branch-notes/feature-server-state-caching-contract]] `FE-OC-012` 소유.
- async surface state model 정의 → [[raw/branch-notes/feature-async-ui-state-contract]] `FE-OC-011` 소유.
- CI gate/fixture/artifact taxonomy → [[raw/branch-notes/feature-frontend-test-taxonomy-contract]] `FE-OC-020` 소유.
- web-vitals budget/report (sample list는 측정 fixture일 뿐) → [[raw/branch-notes/feature-web-vitals-performance-budget-contract]] `FE-OC-021` 소유.
- 정적 import 금지 규칙 *authoring* (dependency-cruiser/ESLint rule) → [[raw/branch-notes/feature-frontend-architecture-enforcement-lint-contract]] `FE-OC-002` 소유.
- token 발급/저장/refresh lifecycle → external auth owner / [[raw/branch-notes/feature-frontend-auth-session-integration-contract]] `FE-OC-010` 소유.
- domain/business rule, product analytics taxonomy, branding/copy (hub §0.5 out of scope).
## 근거 (필수, 최소 1개+)
| Source | 정당화하는 결정 |
|---|---|
| [[raw/official-docs/react-ui-library-official]] REACT-UI-C1 | D5 — sample presentation을 React 컴포넌트로 구성 (project decision FE-D004 consume) |
| [[raw/project-notes/ca-skeleton-frontend-operational-contract]] FE-D025 · FE-OC-024 | D1·D6 — sample = 제거 가능 fixture, product import 금지 |
| [[raw/project-notes/ca-skeleton-frontend-operational-contract]] FE-SC-002 · §20 | D2 — API→schema→mapper→application→presentation full contract slice reference vertical |
| [[raw/project-notes/ca-skeleton-frontend-operational-contract]] FE-GATE-020 · §14 | D3 — sample removal smoke gate (`pnpm test:sample-removal`) |
| [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §5 | D4 — sample API operation/route registry 행(`FE-REG-API`/`FE-REG-ROUTE`) |
## TODO
- [ ] `src/sample/contract-fixture/` 서브트리 생성 + 모든 sample 코드를 이 한 디렉터리로 격리 — 등급: `planned`
- [ ] `pnpm test:sample-removal` smoke + `artifacts/tests/sample-removal.xml` 산출 — 등급: `planned`
- [ ] `FE-REG-API` sample operation 2행(`LIST_SAMPLE_RESOURCES`/`CREATE_SAMPLE_RESOURCE`) + schema 참조 wiring — 등급: `planned`
- [ ] API→schema→mapper→application→presentation 관통 vertical 구현 (contributing 계약 owner 완료 후) — 등급: `planned`
- [ ] §4.1 sample view-model 필드 목록을 backend payload 계약 확정 시 재검토 (현재 임의 채택) — 등급: `needs-confirmation`
- [ ] product code의 sample import 금지 정적 규칙 연동 (architecture-enforcement branch 위임) — 등급: `needs-confirmation`
## 진행 중 메모
- 없음 — repository 미생성, 모든 항목 `planned`.
## 결정 사항
> 아래는 Decision Evidence Map의 prose 미러. 각 근거는 hub 결정 register 또는 archived official-doc.
- 2026-07-19: sample slice는 **제거 가능한 contract fixture**이며 production/product 코드가 import하지 못한다 (D1). 이유: skeleton의 계약 준수를 증명할 reference가 필요하되 제품 코드가 그것에 결합되면 안 됨. 검토한 대안: fixture 없이 각 계약을 unit test로만 검증 → 계약 간 wiring 회귀를 못 잡음. 근거: [[raw/project-notes/ca-skeleton-frontend-operational-contract]] FE-D025 · FE-OC-024.
- 2026-07-19: sample slice는 **API → schema → mapper → application → presentation을 관통하는 단일 end-to-end reference vertical**이다 (D2). 이유: 계약 상호작용을 통합 fixture 1개로 증명. 대안: 통합 vertical 없이 계약별 unit fixture만. 근거: [[raw/project-notes/ca-skeleton-frontend-operational-contract]] FE-SC-002 · §20.
- 2026-07-19: sample **removal은 전용 smoke gate로 강제**한다 — `src/sample/` 제거 후 production build/smoke green + product import 0 (D3). 이유: removability를 회귀 방지 gate로. 대안: 수동 리뷰. 근거: [[raw/project-notes/ca-skeleton-frontend-operational-contract]] FE-GATE-020 · §14.
- 2026-07-19: sample은 **등록된 registry 행만 사용**한다 — `FE-REG-API`의 2 operation, `FE-REG-ROUTE`의 sample 행; call site raw fetch/route literal 금지 (D4). 이유: fixture가 "좋은 예시"여야 함. 대안: ad-hoc token. 근거: [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §5.
- 2026-07-19: sample presentation은 **React 컴포넌트로 구성**한다 (D5, FE-D004 consume). 이유: React가 project UI 기본. 대안: 다른 framework/native. 근거: [[raw/official-docs/react-ui-library-official]] REACT-UI-C1.
- 2026-07-19: sample scope는 **fixture wiring으로 한정** — domain/business rule·product analytics 도입 금지, 어떤 product feature의 의존 대상도 되지 않음 (D6). 근거: [[raw/project-notes/ca-skeleton-frontend-operational-contract]] FE-D025 · FE-OC-024.
## 결정-근거 매핑
| Decision ID | Decision | 선택 조건 (언제 이 결정 / 언제 대안) | Supporting Claims | Evidence Strength | Open Risk |
|---|---|---|---|---|---|
| D1 | sample slice = 제거 가능한 contract fixture, product/production 코드가 import 금지 (`FE-OC-024`) | skeleton이 "새 feature도 같은 계약을 따르는가"를 증명할 reference vertical이 필요한 동안 유지; 대안(fixture 삭제)은 fixture 없이 동일 gate coverage를 증명할 수 있을 때(hub revisit trigger) | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] FE-D025, FE-OC-024 | `project-decision` | fixture 제거 시 계약들이 end-to-end로 함께 동작하는지 검증할 통합 표면 상실; revisit trigger 충족 여부 미검증 |
| D2 | sample = API→schema→mapper→application→presentation을 관통하는 단일 end-to-end reference vertical; contributing 계약(`FE-OC-005/006/007/008/011/012/020/021`)을 행사하나 메커니즘은 미소유 | 통합 fixture 1개로 계약 상호작용을 증명하는 것이 계약별 unit test만보다 나을 때; 대안은 통합 vertical 없이 unit fixture만(계약 간 wiring 회귀 미포착) | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] FE-SC-002, §20 | `project-decision` | contributing 계약 owner branch 미완이면 vertical이 실제로 관통 못 함(dependency). vertical의 **mapper stage 는 본 branch 미소유** — [[raw/branch-notes/feature-boundary-mapper-viewmodel-contract]] 이 소유하고 본 branch 는 sample view-model 필드 목록만 확정(§구현 가이드 §4) |
| D3 | sample removal을 전용 smoke gate로 강제 — `src/sample/` 제거 후 production build/smoke green + 잔존 product import 0 (`FE-GATE-020`) | removability를 자동 회귀 gate로 둘 때; 대안은 수동 코드리뷰(회귀 방지 불가) | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] FE-GATE-020, §14 | `project-decision` | import 검출 메커니즘(정적 스캔 vs build 실패)이 hub 미명시 → §구현 가이드 UNSUPPORTED_IMPL_DECISION |
| D4 | sample은 등록된 registry 행만 사용 — `FE-REG-API` 2 operation 소유 + `FE-REG-ROUTE` sample 행 consume; call site raw fetch/route literal 금지 | fixture가 registry-first "좋은 예시"여야 할 때(항상); 대안은 ad-hoc token(fixture 목적에 반함) → N/A | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §5 | `project-decision` | request/response schema shape(`SampleResource*`) 이름만 있고 필드 미정 → schema branch 위임 |
| D5 | sample presentation을 React 컴포넌트로 구성 (project decision FE-D004 consume) | React가 project UI 기본인 동안 유지; 대안(native/custom-element/다른 framework)은 FE-D004 revisit trigger 충족 시 | [[raw/official-docs/react-ui-library-official]] REACT-UI-C1, [[raw/project-notes/ca-skeleton-frontend-operational-contract]] FE-D004 | `official-doc` | 컴포넌트가 async surface 4상태(§9.1)를 완전히 표현해야 하나 그 matrix는 async-ui branch 소유 → 위임 |
| D6 | sample scope는 fixture wiring으로 한정 — domain/business rule·product analytics 도입 금지, 어떤 product feature도 sample에 의존 금지 | invariant(분기 없음) → N/A; product feature의 sample import = build/gate 실패로 고정 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] FE-D025, FE-OC-024 | `project-decision` | contributing 계약 owner가 계약을 바꾸면 sample vertical 갱신 필요(delegated dependency) |
## 구현 가이드
> 모두 `planned` — frontend 구현 repository가 아직 없다. 경로는 hub §4.6 Planned directory blueprint + §5 registry owner map에서 도출한 grounded anchor이나 repository 생성 시 변경 가능.
### 1. Sample 서브트리 & removability 경계
> **Trace**: D1 + D3 + `FE-OC-024`; hub §4.6 blueprint(`src/sample/contract-fixture/`).
>
> - **UNSUPPORTED_IMPL_DECISION**: sample import 금지의 *검출 메커니즘*(dependency-graph inbound-edge 규칙 vs ESLint no-restricted-imports vs removal smoke의 build 실패) 은 hub가 gate(`FE-GATE-020`)와 command만 주고 미명시. Trade-off: 정적 dependency 규칙(외부→`src/sample/**` inbound import 0) + removal smoke의 이중 방어를 권고하되, 규칙 *authoring* 은 [[raw/branch-notes/feature-frontend-architecture-enforcement-lint-contract]] `FE-OC-002` 로 위임(R3).
| 항목 | Planned 값 | 근거 |
|---|---|---|
| sample 코드 위치 | `src/sample/contract-fixture/` (단일 서브트리) | hub §4.6 |
| removability 규칙 | `src/sample/` 외부의 어떤 모듈도 `src/sample/`를 import 금지 | D1 (FE-D025) |
| 제거 단위 | 서브트리 1개 삭제 = feature 제거 (product 코드 무변경) | D1·D3 |
### 2. Sample removal smoke test / gate
> **Trace**: D3 + `FE-GATE-020` + hub §14.3 `pnpm test:sample-removal`.
>
> - **UNSUPPORTED_IMPL_DECISION**: 제거 방식(CI에서 ephemeral copy 후 `rm -rf` vs build flag로 dir 제외 vs git worktree)과 "smoke" 판정 assert 목록이 hub 미명시. Trade-off: source 비파괴적인 ephemeral copy + `rm` 을 권고하고, smoke는 최소 "`APP_HOME` shell mount 성공 + `SAMPLE_RESOURCE_LIST` route 부재 + build exit 0" 를 assert.
> - **UNSUPPORTED_IMPL_DECISION**: step d의 *ID-residue 검출 메커니즘* — §1의 label은 모듈 *import* 검출만 다루고, 제거 후 남은 **문자열 ID 잔재**(`LIST_SAMPLE_RESOURCES`/`CREATE_SAMPLE_RESOURCE` operationId, `SAMPLE_RESOURCE_LIST` routeId, sample query-key)의 검출 방식은 hub 미명시. Trade-off: 남은 서브트리 전체에 대한 **registry ID token grep(고정 문자열 exact-match, 검사 대상 ID 목록은 `FE-REG-API`/`FE-REG-ROUTE`의 sample owner 행에서 생성)** 을 채택 — dependency-graph 스캔은 문자열 리터럴을 못 잡고 build 실패는 dead 상수를 못 잡기 때문. 검사 범위는 `src/` **와 `tests/`** 둘 다로 둔다. 비용(false positive): grep 은 주석/문서의 우연한 언급도 잡는다. 더 중요한 것은 **false negative** 쪽인데, `src/` 만 스캔하면 delegate branch 가 서브트리 *밖에* 놓은 sample 참조를 놓친다 — 예: [[raw/branch-notes/feature-boundary-mapper-viewmodel-contract]] 의 mapper negative fixture 는 `tests/unit/` 경로를 planned 로 잡고 있어, sample 제거 후 `pnpm build` 는 green 인데 test suite 가 깨지는 상태를 gate 가 통과시킬 수 있다. `tests/` 포함으로 이 구멍을 막는다.
> - 그럼에도 `src/`·`tests/` 밖(설정 파일, CI 워크플로, 문서)의 sample 참조는 본 gate 가 검출하지 않는다. 그런 참조를 만든 **delegate branch 가 자기 몫의 제거 책임을 진다** — 본 branch 는 제거 *단위*(§1 서브트리)와 gate 를 소유하고, 각 co-tenant 기여자([[raw/branch-notes/feature-boundary-mapper-viewmodel-contract]], [[raw/branch-notes/feature-tailwind-design-token-styling-contract]])는 자신이 서브트리 밖에 남긴 참조의 제거를 소유한다.
| 단계 | Planned 동작 | 기대 결과 |
|---|---|---|
| a | `src/sample/` 서브트리를 전용 fixture/CI job에서 제거 | — |
| b | `pnpm build` | exit 0 + manifest 존재 |
| c | production smoke (app boot, home shell 렌더) | pass, sample route/operation 미참조 |
| d | 잔존 sample operationId/routeId/query-key 참조 검출 — `src/` **+ `tests/`** 대상 registry ID token grep(위 UNSUPPORTED label) | 0건 |
| artifact | `artifacts/tests/sample-removal.xml` | hub §14.3 |
### 3. Sample API operation & schema wiring
> **Trace**: D4 + hub §5.3(`FE-REG-API` 소유 행) + `FE-OC-006`. 아래 2행은 hub §5.3에서 owner=본 branch 로 지정된 grounded registry 행이다.
>
> - **UNSUPPORTED_IMPL_DECISION**: request/response schema *shape*(`SampleResourceListQuery`/`SampleResourceListPayload`/`CreateSampleResourceCommand`/`SampleResourcePayload`)은 hub가 이름만 준다. Trade-off: fixture 안에 최소 placeholder shape을 정의하되 Zod schema *작성·검증 엔진* 은 [[raw/branch-notes/feature-runtime-schema-validation-contract]] `FE-OC-007` 로 위임. keyed mutation의 idempotency key 생성 계약은 [[raw/branch-notes/feature-api-client-response-envelope-contract]] `FE-OC-009` 소유.
| operationId | method | path | auth | timeoutMs | idempotency | requestSchema | responseSchema |
|---|---|---|---|---|---|---|---|
| `LIST_SAMPLE_RESOURCES` | `GET` | `/api/sample/resources` | `external-session` | `10000` | `safe` | `SampleResourceListQuery` | `SampleResourceListPayload` |
| `CREATE_SAMPLE_RESOURCE` | `POST` | `/api/sample/resources` | `external-session` | `10000` | `keyed` | `CreateSampleResourceCommand` | `SampleResourcePayload` |
- sample query/command use case는 위 operation을 **shared client + application output port**(`ResourceQueryPort`/`ResourceCommandPort`, hub §4.4)로만 호출한다. shared client 메커니즘은 [[raw/branch-notes/feature-api-client-response-envelope-contract]] `FE-OC-006` 위임.
### 4. Sample vertical wiring (domain → application → presentation → routes)
> **Trace**: D2 + D5 + `FE-SC-002`; hub §4.2 component responsibility, §4.6 layer dirs, §9.1 async states.
>
> - **UNSUPPORTED_IMPL_DECISION**: 컴포넌트/파일 이름(예: `SampleResourceListPage.jsx`)과 domain 모델 유무는 hub 미명시. Trade-off: 이름은 operationId를 미러(`SampleResourceListPage`), domain은 fixture이므로 비워두거나 trivial `SampleResource` value만 — layering 규칙 자체는 [[raw/branch-notes/feature-frontend-clean-architecture-layering-contract]] `FE-OC-002` 소유.
| 레이어 | sample이 제공(in-scope) | 위임(다른 owner) |
|---|---|---|
| domain | (선택) trivial `SampleResource` value 또는 없음 | layering 규칙 → `FE-OC-002` |
| mapper (boundary) | sample view-model의 **구체 필드 목록**만 확정 — §4.1 표 (`SampleResourceListPayload`/`SampleResourcePayload` → sample view-model) | mapper 메커니즘 자체(2-stage 배치, raw DTO 직접 사용 금지 규칙, negative fixture, 명명 convention) → [[raw/branch-notes/feature-boundary-mapper-viewmodel-contract]] (`FE-OC-024` 기여분 · `FE-OC-007`) |
| application | sample query/command use case + view-model **투영**(mapper 계약 준수), `QueryCachePort` 소비 | view-model 계약 메커니즘 → 위 mapper 행; port 정의 → `FE-OC-002`/`FE-OC-012` |
| presentation | React sample page/component, async 4상태 렌더(§9.1) | async state model → `FE-OC-011` |
| adapters | 기존 http/query-cache adapter *재사용* (신규 adapter 없음) | adapter 구현 → owner branch |
| routes | `APP_HOME`(sample shell)·`SAMPLE_RESOURCE_LIST`(fixture)의 route element/loading/error surface 내용 | route registry/guard → `FE-OC-005` |
- vertical의 **mapper stage는 본 branch가 소유하지 않는다** — 메커니즘(adapter→validated model→application view-model 2-stage 배치, "raw DTO 직접 사용 금지" 규칙, mapper negative fixture)은 [[raw/branch-notes/feature-boundary-mapper-viewmodel-contract]] 소유이고, 그 branch의 mapper 시연부가 본 sample 서브트리 *안에* 놓인다(양방향 기여). 본 branch는 그 계약을 **소비**하며 그 mapper가 산출할 sample view-model의 *필드 목록* 만 확정한다(해당 branch가 명시적으로 `FE-OC-024` owner 에게 위임한 부분 — §4.1). 따라서 위 표 `application` 행의 "view-model"은 *계약 소유* 가 아니라 *투영 수행* 을 뜻하고, `adapters` 행의 "신규 adapter 없음"은 mapper 모듈이 기존 http adapter 재사용 위에 그 branch 몫으로 추가된다는 뜻이다.
- `APP_HOME`(`/`, public) 과 `SAMPLE_RESOURCE_LIST`(`/sample/resources`, integration-defined)는 hub §5.2 등록 행이다. 본 branch는 그 route의 *content* 만 제공하고 registry schema·guard·redirect-loop 방지는 [[raw/branch-notes/feature-routing-navigation-guard-contract]] `FE-OC-005` 위임.
#### 4.1 Sample view-model 필드 목록 (본 branch 단독 소유)
> **Trace**: D2 + D5 → hub §5.3(payload schema 이름), §9.1(async 4상태); [[raw/branch-notes/feature-boundary-mapper-viewmodel-contract]] 이 `FE-OC-024` owner 에게 명시 위임한 항목(그 노트 §3 OUT_OF_BRANCH_SCOPE). 그 branch 의 mapper 가 *산출할* 결과물의 shape 을 본 branch 가 확정한다 — mapper 메커니즘(2-stage 배치·total function·negative fixture)은 여전히 그 branch 소유.
>
> - **UNSUPPORTED_IMPL_DECISION**: 아래 **필드 이름·개수·타입은 hub 미명시**다. hub §5.3 은 payload schema 의 *이름*(`SampleResourceListPayload`/`SampleResourcePayload`)만 주고 필드를 열거하지 않으며, backend API 도 아직 없다. Trade-off: fixture 의 목적은 *도메인 표현*이 아니라 *계약 시연*이므로 **§9.1 4상태를 렌더하는 데 필요한 최소 필드만** 임의 채택했다 — 식별자 1개(list key·mutation 대상), 표시 문자열 1개(도메인 의미 도입 금지 D6), 포맷 완료된 시각 1개(포맷팅이 presentation 이 아닌 view-model 책임임을 시연), 그리고 `success` 와 `empty` 를 presentation 이 재계산 없이 구분할 파생 flag 1개. backend 계약이 확정되면 이 표가 1차 갱신 대상이다.
| view-model | 필드 | 타입 | 왜 이 필드인가 (시연 목적) |
|---|---|---|---|
| `SampleResourceListViewModel` (← `SampleResourceListPayload`) | `items` | `SampleResourceItemViewModel[]` | `success` 상태의 render 입력(§9.1) |
| | `isEmpty` | `boolean` | `success` vs `empty` 를 presentation 이 재계산 없이 분기(§9.1 "loading boolean 하나로 병합 금지" 정합). `items.length === 0` 의 파생값 |
| `SampleResourceItemViewModel` | `id` | `string` | list key + `CREATE_SAMPLE_RESOURCE` 후 invalidation 대상 식별 |
| | `label` | `string` | 표시 전용 문자열. 도메인 의미 없음(D6 — fixture 는 business rule 도입 금지) |
| | `updatedAtText` | `string` | **포맷 완료된** 표시 문자열. `Date`/epoch 를 넘기지 않아 "포맷팅은 view-model 책임, presentation 은 render 만" 을 시연 |
| `SampleResourceViewModel` (← `SampleResourcePayload`) | = `SampleResourceItemViewModel` 과 동일 shape | — | `CREATE_SAMPLE_RESOURCE` 성공 결과를 목록 항목과 같은 shape 으로 투영 → mutation 후 캐시 갱신 시 두 번째 매핑 규칙 불필요 |
- 위 view-model 은 raw HTTP status·backend error code·DTO 필드명을 **그대로 노출하지 않는다**(mapper branch D3 계약 준수). optional 필드 부재는 throw 가 아니라 안전 default/absent 로 표기한다.
- `SampleResourceListQuery`/`CreateSampleResourceCommand` 는 view-model 이 아니라 *request* schema 이므로 본 표 밖이다 — 그 shape 은 §3 의 UNSUPPORTED_IMPL_DECISION 이 다룬다.
## 엣지·실패·의존
- **실패·엣지 경로**:
- sample removal이 production build를 깬다 → product 코드가 sample에 의존한다는 신호 → `FE-GATE-020` 실패, merge/release 차단 (D3).
- sample list read 실패(네트워크/schema/error) → 정규화된 frontend error kind로 표시되어야 하나, 정규화 자체는 error-classification 소유; sample은 그 결과를 **렌더만** 한다.
- empty result → §9.1 `empty` 상태(빈 사유 + primary action) 표현 — 상태 모델은 async-ui 소유.
- `CREATE_SAMPLE_RESOURCE`(keyed mutation) 재시도 → stable idempotency key + backend replay contract 없으면 replay 금지(hub §8.5) — 규칙은 api-client 소유.
- runtime config/boot 실패 시 sample route는 mount되지 않음(hub §4.5 boot 2~4단계 실패 → boot error shell) — boot는 env/config branch 소유.
- **다른 계약 의존** (sibling branch consume; 계약 변경 시 sample vertical 갱신 필요):
- [[raw/branch-notes/feature-api-client-response-envelope-contract]] — `FE-OC-006`·`FE-OC-009` shared client + retry/timeout/idempotency consume.
- [[raw/branch-notes/feature-runtime-schema-validation-contract]] — `FE-OC-007` boundary schema validation consume.
- [[raw/branch-notes/feature-boundary-mapper-viewmodel-contract]] — vertical의 mapper stage owner. "raw DTO 직접 사용 금지 → mapper가 view-model 생산" 계약을 consume 하고, 그 branch의 mapper 시연부·negative fixture 를 본 sample 서브트리 안에 수용한다 (`FE-OC-007`·`FE-OC-024` 교집합). 계약 변경 시 sample view-model 필드 목록 갱신 필요.
- [[raw/branch-notes/feature-tailwind-design-token-styling-contract]] — **서브트리 co-tenant 기여자**(mapper branch와 동일 패턴). 그 branch가 `src/sample/contract-fixture/` 안에 token·async 시각 primitive 시연 UI 를 놓는다(그 노트 §5 "sample UI fixture — 협업 `FE-OC-024`"). 본 branch는 그 파일들을 §1 **제거 단위 안에** 수용하며, 따라서 §2 removal smoke 는 그 시연부까지 함께 제거된 상태를 검증한다. token 어휘·화면 구성은 그 branch 소유.
- [[raw/branch-notes/feature-frontend-storage-registry-contract]] — `FE-OC-013` **storage key 계약을 fixture 로 사용**(그 노트가 본 branch 를 dependency 로 선언한 단방향 관계의 반대편 기록). sample slice 가 storage 를 쓸지 여부는 본 branch 결정이며 현재 **미확정** — §9.1 4상태 시연에 storage 가 필수는 아니므로 기본 입장은 "sample 은 storage 를 쓰지 않음"이고, 쓰기로 하면 namespace/version/classification 규약은 그 branch 소유다. repository 생성 시 확정.
- [[raw/branch-notes/feature-frontend-auth-session-integration-contract]] — `FE-OC-010` **runtime session state consume**. §3의 두 sample operation이 모두 `auth`=`external-session` 이므로 request 전 `AuthSessionPort.attach(request)` 와 unauthenticated transition 통지를 그 branch에서 공급받는다. Out of scope의 *token lifecycle* 위임과는 별개 관심사(그쪽은 발급/저장/refresh, 이쪽은 런타임 세션 소비).
- [[raw/branch-notes/feature-frontend-error-classification-boundary-contract]] — `FE-OC-008` normalized error kind consume.
- [[raw/branch-notes/feature-routing-navigation-guard-contract]] — `FE-OC-005` route registry/guard consume.
- [[raw/branch-notes/feature-server-state-caching-contract]] — `FE-OC-012` `QueryCachePort`/invalidation consume.
- [[raw/branch-notes/feature-async-ui-state-contract]] — `FE-OC-011` async surface state model consume.
- [[raw/branch-notes/feature-frontend-test-taxonomy-contract]] — `FE-OC-020` gate/fixture/artifact taxonomy consume.
- [[raw/branch-notes/feature-web-vitals-performance-budget-contract]] — `FE-OC-021` (sample list가 lab/field 측정 fixture).
- [[raw/branch-notes/feature-accessibility-baseline-contract]] — (advisory) `FE-GATE-009`(accessibility)가 Covered 계약에 `FE-OC-024` 를 포함하므로 axe/keyboard 검사가 사실상 sample route 를 대상으로 돈다. a11y 기준·증거는 그 branch 소유이고 hub §20이 본 branch 에 배정하지 않았다 — 발견성 목적의 포인터일 뿐 in-scope 아님.
## 검증해야 할 주장
| Claim | Why uncertain | How to verify | Status |
|---|---|---|---|
| sample subtree 제거 후 production build/smoke가 통과한다 | 코드/CI 없음; product import 부재가 미검증 | `pnpm test:sample-removal` (§14.3) → `artifacts/tests/sample-removal.xml` exit 0 (`FE-GATE-020`) | `needs-confirmation` |
| sample vertical이 API→schema→mapper→application→presentation을 실제로 관통한다 | contributing 계약 owner branch 미완; wiring 미구현 | integration test(MSW) + e2e sample critical read/write (`FE-GATE-007`/`FE-GATE-008`) | `needs-confirmation` |
| 어떤 product feature도 sample을 import하지 않는다 | 정적 검출 메커니즘 미정(UNSUPPORTED_IMPL) | dependency-graph 규칙(architecture-enforcement 위임) + removal smoke | `needs-confirmation` |
| sample list가 async surface 4상태(loading/success/empty/terminal-error)를 표현한다 | async 상태 matrix는 async-ui branch 소유, 미구현 | component state matrix test (`FE-GATE-006`, §9.1) | `needs-confirmation` |
| §4.1의 sample view-model 필드 목록이 §9.1 4상태 렌더에 충분하다 | 필드가 hub 미명시 상태에서 임의 채택됨(UNSUPPORTED_IMPL_DECISION); backend payload 계약 미존재 | mapper 단위 테스트(payload→view-model 투영) + component state matrix test 로 4상태가 이 필드만으로 렌더되는지 확인; backend 계약 확정 시 표 갱신 | `needs-confirmation` |
| React 컴포넌트 구성이 sample presentation에 충분하다 | REACT-UI-C1은 컴포넌트 모델 *존재* 만 증명, 프로젝트 적용 보장 아님 | component test로 sample page 렌더 확인 | `needs-confirmation` |
## 관심사 커버리지 (coverage-auditor 자동 생성 — 있을 때)
| 관심사 | 상태 | owner | 심각도 | 근거 |
|---|---|---|---|---|
## 마주친 문제
- 없음 — repository 미생성 단계.
## 묶음 (이 branch에서 파생된 자료)
<!-- GENERATED: project-contract-imports:start -->
## 가져온 프로젝트 계약
| Ref | Owner | 요약 | Branch 적용 |
|---|---|---|---|
| `FE-GATE-006@1` | [[raw/branch-notes/feature-frontend-test-taxonomy-contract]] | component 레벨이 실패하면 merge 를 MUST 차단 | import 참조로 적용 |
| `FE-GATE-007@1` | [[raw/branch-notes/feature-frontend-test-taxonomy-contract]] | MSW 기반 integration 매트릭스가 미충족이면 merge 를 MUST 차단 | import 참조로 적용 |
| `FE-GATE-008@1` | [[raw/branch-notes/feature-frontend-test-taxonomy-contract]] | critical e2e 시나리오가 실패하면 merge·release 를 MUST 차단 | import 참조로 적용 |
| `FE-OC-002@1` | [[raw/branch-notes/feature-frontend-clean-architecture-layering-contract]] | `domain <- application <- presentation` 의존 방향과 application-owned output port를 MUST 지킴 | import 참조로 적용 |
| `FE-OC-005@1` | [[raw/branch-notes/feature-routing-navigation-guard-contract]] | route ID/path/params/access/loading/error owner는 route registry 하나여야 함 | import 참조로 적용 |
| `FE-OC-007@1` | [[raw/branch-notes/feature-runtime-schema-validation-contract]] | JSON envelope와 payload는 boundary에서 runtime schema를 MUST 통과 | import 참조로 적용 |
| `FE-OC-011@1` | [[raw/branch-notes/feature-async-ui-state-contract]] | async surface는 initial-loading, success, empty, terminal-error를 MUST 표현 | import 참조로 적용 |
| `FE-OC-012@1` | [[raw/branch-notes/feature-server-state-caching-contract]] | query key와 invalidation은 registry factory만 MUST 사용 | import 참조로 적용 |
<!-- GENERATED: project-contract-imports:end -->
### Sub-branches (세부 작업)
- 없음 — scaffolding 단계
### 오류 기록 (이 branch 작업 중 발생)
- 없음 — scaffolding 단계
### 면접 준비 (이 작업에서 나올 수 있는 면접 질문)
- 없음 — scaffolding 단계
### 강의 (이 작업을 위해 학습한 강의)
- 없음 — scaffolding 단계
### job-posting tie-ins (이 작업에서 파생된 글감)
- 없음 — scaffolding 단계
## 관련 일일 노트
- 없음 — scaffolding 단계
## 완료 후 정리
- PR 링크: 없음 (repository 미생성)
- 리뷰 메모: 없음
- 머지 결과 / 배포 환경: 없음 — 모든 항목 `planned`
- **wiki 추출 대상**: 없음 — verified 항목 없음
- **추출하지 않을 항목**: 전체 (`planned` / `needs-confirmation`)