## 2026-06-03 — 세션 멀티 윈도우(별도 창) 상태 표시 + 중앙으로 불러오기

### 배경

세션 윈도우를 별도 OS 창(popout)으로 빼내면 메인 앱에는 아무 표시도, 다시 불러올 경로도 남지 않았다 — 복귀하려면 팝아웃 창 자체의 "Re-dock" 버튼을 찾아 눌러야 했고, 팝아웃이 다른 창 뒤에 가려지면 사실상 길이 끊겼다. 터미널 목록에서 어떤 세션이 별도 창에 떠 있는지조차 알 수 없었다.

### 주요 변경

#### 1. 터미널 목록의 창 상태 배지 + 복귀 동작 (`8d3ede0`)

- **클라이언트**: 각 세션 행에 현재 창 위치 배지 — **열림**(플로팅) / **최소화** / **별도 창**(팝아웃). 별도 창은 보라색 칩 + `ExternalLink` 아이콘으로 구분.
- 별도 창 상태 행에는 **중앙으로 불러오기** 버튼이 생기고, 행 클릭으로도 복귀. 복귀는 **re-dock** — 별도 OS 창을 닫고 메인 창 안 플로팅 창으로 복원(세션·출력 유지). 팝아웃 무응답 시 1.5s 후 강제 회수 폴백.

#### 2. 하단 독에 별도-창 칩 (`8d3ede0`)

- 기존엔 **최소화된 창만** 하단 독(`GlobalSessionDockTray`)에 떴는데, 이제 **별도 창(팝아웃)도** 칩으로 표시(보라 테두리 + 아이콘). 칩 클릭 → 중앙으로 불러오기(같은 프로젝트 즉시, 다른 프로젝트는 이동 후 복귀).
- 별도 창 그룹을 독에서 닫으면 팝아웃 OS 창도 함께 종료(`group-reclaimed`)해 orphan 방지.

#### 3. 버스 프로토콜 `group-recall` (`8d3ede0`)

- `popoutBus`에 `group-recall`(메인 → 팝아웃) 메시지 추가. `PopoutPage`가 이를 받으면 자체 Re-dock 버튼과 동일하게 동작 — 최신 그룹 상태를 `group-return`으로 반환 후 창을 닫는다.
- 호스트(`SessionWindowsHost`): `WindowState` 타입 + `useSessionWindowStates` 컨텍스트(목록 실시간 반영), `recallPopout` API, same/cross-project 복귀 경로(`session-windows:recall` 이벤트 + `pendingSessionRecall` 핸드오프).

### 수정된 주요 파일

| 파일 | 변경 내용 |
|------|----------|
| `src/client/src/components/SessionWindowsHost.tsx` | `WindowState`/`useSessionWindowStates` 컨텍스트, `recallPopout`·`recallGroup`, recall 이벤트/pending 처리, 닫기 시 팝아웃 회수 |
| `src/client/src/components/SessionList.tsx` | 행별 창 상태 배지 + 별도 창 복귀 버튼/행 클릭 |
| `src/client/src/components/GlobalSessionDockTray.tsx` | 별도-창 칩 표시 + 클릭 복귀 |
| `src/client/src/components/popout/popoutBus.ts` | `group-recall` 메시지 타입 추가 |
| `src/client/src/components/popout/PopoutPage.tsx` | `group-recall` 수신 시 반환 후 종료 |
| `src/client/src/i18n.tsx` | `session.windowState.*`·`recallToMain`·`dock.poppedHint` 등 키 (en/ko) |

### 아키텍처 결정

1. **창 상태를 별도 컨텍스트로 노출**: 자주 바뀌는 per-session 배치(`windowStates`)를 안정적인 API 컨텍스트와 분리해, 액션만 필요한 소비자가 매 geometry/minimize 변경마다 리렌더되지 않게 했다.
2. **복귀 = re-dock + 강제 회수 폴백**: 우선 `group-recall`로 팝아웃이 최신 상태를 반환하게 하되, 무응답 시 heartbeat 회수 경로(`group-reclaimed`)를 1.5s 폴백으로 재사용해 "버튼은 항상 동작"을 보장한다.
