Skip to content

fix(in-app): run 종료 시 graceful detach — 차단 오버레이 해제·인터랙션 복원 (#748)#804

Open
DaveDev42 wants to merge 1 commit into
mainfrom
agent-af670219b43c47a86
Open

fix(in-app): run 종료 시 graceful detach — 차단 오버레이 해제·인터랙션 복원 (#748)#804
DaveDev42 wants to merge 1 commit into
mainfrom
agent-af670219b43c47a86

Conversation

@DaveDev42

Copy link
Copy Markdown
Collaborator

배경

run7 실기기 관측: env3 run 종료 후 대시보드는 완료인데 미니앱에는 "Debugger Disconnected" + 스피너가 계속 돌고 click/scroll 전부 무반응 — 토스 앱 강제 종료 외 복구 불가. 이슈의 두 가설 중 (b) relay 종료 시 chii/in-app 표면이 blocking 상태를 남김을 코드로 규명하고 graceful detach를 구현한다. (전체 freeze 원인 제거는 sdk-example#277이 담당 — 이슈 코멘트의 run8 사후 분석 참조.)

(b)-가설: 코드로 확인된 leak (file:line 근거)

우리 in-app 디버그 표면이 run 종료 후 남기는 것 3건 — 어느 것도 스피너를 그리거나 전체 터치를 흡수하지 않지만, 셋 다 잔존한다:

  1. 영구 "Debugger Disconnected" 배지src/mcp/attach-orchestrator.ts buildIndicatorExpression#__ait_debug_indicator(fixed, bottom-left, pointer-events:auto, z-index:2147483647)를 렌더한다. src/test-runner/relay-factory.ts:652 close()가 종료 시 { state: 'disconnected' }만 주입하고 제거하는 코드가 없다 → 회색 배지가 무한 잔존. run7이 본 "Debugger Disconnected"가 바로 이 배지(기본 disconnectedLabel).
  2. eruda 인-페이지 콘솔src/in-app/eruda-overlay.tsmountEruda()로 마운트만 하고 unmount 경로가 없어 세션 종료 후에도 플로팅 버튼/열린 패널 잔존.
  3. keepAwake 미복구src/in-app/attach.ts:455가 attach 시 강제 on, beforeunload에서만 복구 → 세션 종료(비-unload) 시 화면 계속 awake.

Fix — graceful detach

  • 배지(buildIndicatorExpression): disconnected를 non-blocking(pointer-events:none 즉시)·self-dismissing(창 후 fade→DOM 제거)으로. 재연결(ait:relay-ws-state open)이 self-dismiss를 취소·재마운트(transient 터널 blip은 배지를 날리지 않음). 컨트롤러 객체는 유지돼 재주입 시 window.WebSocket 이중 래핑 없음. 기본 라벨을 ko-primary 디버거 연결 끊김으로(레포 in-app 문자열 관례 — 형제 대시보드/MCP 문자열 전부 한국어).
  • in-app(attach.ts detachDebugSurface()): 단일 idempotent·비-throw 정리 함수. 배지 제거 + unmountEruda() + keepAwake 복구. relay WS 종료 모든 경로에 배선:
    • 비-4401 종료 → grace window(5s) 후 정리, 재연결 open이 취소.
    • 4401(TOTP 만료 = 종결) → 즉시.
    • error(clean close 없이) → 방어적 스케줄(unclean-close path).
    • pagehide → 즉시(beforeunload-safe).
    • #478 post-4401 fail-fast 보존을 위해 WS observer proxy는 의도적으로 유지(non-blocking이라 입력 흡수 안 함).

(a)-가설 경계

스피너 + 전체 터치 무반응은 우리 레이어 밖이다. 우리 표면에는 full-viewport 요소·body 스크롤/pointer-events 잠금·capture-phase document 리스너가 하나도 없다(코드 grep으로 확인). 배지는 작은 bottom-left fixed 요소(padding:5px 9px)라 구조적으로 모든 입력을 흡수할 수 없다. 네이티브 토스 앱 로딩 오버레이는 JS 표면이 해제할 수 없어 시도하지 않으며 코드 주석으로 경계를 명시했다. (a)는 실기기 증거가 필요해 이슈에서 열린 채로 둔다(sdk-example#277이 진범 후보).

테스트

  • src/mcp/__tests__/indicator-expression.test.ts (신규): 배지 IIFE를 jsdom에서 indirect-eval로 실행 — attached 렌더/disconnected non-blocking(pointer-events:none)·ko 라벨/self-dismiss DOM 제거/재연결 취소·재마운트/재주입 무중복/SECRET-HANDLING.
  • src/__tests__/in-app-attach.test.ts: detachDebugSurface(배지 제거·keepAwake 복구·idempotency·비-throw·teardown 후 클릭이 앱에 도달), WS 종료 배선(grace 후 정리 / 재연결 취소 / 4401 즉시 / error 스케줄 / pagehide / no-attach 무변경). 기존 observer describe들은 grace-timer 누수 방지로 fake-timer 위생 추가.
  • src/__tests__/in-app-eruda-overlay.test.ts: unmountEruda(destroy 호출·미마운트 no-op·idempotency·guard 리셋·fail-silent).
  • src/test-runner/cell.test.ts: 기본 disconnected 라벨 ko 갱신 + 비-blocking/self-dismiss 어서션.

pnpm typecheck·pnpm test(2527 pass)·pnpm lint green. check:mcp-react-free·check:debug-surface-absent(release 0 bytes)·check:dashboard-html-fresh 통과. Type A patch changeset 포함.

프로덕션 무영향

디버그 attach가 없으면 zero behavior change — installRelayWsObserver(gate 통과 후에만 실행) 안에서만 종료 배선·pagehide 리스너를 등록한다. release 빌드는 __DEBUG_BUILD__:false로 in-app 그래프 전체 DCE(check:debug-surface-absent가 0 bytes 강제).

남은 검증 (폰-게이트)

run7 실기기 재현 확인은 폰이 필요하다 — full closure의 acceptance는 다음 env3 세션이 강제 종료 없이 복구되는 것을 확인하는 것. 그래서 Closes가 아니라 Refs.

Refs #748

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant