# KBX FE QA Hardening v46

## 1. 목적

v46은 새 화면 타입이나 새 UI Framework를 추가하는 버전이 아니다. v45까지의 Theme, State, Safe Preference 기반 위에서 **업무 재개성(work resume)** 과 **T09 오류 복구**를 실제 FE 계약으로 닫는다.

판단 기준은 단순하다.

- 새로고침 후 사용자가 다시 메뉴를 찾아야 하는가?
- 업무 위치를 복원한다고 Dirty Form/Grid 값까지 브라우저 저장소에 넣고 있지는 않은가?
- Home에서 복원된 업무를 키보드만으로 다시 열 수 있는가?
- WMS 초기 조회가 실패했을 때 현장 사용자가 같은 화면에서 다시 시도할 수 있는가?

## 2. QA 비판

### 2.1 Preference는 복원되지만 Workspace는 사라졌다

v45에서는 Theme, 즐겨찾기, 최근 메뉴, 안전한 검색 기본조건은 복원되지만 열린 Workspace Tabs는 메모리에만 있었다. 브라우저 새로고침 후 다시 메뉴를 찾아야 하므로 한국형 ERP의 다중업무 탭 문법에 비해 재개성이 약했다.

반대로 이를 해결한다고 전체 Form/Grid state를 localStorage에 저장하는 것은 더 나쁜 해결이다. Dirty 데이터는 오래된 서버 상태, 개인정보, 주문/재고 업무값을 브라우저 저장소에 남기고 복원 시점의 Domain truth와 충돌할 수 있다.

### 2.2 Home에서 active tab이 없으면 Workspace Tab keyboard entry가 사라졌다

Home에서는 `activeKey=null`이므로 기존 `KbxWorkspaceTabs`의 모든 main tab button이 `tabindex=-1`이었다. 화면에는 업무 탭이 보이지만 Keyboard Tab으로 탭 영역에 들어갈 수 없는 접근성 결함이다.

### 2.3 Navigation Preference의 최초 읽기 경로가 완전히 fail-soft가 아니었다

v45의 화면별 `screenPreferenceStore`는 storage 예외를 방어했지만 `workspaceStore.loadPreference()`의 최초 `localStorage.getItem()`은 try/catch 밖에 있었다. 정책상 Web Storage가 차단된 환경에서 Shell 초기화를 깨뜨릴 가능성이 있었다.

### 2.4 T09는 Error UI는 있었지만 실제 Retry loop가 끊겨 있었다

`KbxWmsMobilePage`는 `KbxTemplateStateBoundary`를 사용하지만 retry event를 상위 업무 Page로 전달하지 않았다. `WMS-PICK-001`도 TanStack Query의 loading/error 상태를 mobile template state에 연결하지 않았다. 따라서 초기 작업 조회 실패 시 공통 오류 표시는 가능해도 Recovery Action이 실제 Query 재조회로 이어지지 않았다.

## 3. v46 구현

### 3.1 Safe Workspace Resume

신규 `workspaceSessionStore.ts`는 `sessionStorage`만 사용한다.

저장:

- ScreenId
- 안전한 pathname
- Pin 여부
- openedAt / lastActivatedAt

저장하지 않음:

- Query
- Hash
- Dirty Form 값
- Dirty Grid 값
- Lookup 입력값
- 업무 DTO
- Permission/Domain state

Dirty 탭은 session snapshot에서 제외하고 `dirtyDiscardedCount`만 제한된 숫자로 기록한다. 이 숫자는 사용자에게 “미저장 편집은 안전상 복원하지 않았다”는 사실을 설명하기 위한 것이며 업무 데이터는 포함하지 않는다.

복원 시 AppFrame은 다시:

1. 현재 permission 확인
2. Screen registry 확인
3. `resolveKbxSafeWorkspacePath()`로 route capability 검증
4. 현재 Screen title 재사용
5. `dirty=false`, `resumed=true`로 복원

한다. 저장된 문자열을 신뢰해 화면을 열지 않는다.

### 3.2 Home/Workspace Resume UX

복원된 업무는 Home `바로 시작`에서 일반 `열림`과 구분해 `복원`으로 표시한다. Workspace Tab에도 `↺`와 접근성 이름 `이전 세션에서 복원됨`을 표시한다.

사용자가 해당 업무를 실제로 열면 `resumed=false`가 되어 복원 표시는 제거된다. 이는 “복원 후보”와 “현재 사용 중인 업무”를 구분한다.

Home에서 active tab이 없어도 첫 Workspace Tab이 roving tabindex `0`이 되도록 수정했다. 따라서 Mouse뿐 아니라 Keyboard Tab → ArrowLeft/Right/Home/End 흐름으로 재개할 수 있다.

### 3.3 Storage fail-soft 통일

Navigation Preference의 초기 `localStorage.getItem()`도 try/catch 내부로 이동했다. Storage 차단은 Preference가 없는 상태로 degrade되며 Home/Shell 기동 자체를 실패시키지 않는다.

### 3.4 T09 WMS Error Recovery

`KbxWmsMobilePage 1.6.0`은 `retry` / `emptyAction`을 상위 Page로 전달하고 모바일 기본 retry label을 `다시 시도`로 제공한다.

`useWmsPicking()`은 TanStack Query 상태를:

- loading
- error
- empty
- ready

로 명시적으로 매핑한다. `WmsPickingPage.vue`는 이를 `contentState`로 전달하고 `[작업 다시 불러오기]`를 `query.refetch()`에 연결한다.

Scanner success, 재고, idempotency 같은 Domain 결과는 여전히 Server authority다. 이번 변경은 실패 복구 UX를 연결한 것이지 Client를 업무 truth로 만든 것이 아니다.

## 4. Theme 적용 원칙

v46은 새로운 Theme를 추가하지 않는다. KBX Business Light/Dark와 Forced Colors 의미 체계를 그대로 사용한다.

`복원`, `미저장`, `Pin`, `Error`는 색만으로 구분하지 않으며 icon/text/aria state를 같이 사용한다. Theme 변경은 Workspace Resume 여부나 Permission, Domain state를 바꾸지 않는다.

## 5. Browser QA

Reference HTML/CSS/JavaScript에서 20개 체크를 실행했다.

핵심 시나리오:

- Dark theme 적용
- clean 품목관리 tab open + pin
- 주문등록 수정 → Dirty
- session snapshot에 clean tab만 존재
- Dirty Form 값 문자열이 snapshot에 없음
- 새 문서에서 clean tab `복원`
- Dirty tab 복원 안 됨
- Home에서 Workspace 첫 tab keyboard entry 가능
- Home에서 미저장 복원 제외 설명
- 복원 업무를 실제 열면 resume marker 제거
- Web Storage 접근 자체가 차단되어도 Home render
- 1280×720 document horizontal overflow 0
- runtime page error 0

실행환경의 direct-origin navigation이 관리자 정책으로 차단되어, 동일 Reference asset을 inline Playwright harness에서 실행하고 Web Storage는 in-memory shim으로 재현했다. 이는 실제 origin/sessionStorage browser E2E와 구분한다.

## 6. QA 자동화 자체의 기술부채 정리

전체 Gate에서 v46 제품 동작이 아니라 `v44|v45` 같은 과거 버전 문자열, 과거 Home purpose 문구, 특정 Component exact version을 source text로 요구하는 legacy validator가 회귀를 만들었다.

이를 버전마다 문자열을 추가하는 방식으로 봉합하지 않고, foundation iteration 최소값, component category/존재, minimum semver 등 **실제 보장하려던 의미 계약**으로 바꿨다. 정적 Gate도 제품과 같은 기술부채 관리 대상이어야 하며, 구현 문구를 동결하는 테스트는 리팩터링을 방해한다.

최종 aggregate는 153 tokens, 20 screens, 84 components, 69 catalog entries, 306 TS/Vue units이며 Design Debt는 `116 <= 131`을 유지한다.

## 7. 남은 병목

다음 우선순위는 새 Template이 아니다.

1. network-enabled 환경에서 실제 Vue/Vite install + build
2. 실제 browser origin에서 sessionStorage Workspace Resume E2E
3. OMS-ORD-001 Search Preference + Workspace Resume 조합
4. OMS-ORD-002 keyboard-only F2 → Detail → F8
5. T02/T03 Permission / Conflict / Integration failure
6. WMS-PICK-001 실제 API 초기 실패 → Retry → Recovery
7. server-side full-query XLSX / Hangfire export

이 항목이 닫히기 전 T10/T11 또는 새로운 Runtime UI Engine을 추가하는 것은 과유불급이다.
