3-2. Electron 패리티 — 창과 네이티브 API (4/24 ~ 5/2)

17일 공백 뒤 4월 24일에 돌아와, 엿새 동안 242커밋을 쏟았다. 목표는 하나다 — Electron 코드가 그대로 읽히는 API 표면.

작업 순서를 정하는 기준은 "검증할 수 있는가"였다.

"완전 e2e 가능한것부터 쭉 진행가시죠" — 2026-04-28

"네 미구현 부분도 e2e로 충분히 확인 가능하죠?" / "테스트코드 빡세게 들어가는 거죠?" — 2026-04-28

"검증 안된 케이스는 문서에 적어주세요" — 2026-04-28

마지막 문장이 audit의 "정직 경계" 표기로 굳는다 — 되는 것과 안 되는 것, 확인하지 못한 것을 구분해 적는다.

Zig 0.16 마이그레이션부터

돌아온 첫 커밋이 feat!: Zig 0.16으로 마이그레이션 (하위호환 제거)다. std.Io 개편에 맞춰 코어를 옮기고, SujiCore에 get_io를 추가해 state 플러그인이 메인 io를 공유하게 했다.

Phase 2 — 멀티 윈도우 (4/24)

단계내용
설계멀티 윈도우 결정사항 확정, 스레드 모델 main-only 계약 채택
스켈레톤WindowManager + 단위 테스트 19개
이벤트EventSink + window:created/close/closed + preventDefault
동시성std.Io.Mutex 기반 안전성과 동시성 테스트
Step A~B.5CefNative 어댑터, EventBusSink, WindowStack 배선, CEF life_span 통합(취소 가능한 close), create_window IPC를 WM 경유로

여기에 window:all-closed 이벤트와 플랫폼별 quit 정책, Cmd+W의 NSWindow close cascade, 구조화 디버그 로거가 함께 들어갔다. Zig SDK에 suji.quit()과 suji.platform()을 노출한 뒤 Rust·Go·Node·프론트엔드에도 같은 API를 폈다.

Phase 2.5 — IPC가 창을 안다 (4/25, 77커밋)

  • IPC wire level에서 __window 자동 주입
  • 핸들러 2-arity API (Request, InvokeEvent)
  • event.window.name·url·is_main_frame 노출, 이름 길이·문자 검증
  • sendTo / {to: winId} — Electron webContents.send 대응
  • 생명주기 payload {id, name?} 표준화, state scope 확장

config는 windows[] 배열로 전환하며 하위호환을 끊었다.

Phase 3 — 창 외형 (4/25)

frameless / transparent / parent-child를 시작으로 외형 옵션 8종을 넣고, 런타임 IPC와 프론트엔드 SDK에 풀 노출했다. examples/window-styles에 드래그 리전 데모까지 붙였고, 리팩터로 파서 헬퍼 분리·createMacWindow 분할·Appearance/Constraints 정리를 했다.

Phase 4-A — webContents (4/25)

load_url / reload / execute_javascript / get_url / is_loading을 코어에 넣고 4개 언어 SDK와 분리 e2e 스크립트, 포팅 가이드까지 한 묶음으로 갔다. 백엔드 SDK가 callBackend("__core__" / __fanout__ / __chain__)을 쓸 수 있게 된 것도 이때다.

Phase 5 — 네이티브 API (4/26, 40커밋)

슬라이스내용
5-aClipboard / Shell / Dialog + sheet modal
5-bTray v1 — NSStatusItem + 메뉴 + click 라우팅
5-cNotification v1 — UNUserNotificationCenter + click 이벤트
5-eGlobal shortcut — macOS Carbon Hot Key + accelerator 파싱
lifecycle창 resize/focus/blur/move 이벤트 — NSWindowDelegate 자동 부착
fs파일 시스템 코어 API, fs.rm, mtime ms, 언어별 typed wrapper

애플리케이션 메뉴 커스터마이즈 API도 이날 들어갔다. 각 기능이 5개 진입점(4개 백엔드 SDK + 프론트엔드)에 동시에 노출되는 패턴이 이때 굳는다.

Phase 7 — 보안 (4/26)

  • IPC 유효성 검사 + CSP 기본 헤더 + contextIsolation 감사
  • fs 샌드박스 — 프론트엔드 경로 화이트리스트, 백엔드는 우회, Zig typed wrapper
  • 앱별 cache 격리 (Electron app.getPath('userData') 동등, 3 OS)
  • macOS App Sandbox 자동화 — config.app.sandbox, helper별 entitlements

번들 다이어트와 WebContentsView (4/27)

CEF locale prune과 바이너리 strip으로 38MB(12%)를 줄였다. iframe sandbox와 origin allowlist를 넣어 CSP frame-src를 자동 합성하게 했다.

같은 날 Phase 17-A로 WebContentsView를 7단계에 걸쳐 붙였다 — 데이터 모델 → IPC 핸들러 8개 → macOS NSView 합성 → bounds/visible/reorder → 이벤트와 자동 정리 → 프론트엔드 API·E2E·문서 → 예제 데모와 시각 캡처 검증.

창 생명주기와 시스템 API (4/28, 64커밋)

Phase 5 라이프사이클 이벤트를 다섯 슬라이스로 채웠다 — minimize/maximize/fullscreen, ready-to-show와 page-title-updated, show/hide, 취소 가능한 will-resize, quitOnAllWindowsClosed 정책.

시스템 API도 한 번에 늘었다: screen.getAllDisplays, dock badge, powerSaveBlocker, find-result 이벤트, safeStorage(macOS Keychain), app.requestUserAttention(dock bounce), shell.trashItem, app.getPath(Electron 표준 7키). 모두 5개 SDK에 노출했다.

4월 29일에 마무리 정리를 하고, 5월 2일 session.cookies set/get/remove를 끝으로 이 구간이 닫힌다.