어 앱을 업뎃할때마다 설정이 날아가지 않도록 하는 것만으로 충분한니까 , 그거 위주로 작업해줘! 

# Data Survival Blueprint — 업데이트·재설치 후에도 데이터·웹 로그인 유지

> **목적:** Isle이 쓰는 “데이터가 안 날아가는” 시스템을 **플랫폼·프레임워크 무관**하게 설명한다.  
> 다른 Flutter/네이티브 앱에 이 문서만 주고 동일 아키텍처를 이식할 수 있게 작성했다.  
> **정본 구현:** `isle/lib/core/services/`, `isle/lib/main.dart`, `isle/ios/Runner/ScreenTimeBridge.swift`

---

## 1. 한 줄 요약

| 상황 | 사용자 데이터 (Hive) | 웹 로그인 (WebView 쿠키) | OS 차단 설정 (Screen Time 등) |
|------|---------------------|--------------------------|-------------------------------|
| **앱 스토어 업데이트** (같은 Bundle ID) | ✅ 샌드박스 유지 → 자동 유지 | ✅ WKWebView/Android WebView 기본 영구 저장소 | ✅ Hive + (iOS) Keychain 미러 |
| **앱 강제 종료·재실행** | ✅ + 25초 체크포인트 | ✅ | ✅ |
| **앱 삭제 후 재설치** | ⚠️ 샌드박스 삭제 → **Survival Backup**으로 복구 | ⚠️ 쿠키 삭제 → **다시 로그인** (백업 JSON에 쿠키 없음) | ✅ (iOS) Keychain 미러로 스케줄·앱 선택 복구 가능 |
| **기기 교체** | 수동 JSON/백업 코드 또는 iCloud 백업 폴더 | 수동 로그인 | 백업 JSON + Screen Time 재승인 |

**핵심:** “업데이트해도 안 날아감” = **① 같은 설치의 OS 샌드박스** + **② 샌드박스 밖 미러 백업** + **③ Keychain(삭제 후에도 남는 계층)** 의 **3층 방어**.

---

## 2. 아키텍처 — 4계층 모델

```mermaid
flowchart TB
  subgraph L1["Layer 1 — Hot path (매 실행)"]
    Hive["Hive boxes\n(app sandbox Documents)"]
    WebView["WebView cookie jar\n(OS-managed, same sandbox)"]
  end

  subgraph L2["Layer 2 — Change-triggered mirror"]
    Survival["Survival Backup\ncurrent.json (sandbox 밖)"]
    Debounce["debounce 800ms\nisleNotifyLocalDataChanged()"]
  end

  subgraph L3["Layer 3 — Crash / partial loss"]
    Checkpoint["Checkpoint\nisle_state_checkpoint.b64\n(Documents, 25s)"]
  end

  subgraph L4["Layer 4 — OS survives app delete (iOS)"]
    Keychain["Keychain\nscreen_time_persistence"]
    AppGroup["App Group file\nscreen_time_persistence_v1.json"]
  end

  UI[UI / Stores] --> Hive
  UI --> WebView
  Hive --> Debounce --> Survival
  Hive --> Checkpoint
  Hive --> Keychain
  Keychain --> AppGroup
  Bootstrap[main bootstrap] --> Survival
  Bootstrap --> Keychain
  Bootstrap --> Checkpoint

Layer 1 — Primary store (업데이트 시 99% 여기서 해결)

Layer 2 — Survival backup (재설치·샌드박스 초기화)

Layer 3 — Checkpoint (강제 종료·일부 박스만 날아간 경우)

Layer 4 — Native mirror (iOS Screen Time 등 “앱 삭제 후에도 남아야 하는” 데이터)


3. 부팅 순서 (다른 앱에 그대로 복사)

runApp() 전에 반드시 이 순서:

1. WidgetsFlutterBinding.ensureInitialized()
2. Hive.initFlutter()
3. openBox(*) — 모든 박스 (실패해도 앱은 뜨게 _safe 래퍼 권장)
4. [선택] 마스터 플래그 복구 — 캐시만 리셋됐을 때 도메인 데이터로 플래그 재설정
     예: BlockSchedule 있으면 youtubeScheduleLockEnabled = true
5. SurvivalBackupService.bootstrapOnLaunch()
     → fresh install + meaningful current.json → restore
     → _safeToSync = true
6. NativePersistence.bootstrapAfterDataLoad()  // iOS Keychain 등
7. CheckpointService.restoreFromCheckpointIfLocalEmpty()
8. CheckpointService.startPeriodicCheckpoint()  // 25s Timer
9. runApp()

Isle 정본: isle/lib/main.dart _bootstrap() 83–154행.


4. Store 패턴 (이식용 템플릿)

4.1 Store 계약

class ExampleStore {
  static const _boxName = 'my_app_example';
  static Box<String>? _box;

  static Future<void> ensureOpen() async {
    if (_box != null && _box!.isOpen) return;
    _box = await Hive.openBox<String>(_boxName);
  }

  static Future<void> saveAll(List<ExampleModel> items) async {
    await ensureOpen();
    await _box!.put('items_v1', jsonEncode(items.map((e) => e.toJson()).toList()));
    isleNotifyLocalDataChanged(); // ← 필수: Survival debounce
  }
}

4.2 “로컬이 비었는지” 판별 (localLooksFresh)

재설치·초기화 감지용 — 의미 있는 데이터가 하나도 없으면 true:

정본: IsleSurvivalBackupService.localLooksFresh().

4.3 Export payload (단일 스키마)

모든 백업 경로가 같은 JSON 스키마를 쓴다:

소비자 포맷 파일
Survival current.json JSON indent 샌드박스 밖
Checkpoint Base64(JSON) Documents
사용자 공유 백업 JSON 파일 + Share 임시
백업 코드 Base64 + schema version 클립보드

정본 빌더: IsleDataExportService.buildExportPayload() + export_schema 버전 번호.

스키마 버전 올릴 때: export_schema 증가, importFromJsonString 에 migration 분기.


5. Survival Backup — 구현 체크리스트

새 앱에 YourAppSurvivalBackupService 만들 때:

정본: isle/lib/core/services/isle_survival_backup_service.dart


6. Checkpoint — 구현 체크리스트

정본: isle/lib/core/services/isle_checkpoint_service.dart


7. 웹 로그인 유지 (YouTube / Instagram / Brunch)

7.1 업데이트 시 유지되는 이유

7.2 구현 규칙

규칙 Isle
로그아웃 시에만 쿠키 삭제 수동 “로그아웃” 메뉴에서만
WebView controller 재생성해도 동일 process/data store YoutubeSession.park() — controller 유지
UA 고정 (봇 감지 방지) InstagramWebConfig.mobileSafariUserAgent, kYoutubeSafariUserAgent
Instagram 키보드 resizesToAvoidBottomInset: true + viewport interactive-widget=resizes-content

7.3 UX 플래그 vs 실제 세션

7.4 재설치 후 웹 로그인

다른 앱에서 쿠키까지 백업하려면 (고급, 비권장):
별도 암호화 Keychain + 도메인별 cookie export — 심사·보안 리스크 큼. Isle은 하지 않음.


8. iOS Screen Time / Keychain 미러 (Layer 4)

8.1 무엇을 미러링하는가

ScreenTimePersistenceService.exportBundle():

8.2 쓰기 / 읽기

동작 Dart Native
저장 syncMirror() writePersistenceMirror (App Group) + savePersistenceKeychain
복구 restoreFromNativeMirrorIfNeeded() loadPersistenceKeychain → fallback loadPersistenceMirror
재적용 reapplyIfAuthorized() BlockScheduleEnforcementService + applySavedAppShield

정본 Swift: ScreenTimeBridge.swift persistenceKeychainService, IsleAppGroupFiles.

8.3 다른 앱 이식 시

  1. App Group capability + Keychain Sharing (필요 시).
  2. MethodChannel {bundleId}/your_persistence.
  3. Hive가 비었을 때만 native → Hive import.
  4. onAuthorizationGranted() 에서 mirror + OS 재적용.

9. 수동 백업·복원 (사용자 주도)

기능 클래스 용도
JSON 파일 공유 IsleDataExportService.shareExport() 기기 간 이동
붙여넣기 복원 importFromJsonString Replace All / Merge 모드
백업 코드 (Base64) IsleBackupCodec + IsleBackupService 메신저로 짧게 전달
설정 UI DataBackupScreen 폴더 선택·동기화·복원

Replace All 전: clearAllUserData() 로 고아 키 제거.


10. 업데이트만 했을 때 깨지는 케이스 — 방어 패턴

Isle이 쓰는 추가 방어 (다른 앱에 권장):

  1. 마스터 스위치 복구 (main.dart schedule_master_recovery):
    isle_cache만 초기화되고 스케줄 박스는 남은 경우 → 플래그 재켜기.

  2. Screen Time 재승인 (reapplyIfAuthorized):
    업데이트 후 authorization 리셋되어도 저장된 pick으로 자동 재요청·재적용.

  3. YouTube WebView OS 차단 해제 (ensureIsleYoutubeWebViewUnblocked):
    ManagedSettings가 WebView 스트림까지 막은 상태 복구.

  4. export_schema / snapshot_version:
    구버전 백업 import 시 필드 기본값.


11. 플랫폼별 BackupRoot 권장

플랫폼 업데이트 앱 삭제 후 웹 로그인
iOS Hive+WebView 유지 Survival(iCloud 폴더) + Keychain 재로그인
Android Hive+WebView 유지 Survival(외부/Drive) + Auto Backup 재로그인
macOS Documents 밖 Survival 동일 동일

iOS 사용자 안내 문구 패턴:
「앱을 삭제하면 이 기기 안 데이터는 사라집니다. iCloud Drive에 백업 폴더를 지정하세요.」


12. 새 앱 이식 — 파일/모듈 매핑

역할 Isle 경로 새 앱에서 만들 이름 예시
부팅 lib/main.dart main.dart
Export 스키마 core/services/isle_data_export_service.dart app_data_export_service.dart
Survival core/services/isle_survival_backup_service.dart app_survival_backup_service.dart
Checkpoint core/services/isle_checkpoint_service.dart app_checkpoint_service.dart
변경 알림 core/services/isle_local_data_sync.dart notifyLocalDataChanged()
Base64 백업 코드 core/services/isle_backup_codec.dart 동일 패턴
Keychain 미러 core/intervention/screen_time_persistence_service.dart native_prefs_persistence_service.dart
iOS Native ios/Runner/ScreenTimeBridge.swift PersistenceBridge.swift
설정 UI features/settings/presentation/data_backup_screen.dart data_backup_screen.dart
Store 예시 *_store.dart + ensureOpen + isleNotifyLocalDataChanged 동일

13. 안티패턴 (하지 말 것)


14. AI 에이전트 작업 순서 (다른 레포에 적용)

  1. 이 문서 §2 4계층 중 필요 계층만 선택 (웹만 쓰는 앱 → Layer 1+2, OS 권한 앱 → +4).
  2. DataExportService.buildExportPayload() + export_schema 정의.
  3. 모든 *Store.save* 끝에 notifyLocalDataChanged().
  4. main() bootstrap 순서 §3 적용.
  5. 설정 화면에 백업 폴더·동기화·복원.
  6. (iOS) Keychain 미러 채널 + bootstrapAfterDataLoad.
  7. WebView: 쿠키 clear 금지·UA 고정 문서화.
  8. 테스트: ① 데이터 저장 ② kill app ③ 재실행 ④ 업데이트 시뮬(동일 bundle) ⑤ [선택] 삭제 후 Survival 복원.

15. Isle 데이터가 Survival JSON에 포함되는 항목 (참고)

buildExportPayload() + Survival extras:

포함 안 함: WebView 쿠키, Supabase refresh token (별도 auth), 바이너리 미디어.


문서 버전: 2026-06-03 · Isle export_schema 10 · survival snapshot_version 10