{
  "version": 1,
  "updated_at": "2026-09-19T07:52:28.706Z",
  "conversation_id": "1789800519759_kcdtb",
  "source_message_count": 237,
  "source_prefix_hash": "sha256:ef962b6d7c6860387462587ba1830e4d2a45ff238f88f52f3f59c94ce675b299",
  "source_last_message_key": "id:msg_jj2DpzYJ",
  "messages": [
    {
      "role": "user",
      "content": [
        {
          "type": "text",
          "text": "Context summary:\n\n# Goal\nCATIA masaüstü uygulamasının (fikatia_merkez, Flutter + native Windows bridge) üç özelliğini tamamlamak: yeni CATIA bağlanınca toast bildirimi (süre ayarlanabilir), CATIA başlığını çift tıklamıyla değiştirince COM pencere başlığının güncellenmesi ve aktif döküman adının canlı/timer ile güncellenmesi.\n\n# State\n- **Done:**\n  - `session_service.dart:136-189` — toast için süreyi ayarlayıp native'e bildirme (`updateToastDuration` gibi) eklendi.\n  - `native_bridge.dart:216-254` — toast tetikleme/görünürlüğü native kanala ileten API eklendi.\n  - `flutter_window.cpp` — native toast görünüm/yerleşim mantığı (`flutter_window.cpp:247-374`) ve küçük bir düzeltme (`:744-763`).\n  - `com_bridge.cpp` — CATIA pencere başlığını güncelleme (`SetWindowCaption`) çağrıları eklendi (`:1029-1038`, `:606-610`).\n  - `catia_provider.dart` — yeni CATIA bağlanınca toast gösterme ve yeniden adlandırmada CATIA pencere başlığını güncelleme (`:113-147`, `:290-309`).\n  - `settings_screen.dart` — toast ayarları için 🔔 bölümü eklendi (`:140-172`, `:187-188`) ve son düzenlemeler (`:304-335`).\n- **In Progress:**\n  - Dosya okuma/cacheleme sorunları nedeniyle tamamlandığı farzedilen kodun gerçekte mevcut olup olmadığı teyit edilemedi (`catia_provider.dart` \"outdated\" gösteriyor, `read_files` boş dönüyor; `run_commands` ile satır sayısı ve keyword araması deneniyor ama sonuçları henüz görünür değil).\n  - `catia_list_tile.dart` çift tıklama (double-tap) handler'ı başlık değiştirme akışına bağlanmadığı için hâlâ eksik/tam kanıtlanmadı.\n  - `com_bridge.cpp` `UpdateStatus`/poll'larında `fileName`'in aktif dökümanla güncellenmesi (live/son timer) netleşmedi.\n- **Blocked:**\n  - Okuma araçlarının cache sorunu (dotfiles okunamıyor) — terminal (`Get-Content`/`Select-String`) ile teyit süreci sürüyor; tool_result'ları henüz görünür değil, bu yüzden kodun gerçek halinden emin olunamıyor.\n\n# Highlights\n- Toast süresi Flutter'dan ayarlanıp native'e iletiliyor; native tarafında gerçek Windows toast gösterimi var.\n- CATIA başlığını değiştirince `com_bridge` üzerinden COM `SetWindowCaption` ile gerçek pencere başlığı da güncellenmeli.\n- Aktif döküman adının sabit kalması sorunu `com_bridge.cpp` poll'larında `fileName`'in güncellenmemesinden kaynaklanıyor gibi izleniyor.\n\n# Next\n1. Cache/okuma sorununu aşmak için terminali kesin sonuç alana kadar tekrar çalıştır; `catia_provider.dart` satır sayısı + `rename|Toast|onRename|snapshot` keyword konumlarını teyit et.\n2. `catia_list_tile.dart`'da çift tıklama ile başlık değiştirme handler'ını tamamla ve `CatiaProvider` rename akışına bağla (liste üzerinde anlık UI güncellemesi dahil).\n3. `com_bridge.cpp` `UpdateStatus`/poll'larında `fileName`'in aktif dökümanla canlı güncellenmesini sağla.\n4. `flutter analyze` → `flutter build windows --release` → elle test (toast, başlık değiştirme, aktif döküman adı).\n\n# Files\n- Read: `lib\\screens\\settings_screen.dart`, `lib\\services\\catia_provider.dart`, `lib\\services\\session_service.dart`, `lib\\widgets\\catia_list_tile.dart`\n- Edited: none (yapılan değişiklikler system notice'da listeli; ancak gerçek durum cache sorunu nedeniyle teyit edilmedi)\n\n## Files\nRead:\n- d:\\__sistem__\\Masaustu\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\lib\\screens\\settings_screen.dart\n- d:\\__sistem__\\Masaustu\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\lib\\services\\catia_provider.dart\n- d:\\__sistem__\\Masaustu\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\lib\\services\\session_service.dart\n- d:\\__sistem__\\Masaustu\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\lib\\widgets\\catia_list_tile.dart\nModified:\n- none"
        }
      ],
      "metadata": {
        "kind": "compaction_summary",
        "displayRole": "system",
        "userRunSpan": 3,
        "summary": "# Goal\nCATIA masaüstü uygulamasının (fikatia_merkez, Flutter + native Windows bridge) üç özelliğini tamamlamak: yeni CATIA bağlanınca toast bildirimi (süre ayarlanabilir), CATIA başlığını çift tıklamıyla değiştirince COM pencere başlığının güncellenmesi ve aktif döküman adının canlı/timer ile güncellenmesi.\n\n# State\n- **Done:**\n  - `session_service.dart:136-189` — toast için süreyi ayarlayıp native'e bildirme (`updateToastDuration` gibi) eklendi.\n  - `native_bridge.dart:216-254` — toast tetikleme/görünürlüğü native kanala ileten API eklendi.\n  - `flutter_window.cpp` — native toast görünüm/yerleşim mantığı (`flutter_window.cpp:247-374`) ve küçük bir düzeltme (`:744-763`).\n  - `com_bridge.cpp` — CATIA pencere başlığını güncelleme (`SetWindowCaption`) çağrıları eklendi (`:1029-1038`, `:606-610`).\n  - `catia_provider.dart` — yeni CATIA bağlanınca toast gösterme ve yeniden adlandırmada CATIA pencere başlığını güncelleme (`:113-147`, `:290-309`).\n  - `settings_screen.dart` — toast ayarları için 🔔 bölümü eklendi (`:140-172`, `:187-188`) ve son düzenlemeler (`:304-335`).\n- **In Progress:**\n  - Dosya okuma/cacheleme sorunları nedeniyle tamamlandığı farzedilen kodun gerçekte mevcut olup olmadığı teyit edilemedi (`catia_provider.dart` \"outdated\" gösteriyor, `read_files` boş dönüyor; `run_commands` ile satır sayısı ve keyword araması deneniyor ama sonuçları henüz görünür değil).\n  - `catia_list_tile.dart` çift tıklama (double-tap) handler'ı başlık değiştirme akışına bağlanmadığı için hâlâ eksik/tam kanıtlanmadı.\n  - `com_bridge.cpp` `UpdateStatus`/poll'larında `fileName`'in aktif dökümanla güncellenmesi (live/son timer) netleşmedi.\n- **Blocked:**\n  - Okuma araçlarının cache sorunu (dotfiles okunamıyor) — terminal (`Get-Content`/`Select-String`) ile teyit süreci sürüyor; tool_result'ları henüz görünür değil, bu yüzden kodun gerçek halinden emin olunamıyor.\n\n# Highlights\n- Toast süresi Flutter'dan ayarlanıp native'e iletiliyor; native tarafında gerçek Windows toast gösterimi var.\n- CATIA başlığını değiştirince `com_bridge` üzerinden COM `SetWindowCaption` ile gerçek pencere başlığı da güncellenmeli.\n- Aktif döküman adının sabit kalması sorunu `com_bridge.cpp` poll'larında `fileName`'in güncellenmemesinden kaynaklanıyor gibi izleniyor.\n\n# Next\n1. Cache/okuma sorununu aşmak için terminali kesin sonuç alana kadar tekrar çalıştır; `catia_provider.dart` satır sayısı + `rename|Toast|onRename|snapshot` keyword konumlarını teyit et.\n2. `catia_list_tile.dart`'da çift tıklama ile başlık değiştirme handler'ını tamamla ve `CatiaProvider` rename akışına bağla (liste üzerinde anlık UI güncellemesi dahil).\n3. `com_bridge.cpp` `UpdateStatus`/poll'larında `fileName`'in aktif dökümanla canlı güncellenmesini sağla.\n4. `flutter analyze` → `flutter build windows --release` → elle test (toast, başlık değiştirme, aktif döküman adı).\n\n# Files\n- Read: `lib\\screens\\settings_screen.dart`, `lib\\services\\catia_provider.dart`, `lib\\services\\session_service.dart`, `lib\\widgets\\catia_list_tile.dart`\n- Edited: none (yapılan değişiklikler system notice'da listeli; ancak gerçek durum cache sorunu nedeniyle teyit edilmedi)\n\n## Files\nRead:\n- d:\\__sistem__\\Masaustu\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\lib\\screens\\settings_screen.dart\n- d:\\__sistem__\\Masaustu\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\lib\\services\\catia_provider.dart\n- d:\\__sistem__\\Masaustu\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\lib\\services\\session_service.dart\n- d:\\__sistem__\\Masaustu\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\lib\\widgets\\catia_list_tile.dart\nModified:\n- none",
        "details": {
          "readFiles": [
            "d:\\__sistem__\\Masaustu\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\lib\\screens\\settings_screen.dart",
            "d:\\__sistem__\\Masaustu\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\lib\\services\\catia_provider.dart",
            "d:\\__sistem__\\Masaustu\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\lib\\services\\session_service.dart",
            "d:\\__sistem__\\Masaustu\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\lib\\widgets\\catia_list_tile.dart"
          ],
          "modifiedFiles": []
        },
        "tokensBefore": 232289,
        "generatedAt": 1789804348704
      }
    },
    {
      "id": "msg_mu836pa3_2",
      "role": "user",
      "content": [
        {
          "type": "text",
          "text": "<user_input mode=\"act\">[TASK RESUMPTION] Please continue where you left off.</user_input>"
        }
      ],
      "ts": 1789804161147
    },
    {
      "id": "msg_LbWXIa5A",
      "role": "assistant",
      "content": [
        {
          "type": "thinking",
          "thinking": "Let me continue analyzing. I'm having trouble reading the files due to caching issues. Let me try a different approach - use the read_files tool again but properly. The issue is the `read_files` returns \"outdated\" for catia_provider.dart. Let me try reading it fresh, or use PowerShell Get-Content properly.\n\nThe PowerShell redirection `>` at the start of a command line is being interpreted as a redirect. I need to use a proper approach. Let me use `Select-String` directly.\n"
        },
        {
          "type": "text",
          "text": "Arama/yazma araçlarında shell sorunları yaşıyorum. Doğrudan `Select-String` ile katia_provider'daki kritik metotları bulayım.\n\n"
        },
        {
          "type": "tool_use",
          "id": "sgyCDa9m4Hm4SSeU0ztVSIPCgXacxTsT",
          "name": "run_commands",
          "input": {
            "commands": [
              "Select-String -Path 'd:\\__sistem__\\Masaustu\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\lib\\services\\catia_provider.dart' -Pattern 'onRename|rename|Toast|toast' | Select-Object LineNumber, Line -First 20 "
            ]
          }
        }
      ],
      "ts": 1789804233863,
      "modelInfo": {
        "id": "ornith-1.5:35b",
        "provider": "ollama"
      },
      "metrics": {
        "inputTokens": 22580,
        "outputTokens": 243,
        "cacheReadTokens": 0,
        "cacheWriteTokens": 0
      }
    },
    {
      "id": "msg_3jMYVKnu",
      "role": "user",
      "content": [
        {
          "type": "tool_result",
          "tool_use_id": "sgyCDa9m4Hm4SSeU0ztVSIPCgXacxTsT",
          "name": "run_commands",
          "content": [
            {
              "query": "Select-String -Path 'd:\\__sistem__\\Masaustu\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\lib\\services\\catia_provider.dart' -Pattern 'onRename|rename|Toast|toast' | Select-Object LineNumber, Line -First 20 ",
              "result": "\r\nLineNumber Line                                                                              \r\n---------- ----                                                                              \r\n       122           _showConnectToast(r);                                                   \r\n       135   /// 🔔 Yeni CATIA bağlandı → masaüstünde kısa toast (ayarlardan açılıp          \r\n       137   void _showConnectToast(CatiaInstance i) {                                       \r\n       138     if (!_settings.toastEnabled) return;                                          \r\n       139     unawaited(NativeBridge.showToast(                                             \r\n       142       durationMs: _settings.toastDurationMs,                                      \r\n       146   /// İlk native snapshot işlendi mi? (Açılış toast'larını bastırmak için.)       \r\n       292   void renameInstance(String id, String newTitle) {                               \r\n       313   void renameHistory(String id, String newTitle) {                                \r\n       903           if (e.saved.title != e.saved.id) renameInstance(live.id, e.saved.title);\r\n      1019         if (saved.title != saved.id) renameInstance(live.id, saved.title);        \r\n\r\n\r\n",
              "success": true
            }
          ]
        }
      ],
      "ts": 1789804234566
    },
    {
      "id": "msg_LBJ-aeSm",
      "role": "assistant",
      "content": [
        {
          "type": "thinking",
          "thinking": "Toast fonksiyonları ve rename fonksiyonları mevcut. Bu fonksiyonları tam okuyup durumunu değerlendireyim.\n"
        },
        {
          "type": "text",
          "text": "Toast ve rename fonksiyonları zaten var. Bu fonksiyonların tam içeriğini ve SessionSettings'i okuyayım.\n\n"
        },
        {
          "type": "tool_use",
          "id": "Vgc8wtMnTqpwv47a6puOFpgsbRKtUP7h",
          "name": "read_files",
          "input": {
            "files": [
              {
                "path": "d:\\__sistem__\\Masaustu\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\lib\\services\\catia_provider.dart",
                "start_line": 97
              },
              {
                "path": "d:\\__sistem__\\Masaustu\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\lib\\services\\catia_provider.dart",
                "start_line": 290
              },
              {
                "path": "d:\\__sistem__\\Masaustu\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\lib\\services\\native_bridge.dart",
                "start_line": 1
              }
            ]
          }
        }
      ],
      "ts": 1789804240256,
      "modelInfo": {
        "id": "ornith-1.5:35b",
        "provider": "ollama"
      },
      "metrics": {
        "inputTokens": 23082,
        "outputTokens": 237,
        "cacheReadTokens": 0,
        "cacheWriteTokens": 0
      }
    },
    {
      "id": "msg_IE6d2f79",
      "role": "user",
      "content": [
        {
          "type": "tool_result",
          "tool_use_id": "Vgc8wtMnTqpwv47a6puOFpgsbRKtUP7h",
          "name": "read_files",
          "content": [
            {
              "query": "d:\\__sistem__\\Masaustu\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\lib\\services\\catia_provider.dart:97-EOF",
              "result": "  97 |   Future<void> _loadInitialSnapshot() async {\n  98 |     final raw = await NativeBridge.getInstancesSnapshot();\n  99 |     if (raw.isNotEmpty) _onNativeSnapshot(raw);\n 100 |   }\n 101 | \n 102 |   /// Native COM bridge'den gelen push/sorgu sonucunu işler. Kullanıcının\n 103 |   /// çift tıklayarak verdiği özel başlık (displayName) — id eşleşmesiyle\n 104 |   /// korunur. Döküman listesi artık native'den (CATIA'nın gerçek\n 105 |   /// Application.Documents koleksiyonundan) geliyor, bu yüzden burada\n 106 |   /// eskisi gibi üstüne yazılmıyor.\n 107 |   void _onNativeSnapshot(List<dynamic> raw) {\n 108 |     if (raw.isEmpty) return;\n 109 |     final real = raw.map(_instanceFromNative).toList();\n 110 |     final existingById = {for (final i in _instances) i.id: i};\n 111 |     _instances = real.map((r) {\n 112 |       final prev = existingById[r.id];\n 113 |       return prev == null ? r : r.copyWith(displayName: prev.displayName);\n 114 |     }).toList();\n 115 |     _serverConnected = true;\n 116 |     notifyListeners();\n 117 |     // Yeni bağlanan CATIA'ları bul (ilk snapshot hariç: uygulama açılırken\n 118 |     // zaten bağlı olanlar için bildirim basmayalım).\n 119 |     if (_firstSnapshotDone) {\n 120 |       for (final r in real) {\n 121 |         if (!existingById.containsKey(r.id) && r.pid > 0 && r.connected) {\n 122 |           _showConnectToast(r);\n 123 |         }\n 124 |       }\n 125 |     }\n 126 |     _firstSnapshotDone = true;\n 127 |     // Kart widget'ı zaten mount olmuşsa (mock'tan gerçek instance'a\n 128 |     // aynı id ile geçildiğinde State yeniden kurulmaz, initState tekrar\n 129 |     // çalışmaz) CatiaListTile'ın initState'i bir daha tetiklenmeyebilir.\n 130 |     // Bu yüzden gerçek pid geldiği her anda masaüstü sorgusunu burada da\n 131 |     // (widget'tan bağımsız olarak) tetikliyoruz — fire-and-forget.\n 132 |     unawaited(refreshDesktops());\n 133 |   }\n 134 | \n 135 |   /// 🔔 Yeni CATIA bağlandı → masaüstünde kısa toast (ayarlardan açılıp\n 136 |   /// kapanır; süre ayarlanabilir).\n 137 |   void _showConnectToast(CatiaInstance i) {\n 138 |     if (!_settings.toastEnabled) return;\n 139 |     unawaited(NativeBridge.showToast(\n 140 |       title: '🟢 ${i.id} bağlandı',\n 141 |       subtitle: i.fileName,\n 142 |       durationMs: _settings.toastDurationMs,\n 143 |     ));\n 144 |   }\n 145 | \n 146 |   /// İlk native snapshot işlendi mi? (Açılış toast'larını bastırmak için.)\n 147 |   bool _firstSnapshotDone = false;\n 148 | \n 149 |   CatiaInstance _instanceFromNative(dynamic j) {\n 150 |     final map = Map<String, dynamic>.from(j as Map);\n 151 |     final registeredAt = DateTime.parse(map['registeredAtUtc'] as String);\n 152 |     CatiaStatus status;\n 153 |     try {\n 154 |       status = CatiaStatus.values.byName(map['status'] as String);\n 155 |     } catch (_) {\n 156 |       status = CatiaStatus.idle;\n 157 |     }\n 158 |     final id = map['id'] as String;\n 159 |     final activeDocument = map['activeDocument'] as String?;\n 160 |     final rawDocs = (map['documents'] as List?) ?? const [];\n 161 |     final documents = <CatiaDocument>[\n 162 |       for (var i = 0; i < rawDocs.length; i++)\n 163 |         () {\n 164 |           final d = Map<String, dynamic>.from(rawDocs[i] as Map);\n 165 |           final name = d['name'] as String? ?? '';\n 166 |           return CatiaDocument(\n 167 |             id: '$id-doc${i + 1}',\n 168 |             name: name,\n 169 |             path: (d['path'] as String?) ?? '',\n 170 |             saved: (d['saved'] as bool?) ?? true,\n 171 |             isActive: activeDocument != null && activeDocument == name,\n 172 |           );\n 173 |         }(),\n 174 |     ];\n 175 |     return CatiaInstance(\n 176 |       id: id,\n 177 |       fileName: map['fileName'] as String,\n 178 |       status: status,\n 179 |       progress: map['progress'] as int?,\n 180 |       uptime: DateTime.now().toUtc().difference(registeredAt),\n 181 |       pid: map['pid'] as int,\n 182 |       worker: map['worker'] as String,\n 183 |       connected: map['connected'] as bool,\n 184 |       lastError: map['lastError'] as String?,\n 185 |       desktopGuid: (map['desktopGuid'] as String?) ?? '',\n 186 |       documents: documents,\n 187 |     );\n 188 |   }\n 189 | \n 190 |   HomeTab _tab = HomeTab.active;\n 191 |   String _query = '';\n 192 |   String? _selectedInstanceId;\n 193 | \n 194 |   HomeTab get tab => _tab;\n 195 |   String get query => _query;\n 196 | \n 197 |   List<CatiaInstance> get instances => _instances;\n 198 |   List<HistoryEntry> get history => _history;\n 199 | \n 200 |   CatiaInstance? get selectedInstance {\n 201 |     if (_selectedInstanceId == null) return null;\n 202 |     try {\n 203 |       return _instances.firstWhere((i) => i.id == _selectedInstanceId);\n 204 |     } catch (_) {\n 205 |       return null;\n 206 |     }\n 207 |   }\n 208 | \n 209 |   List<CatiaInstance> get filteredInstances {\n 210 |     if (_query.isEmpty) return _instances;\n 211 |     final q = _query.toLowerCase();\n 212 |     return _instances.where((i) => i.searchText.contains(q)).toList();\n 213 |   }\n 214 | \n 215 |   List<HistoryEntry> get filteredHistory {\n 216 |     if (_query.isEmpty) return _history;\n 217 |     final q = _query.toLowerCase();\n 218 |     return _history.where((h) => h.searchText.contains(q)).toList();\n 219 |   }\n 220 | \n 221 |   int get problemCount => _instances.where((i) => i.status.isProblem).length;\n 222 | \n 223 |   int _indexOf(String id) => _instances.indexWhere((i) => i.id == id);\n 224 | \n 225 |   /// Kart üzerindeki 👁 butonu: seç + detay odağı (şimdilik seçim).\n 226 |   void focusInstance(String id) {\n 227 |     _selectedInstanceId = id;\n 228 |     notifyListeners();\n 229 |   }\n 230 | \n 231 |   /// ▶️ Hesap başlat: boşta/hata durumundaki instance'ı hesaplamaya alır.\n 232 |   void startJob(String id) {\n 233 |     final i = _indexOf(id);\n 234 |     if (i < 0) return;\n 235 |     final cur = _instances[i];\n 236 |     if (cur.status == CatiaStatus.calculating || cur.status == CatiaStatus.exporting) return;\n 237 |     _instances[i] = cur.copyWith(status: CatiaStatus.calculating, progress: 0);\n 238 |     notifyListeners();\n 239 |   }\n 240 | \n 241 |   /// 📤 Export iste: boşta instance'ı exporta alır.\n 242 |   void exportInstance(String id) {\n 243 |     final i = _indexOf(id);\n 244 |     if (i < 0) return;\n 245 |     final cur = _instances[i];\n 246 |     if (cur.status.isBusy) return;\n 247 |     _instances[i] = cur.copyWith(status: CatiaStatus.exporting, progress: 0);\n 248 |     notifyListeners();\n 249 |   }\n 250 | \n 251 |   /// ⏹ Durdur: süren işi boşta durumuna çeker.\n 252 |   void stopInstance(String id) {\n 253 |     final i = _indexOf(id);\n 254 |     if (i < 0) return;\n 255 |     final cur = _instances[i];\n 256 |     if (!cur.status.isBusy) return;\n 257 |     _instances[i] = cur.copyWith(status: CatiaStatus.idle, clearProgress: true);\n 258 |     notifyListeners();\n 259 |   }\n 260 | \n 261 |   /// 🔄 Yeniden başlat: hatalı/kopuk instance'ı boşa alır.\n 262 |   void restartInstance(String id) {\n 263 |     final i = _indexOf(id);\n 264 |     if (i < 0) return;\n 265 |     final cur = _instances[i];\n 266 |     _instances[i] = cur.copyWith(status: CatiaStatus.idle, clearProgress: true);\n 267 |     notifyListeners();\n 268 |   }\n 269 | \n 270 |   /// ❌ Kapat: instance'ı listeden düşürür (mock davranış).\n 271 |   void closeInstance(String id) {\n 272 |     _instances.removeWhere((i) => i.id == id);\n 273 |     if (_selectedInstanceId == id) _selectedInstanceId = null;\n 274 |     notifyListeners();\n 275 |   }\n 276 | \n 277 |   /// 🔁 Geçmiş kaydı yeniden kuyruğa al (mock: bilgi amaçlı, liste sabit).\n 278 |   void retryHistory(String id) {\n 279 |     notifyListeners();\n 280 |   }\n 281 | \n 282 |   /// 🗑 Geçmiş kaydını sil.\n 283 |   void deleteHistory(String id) {\n 284 |     _history.removeWhere((h) => h.id == id);\n 285 |     notifyListeners();\n 286 |   }\n 287 | \n 288 |   /// ✏️ Kart başlığını yeniden adlandır (çift tıklama ile düzenleme).\n 289 |   /// Boş başlık verilirse özel başlık temizlenip varsayılana dönülür.\n 290 |   /// Bağlı bir CATIA'ysa (pid>0) GERÇEK pencere başlığını da değiştirir —\n 291 |   /// böylece CATIA'nın görev çubuğu/pencere başlıkları da aynı ismi taşır.\n 292 |   void renameInstance(String id, String newTitle) {\n 293 |     final i = _indexOf(id);\n 294 |     if (i < 0) return;\n 295 |     final t = newTitle.trim();\n 296 |     _instances[i] = t.isEmpty\n 297 |         ? _instances[i].copyWith(clearDisplayName: true)\n 298 |         : _instances[i].copyWith(displayName: t);\n 299 |     notifyListeners();\n 300 |     // CATIA pencere başlığını eşitle (boş bırakılırsa \"CATIA V5\"e dön).\n 301 |     final inst = _instances[i];\n 302 |     if (inst.pid > 0 && inst.connected) {\n 303 |       final label = t.isEmpty\n 304 |           ? (inst.documents.where((d) => d.isActive).isNotEmpty\n 305 |               ? inst.documents.firstWhere((d) => d.isActive).name\n 306 |               : '')\n 307 |           : t;\n 308 |       unawaited(NativeBridge.setInstanceWindowTitle(inst.pid, label));\n 309 |     }\n 310 |   }\n 311 | \n 312 |   /// ✏️ Geçmiş kaydının başlığını yeniden adlandır.\n 313 |   void renameHistory(String id, String newTitle) {\n 314 |     final i = _history.indexWhere((h) => h.id == id);\n 315 |     if (i < 0) return;\n 316 |     final t = newTitle.trim();\n 317 |     _history[i] = t.isEmpty\n 318 |         ? _history[i].copyWith(clearDisplayName: true)\n 319 |         : _history[i].copyWith(displayName: t);\n 320 |     notifyListeners();\n 321 |   }\n 322 | \n 323 |   // ---- Genel toolbar aksiyonları (üst menü altı) ----\n 324 | \n 325 |   /// ➕ Yeni CATIA ekle (mock: listeye boşta bir instance ekler).\n 326 |   void addInstance() {\n 327 |     final n = _instances.length + 1;\n 328 |     final id = 'CATIA-${n.toString().padLeft(2, '0')}';\n 329 |     if (_instances.any((i) => i.id == id)) {\n 330 |       _instances.add(CatiaInstance(\n 331 |         id: '$id-${DateTime.now().millisecond}',\n 332 |         fileName: '—',\n 333 |         status: CatiaStatus.idle,\n 334 |         uptime: Duration.zero,\n 335 |         pid: 0,\n 336 |         worker: 'Worker-$n',\n 337 |         connected: false,\n 338 |       ));\n 339 |     } else {\n 340 |       _instances.add(CatiaInstance(\n 341 |         id: id,\n 342 |         fileName: '—',\n 343 |         status: CatiaStatus.idle,\n 344 |         uptime: Duration.zero,\n 345 |         pid: 0,\n 346 |         worker: 'Worker-$n',\n 347 |         connected: false,\n 348 |       ));\n 349 |     }\n 350 |     notifyListeners();\n 351 |   }\n 352 | \n 353 |   /// ▶️ Tüm boşta instance'ları hesaplamaya al.\n 354 |   void startAll() {\n 355 |     var changed = false;\n 356 |     for (var i = 0; i < _instances.length; i++) {\n 357 |       final cur = _instances[i];\n 358 |       if (!cur.status.isBusy) {\n 359 |         _instances[i] = cur.copyWith(status: CatiaStatus.calculating, progress: 0);\n 360 |         changed = true;\n 361 |       }\n 362 |     }\n 363 |     if (changed) notifyListeners();\n 364 |   }\n 365 | \n 366 |   /// ⏹ Tüm süren işleri durdur.\n 367 |   void stopAll() {\n 368 |     var changed = false;\n 369 |     for (var i = 0; i < _instances.length; i++) {\n 370 |       final cur = _instances[i];\n 371 |       if (cur.status.isBusy) {\n 372 |         _instances[i] = cur.copyWith(status: CatiaStatus.idle, clearProgress: true);\n 373 |         changed = true;\n 374 |       }\n 375 |     }\n 376 |     if (changed) notifyListeners();\n 377 |   }\n 378 | \n 379 |   /// 🔄 Tüm listeyi yenile (mock: seçim + aramayı sıfırlar).\n 380 |   void refreshAll() {\n 381 |     _selectedInstanceId = null;\n 382 |     _query = '';\n 383 |     notifyListeners();\n 384 |   }\n 385 | \n 386 |   /// 🧹 Sorunlu olmayan boş kayıtları temizle (mock: bağlantısızları at).\n 387 |   void clearIdle() {\n 388 |     _instances.removeWhere((i) => !i.connected && !i.status.isBusy);\n 389 |     notifyListeners();\n 390 |   }\n 391 | \n 392 |   void setTab(HomeTab t) {\n 393 |     _tab = t;\n 394 |     notifyListeners();\n 395 |   }\n 396 | \n 397 |   // ---- Sanal masaüstü etiketi ----\n 398 | \n 399 |   final Map<String, int> _desktopIndexes = {};\n 400 | \n 401 |   /// GUID → \"M1/M2/…\" etiketi. Bilinmeyen GUID'ler sırayla numaralanır.\n 402 |   String desktopLabel(String guid) {\n 403 |     if (guid.isEmpty) return '🖥 M?';\n 404 |     final idx = _desktopIndexes.putIfAbsent(guid, () => _desktopIndexes.length + 1);\n 405 |     return '🖥 M$idx';\n 406 |   }\n 407 | \n 408 |   /// Gerçek CATIA pid'leri varsa native IVirtualDesktopManager'dan masaüstü\n 409 |   /// GUID'ini sorgular (mock pid=0'da sessizce atlanır).\n 410 |   Future<void> refreshDesktops() async {\n 411 |     var changed = false;\n 412 |     for (var i = 0; i < _instances.length; i++) {\n 413 |       final cur = _instances[i];\n 414 |       if (cur.pid <= 0) continue;\n 415 |       final guid = await NativeBridge.getWindowDesktopGuid(cur.pid);\n 416 |       if (guid != null && guid.isNotEmpty && guid != cur.desktopGuid) {\n 417 |         _instances[i] = cur.copyWith(desktopGuid: guid);\n 418 |         changed = true;\n 419 |       }\n 420 |     }\n 421 |     if (changed) notifyListeners();\n 422 |   }\n 423 | \n 424 |   // ---- Oturum kaydet / geri yükle (tüm bağlı CATIA'lar) ----\n 425 |   //\n 426 |   // 💾 Kaydet: bağlı (gerçek) tüm CATIA'ların açık dökümanlarını, hangi\n 427 |   // CATIA'da ve hangi sanal masaüstünde olduklarıyla birlikte\n 428 |   // `%LOCALAPPDATA%\\FikoCatia\\session.json`'a yazar. Dökümanların KENDİSİNİ\n 429 |   // kaydetmez (Document.Save çağırmaz) — kaydedilmemiş değişiklik varsa\n 430 |   // yalnızca uyarır; yanlışlıkla üzerine yazma riski olmasın.\n 431 |   // 📂 Geri yükle: her kayıtlı CATIA için (aynı masaüstündeki ya da aynı\n 432 |   // dökümanlara sahip) çalışan bir CATIA bulur, yoksa ayarlardaki\n 433 |   // başlatıcıyla yenisini açar; macro kaydolunca dökümanları native worker\n 434 |   // thread'de Documents.Open ile açar.\n 435 | \n 436 |   SessionSettings _settings = const SessionSettings();\n 437 |   SavedSession? _savedSession;\n 438 |   bool _restoreOfferDismissed = false;\n 439 |   bool _sessionBusy = false;\n 440 |   String? _sessionMessage;\n 441 |   bool _sessionMessageIsWarning = false;\n 442 | \n 443 |   SessionSettings get sessionSettings => _settings;\n 444 |   SavedSession? get savedSession => _savedSession;\n 445 |   bool get sessionBusy => _sessionBusy;\n 446 |   String? get sessionMessage => _sessionMessage;\n 447 |   bool get sessionMessageIsWarning => _sessionMessageIsWarning;\n 448 | \n 449 |   /// Native COM bridge'e gerçekten bağlı (mock olmayan) CATIA'lar.\n 450 |   List<CatiaInstance> get _liveInstances =>\n 451 |       _instances.where((i) => i.pid > 0 && i.connected).toList();\n 452 | \n 453 |   /// Bilgisayar yeni açıldığında (hiç bağlı CATIA yokken) kayıtlı oturum\n 454 |   /// varsa üstte \"geri yükle?\" bandı gösterilir.\n 455 |   bool get showRestoreOffer =>\n 456 |       _savedSession != null &&\n 457 |       !_restoreOfferDismissed &&\n 458 |       !_sessionBusy &&\n 459 |       _liveInstances.isEmpty;\n 460 | \n 461 |   void dismissRestoreOffer() {\n 462 |     _restoreOfferDismissed = true;\n 463 |     _safeNotify();\n 464 |   }\n 465 | \n 466 |   void dismissSessionMessage() {\n 467 |     _sessionMessage = null;\n 468 |     _safeNotify();\n 469 |   }\n 470 | \n 471 |   void _setSessionMessage(String? msg, {bool warn = false}) {\n 472 |     _sessionMessage = msg;\n 473 |     _sessionMessageIsWarning = warn;\n 474 |     _safeNotify();\n 475 |   }\n 476 | \n 477 |   Future<void> _initSession() async {\n 478 |     _settings = await SessionService.loadSettings();\n 479 |     // Kayıtlı izleme aralığını native poller'a uygula.\n 480 |     unawaited(NativeBridge.setPollInterval(_settings.pollIntervalMs));\n 481 |     _savedSession = await SessionService.loadSession();\n 482 |     _safeNotify();\n 483 |     if (selfTestSession) unawaited(_selfTestLoop());\n 484 |     if (_savedSession != null && _settings.autoRestore) {\n 485 |       // Zaten çalışan CATIA'ların (macro'ları) ilk push'larının gelmesi için\n 486 |       // kısa bir süre bekle; sonra eşleşmeyenler için CATIA başlatılır.\n 487 |       await Future<void>.delayed(const Duration(seconds: 6));\n 488 |       if (!_disposed && !_sessionBusy) unawaited(restoreSession(auto: true));\n 489 |     }\n 490 |   }\n 491 | \n 492 |   /// Teşhis bayrağı (`fikocatia.exe --selftest-session`): UI'a dokunmadan\n 493 |   /// oturum kaydet/geri yüklemeyi dosya komutlarıyla sınamaya yarar\n 494 |   /// (`%LOCALAPPDATA%\\FikoCatia\\selftest\\cmd_save|cmd_restore` dosyası\n 495 |   /// oluşturulunca ilgili işlem çalışır, sonuç `result_*.txt`'e yazılır).\n 496 |   /// Bayrak verilmedikçe hiçbir şey çalışmaz.\n 497 |   static bool selfTestSession = false;\n 498 | \n 499 |   Future<void> _selfTestLoop() async {\n 500 |     final base =\n 501 |         Platform.environment['LOCALAPPDATA'] ?? Directory.systemTemp.path;\n 502 |     final sep = Platform.pathSeparator;\n 503 |     final dir = Directory('$base${sep}FikoCatia${sep}selftest')\n 504 |       ..createSync(recursive: true);\n 505 |     debugPrint('SELFTEST: döngü başladı dir=${dir.path}');\n 506 |     while (!_disposed) {\n 507 |       for (final cmd in const ['save', 'restore']) {\n 508 |         final f = File('${dir.path}${sep}cmd_$cmd');\n 509 |         if (!f.existsSync()) continue;\n 510 |         try {\n 511 |           f.deleteSync();\n 512 |         } catch (_) {}\n 513 |         debugPrint('SELFTEST: $cmd başlıyor');\n 514 |         if (cmd == 'save') {\n 515 |           await saveSession();\n 516 |         } else {\n 517 |           await restoreSession();\n 518 |         }\n 519 |         File('${dir.path}${sep}result_$cmd.txt').writeAsStringSync(\n 520 |             '${_sessionMessageIsWarning ? 'WARN' : 'OK'}\\n'\n 521 |             '${_sessionMessage ?? ''}\\n');\n 522 |         debugPrint('SELFTEST: $cmd bitti: $_sessionMessage');\n 523 |       }\n 524 |       await Future<void>.delayed(const Duration(milliseconds: 500));\n 525 |     }\n 526 |   }\n 527 | \n 528 |   /// ⚙ Merkezi ayarlar penceresini (ayrı pencere) açar. Kaydet →\n 529 |   /// [updateSessionSettings] (diske yazar + native tarafa uygular).\n 530 |   Future<void> openSettingsWindow() async {\n 531 |     try {\n 532 |       await SettingsWindowService.open(\n 533 |         current: _settings,\n 534 |         onSave: updateSessionSettings,\n 535 |       );\n 536 |     } catch (e) {\n 537 |       _setSessionMessage('Ayarlar penceresi açılamadı: $e', warn: true);\n 538 |     }\n 539 |   }\n 540 | \n 541 |   Future<void> updateSessionSettings(SessionSettings s) async {\n 542 |     final intervalChanged = s.pollIntervalMs != _settings.pollIntervalMs;\n 543 |     _settings = s;\n 544 |     _safeNotify();\n 545 |     if (intervalChanged) {\n 546 |       unawaited(NativeBridge.setPollInterval(s.pollIntervalMs));\n 547 |     }\n 548 |     try {\n 549 |       await SessionService.saveSettings(s);\n 550 |     } catch (e) {\n 551 |       _setSessionMessage('Ayarlar kaydedilemedi: $e', warn: true);\n 552 |     }\n 553 |   }\n 554 | \n 555 |   /// GUID → \"M1/M2…\" numarası (desktopLabel ile aynı sıralama).\n 556 |   int _desktopOrderOf(String guid) => guid.isEmpty\n 557 |       ? 0\n 558 |       : _desktopIndexes.putIfAbsent(guid, () => _desktopIndexes.length + 1);\n 559 | \n 560 |   /// Bağlı (gerçek) tüm CATIA'ların anlık oturum görüntüsünü üretir; hiçbir\n 561 |   /// şey yazmaz. `notes`: kullanıcıya gösterilecek uyarılar (kaydedilmemiş\n 562 |   /// değişiklik, yolu olmayan yeni dökümanlar).\n 563 |   Future<({SavedSession session, List<String> notes})>\n 564 |       _snapshotLiveSession() async {\n 565 |     await refreshDesktops(); // masaüstü bilgisi en güncel haliyle alınsın\n 566 | \n 567 |     var skippedNoPath = 0;\n 568 |     var unsaved = 0;\n 569 |     final saved = <SavedInstance>[];\n 570 |     for (final i in _liveInstances) {\n 571 |       final docs = <SavedDocument>[];\n 572 |       for (final d in i.documents) {\n 573 |         if (d.path.isEmpty) {\n 574 |           // Henüz diske hiç kaydedilmemiş (yeni) döküman — yeniden açılamaz.\n 575 |           skippedNoPath++;\n 576 |           continue;\n 577 |         }\n 578 |         if (!d.saved) unsaved++;\n 579 |         docs.add(SavedDocument(\n 580 |           name: d.name,\n 581 |           path: d.path,\n 582 |           saved: d.saved,\n 583 |           isActive: d.isActive,\n 584 |         ));\n 585 |       }\n 586 |       saved.add(SavedInstance(\n 587 |         id: i.id,\n 588 |         title: i.title,\n 589 |         desktopGuid: i.desktopGuid,\n 590 |         desktopOrder: _desktopOrderOf(i.desktopGuid),\n 591 |         documents: docs,\n 592 |       ));\n 593 |     }\n 594 |     saved.sort((a, b) {\n 595 |       // Masaüstü sırasına göre (M1, M2, …; bilinmeyenler sona), sonra id.\n 596 |       int key(SavedInstance s) => s.desktopOrder == 0 ? 1 << 30 : s.desktopOrder;\n 597 |       final c = key(a).compareTo(key(b));\n 598 |       return c != 0 ? c : a.id.compareTo(b.id);\n 599 |     });\n 600 | \n 601 |     final notes = <String>[];\n 602 |     if (unsaved > 0) {\n 603 |       notes.add('⚠ $unsaved dökümanda kaydedilmemiş değişiklik var — dosyaların '\n 604 |           'kendisi kaydedilmedi');\n 605 |     }\n 606 |     if (skippedNoPath > 0) {\n 607 |       notes.add('$skippedNoPath yeni (diske hiç kaydedilmemiş) döküman atlandı');\n 608 |     }\n 609 |     return (\n 610 |       session: SavedSession(savedAt: DateTime.now(), instances: saved),\n 611 |       notes: notes,\n 612 |     );\n 613 |   }\n 614 | \n 615 |   /// \"Son oturum\" kopyası (`%LOCALAPPDATA%\\FikoCatia\\session.json`): açılışta\n 616 |   /// \"kayıtlı oturum var, geri yükle?\" teklifi ve otomatik geri yükleme bunu\n 617 |   /// kullanır. Kullanıcının seçtiği dosyadan bağımsızdır.\n 618 |   Future<void> _rememberLastSession(SavedSession session) async {\n 619 |     try {\n 620 |       await SessionService.saveSession(session);\n 621 |       _savedSession = session;\n 622 |       _restoreOfferDismissed = true;\n 623 |     } catch (e) {\n 624 |       debugPrint('son oturum kopyası yazılamadı: $e');\n 625 |     }\n 626 |   }\n 627 | \n 628 |   static String _fileNameOf(String path) => path.split(RegExp(r'[\\\\/]')).last;\n 629 | \n 630 |   /// 💾 (teşhis / eski akış) Oturumu sabit konuma (`session.json`) kaydeder.\n 631 |   /// Arayüzdeki 💾 artık [saveSessionToFile] kullanıyor.\n 632 |   Future<void> saveSession() async {\n 633 |     if (_sessionBusy) return;\n 634 |     if (_liveInstances.isEmpty) {\n 635 |       _setSessionMessage('Bağlı CATIA yok — kaydedilecek oturum bulunamadı.',\n 636 |           warn: true);\n 637 |       return;\n 638 |     }\n 639 |     final (:session, :notes) = await _snapshotLiveSession();\n 640 |     if (session.documentCount == 0) {\n 641 |       _setSessionMessage(\n 642 |           'Bağlı CATIA\\'larda kaydedilebilir (diskte yolu olan) döküman yok — '\n 643 |           'mevcut kayıt korundu.',\n 644 |           warn: true);\n 645 |       return;\n 646 |     }\n 647 |     try {\n 648 |       await SessionService.saveSession(session);\n 649 |     } catch (e) {\n 650 |       _setSessionMessage('Oturum kaydedilemedi: $e', warn: true);\n 651 |       return;\n 652 |     }\n 653 |     _savedSession = session;\n 654 |     _restoreOfferDismissed = true;\n 655 |     _setSessionMessage(\n 656 |       '💾 ${session.instances.length} CATIA · ${session.desktopCount} masaüstü · '\n 657 |       '${session.documentCount} döküman kaydedildi'\n 658 |       '${notes.isEmpty ? '' : '\\n${notes.join('\\n')}'}',\n 659 |       warn: notes.isNotEmpty,\n 660 |     );\n 661 |   }\n 662 | \n 663 |   /// 💾 Tüm bağlı CATIA'ların oturumunu, kullanıcının seçeceği bir dosyaya\n 664 |   /// kaydeder (sistem \"Farklı kaydet\" diyaloğu açılır).\n 665 |   Future<void> saveSessionToFile() async {\n 666 |     if (_sessionBusy) return;\n 667 |     if (_liveInstances.isEmpty) {\n 668 |       _setSessionMessage('Bağlı CATIA yok — kaydedilecek oturum bulunamadı.',\n 669 |           warn: true);\n 670 |       return;\n 671 |     }\n 672 |     final (:session, :notes) = await _snapshotLiveSession();\n 673 |     if (session.documentCount == 0) {\n 674 |       _setSessionMessage(\n 675 |           'Bağlı CATIA\\'larda kaydedilebilir (diskte yolu olan) döküman yok.',\n 676 |           warn: true);\n 677 |       return;\n 678 |     }\n 679 | \n 680 |     // Diyalog açıkken popup odak kaybında kapanmasın.\n 681 |     WindowTrayService.beginModal();\n 682 |     String? path;\n 683 |     try {\n 684 |       path = await NativeBridge.pickSaveFile(\n 685 |         title: 'Oturumu kaydet',\n 686 |         defaultName: SessionService.defaultFileName(),\n 687 |         filterName: 'FikoCATIA oturumu (*.${SessionService.fileExtension})',\n 688 |         extension: SessionService.fileExtension,\n 689 |         allFilesLabel: 'Tüm dosyalar (*.*)',\n 690 |       );\n 691 |     } catch (e) {\n 692 |       _setSessionMessage('$e', warn: true);\n 693 |       return;\n 694 |     } finally {\n 695 |       await WindowTrayService.endModal();\n 696 |     }\n 697 |     if (path == null) return; // kullanıcı vazgeçti\n 698 | \n 699 |     try {\n 700 |       await SessionService.saveToFile(session, path);\n 701 |     } catch (e) {\n 702 |       _setSessionMessage('Oturum dosyası yazılamadı: $e', warn: true);\n 703 |       return;\n 704 |     }\n 705 |     await _rememberLastSession(session);\n 706 | \n 707 |     _setSessionMessage(\n 708 |       '💾 ${_fileNameOf(path)} kaydedildi\\n'\n 709 |       '${session.instances.length} CATIA · ${session.desktopCount} masaüstü · '\n 710 |       '${session.documentCount} döküman'\n 711 |       '${notes.isEmpty ? '' : '\\n${notes.join('\\n')}'}',\n 712 |       warn: notes.isNotEmpty,\n 713 |     );\n 714 |   }\n 715 | \n 716 |   /// 📂 Oturum dosyası seç → ortada seçim penceresi aç → kullanıcının verdiği\n 717 |   /// plana göre (yeni CATIA / açık bir CATIA) dökümanları aç.\n 718 |   Future<void> openSessionFromFile() async {\n 719 |     if (_sessionBusy) return;\n 720 | \n 721 |     WindowTrayService.beginModal();\n 722 |     String? path;\n 723 |     try {\n 724 |       path = await NativeBridge.pickOpenFile(\n 725 |         title: 'Oturum dosyası aç',\n 726 |         filterName: 'FikoCATIA oturumu (*.${SessionService.fileExtension})',\n 727 |         extension: SessionService.fileExtension,\n 728 |         allFilesLabel: 'Tüm dosyalar (*.*)',\n 729 |       );\n 730 |     } catch (e) {\n 731 |       _setSessionMessage('$e', warn: true);\n 732 |       return;\n 733 |     } finally {\n 734 |       await WindowTrayService.endModal();\n 735 |     }\n 736 |     if (path == null) return; // kullanıcı vazgeçti\n 737 | \n 738 |     final SavedSession session;\n 739 |     try {\n 740 |       session = await SessionService.loadFromFile(path);\n 741 |     } on FormatException catch (e) {\n 742 |       _setSessionMessage('${_fileNameOf(path)}: ${e.message}', warn: true);\n 743 |       return;\n 744 |     } catch (e) {\n 745 |       _setSessionMessage('${_fileNameOf(path)} okunamadı: $e', warn: true);\n 746 |       return;\n 747 |     }\n 748 |     if (session.documentCount == 0) {\n 749 |       _setSessionMessage(\n 750 |           '${_fileNameOf(path)}: açılacak döküman bulunamadı.',\n 751 |           warn: true);\n 752 |       return;\n 753 |     }\n 754 | \n 755 |     await refreshDesktops(); // hedef listesinde güncel masaüstü etiketleri\n 756 |     final List<PickerPlanItem>? plan;\n 757 |     try {\n 758 |       plan = await SessionPickerService.pick(_pickerPayload(session, path));\n 759 |     } catch (e) {\n 760 |       _setSessionMessage('Seçim penceresi açılamadı: $e', warn: true);\n 761 |       return;\n 762 |     }\n 763 |     if (plan == null || plan.isEmpty) return; // vazgeçildi\n 764 |     await restorePlan(session, plan);\n 765 |   }\n 766 | \n 767 |   /// Seçim penceresine gönderilecek veri: oturumdaki CATIA grupları (önerilen\n 768 |   /// hedefle) + şu an açık (gerçek) CATIA'lar.\n 769 |   Map<String, dynamic> _pickerPayload(SavedSession s, String path) {\n 770 |     final assigned = <String>{};\n 771 |     final groups = <Map<String, dynamic>>[];\n 772 |     for (var i = 0; i < s.instances.length; i++) {\n 773 |       final g = s.instances[i];\n 774 |       // Aynı dökümanları / aynı masaüstünü taşıyan açık bir CATIA varsa\n 775 |       // varsayılan hedef odur; yoksa yeni CATIA.\n 776 |       final match = _matchLive(g, assigned);\n 777 |       if (match != null) assigned.add(match.id);\n 778 |       groups.add({\n 779 |         'index': i,\n 780 |         'title': g.title,\n 781 |         'desktop': g.desktopLabel,\n 782 |         'suggested': match?.id ?? 'new',\n 783 |         'docs': [\n 784 |           for (final d in g.documents)\n 785 |             if (d.path.isNotEmpty)\n 786 |               {\n 787 |                 'name': d.name,\n 788 |                 'fullPath': d.fullPath,\n 789 |                 'isActive': d.isActive,\n 790 |                 'saved': d.saved,\n 791 |               },\n 792 |         ],\n 793 |       });\n 794 |     }\n 795 |     return {\n 796 |       'fileName': _fileNameOf(path),\n 797 |       'savedAt': s.savedAt.toIso8601String(),\n 798 |       'canLaunch': _settings.catiaExe.trim().isNotEmpty,\n 799 |       'groups': groups,\n 800 |       'targets': [\n 801 |         for (final t in _liveInstances)\n 802 |           {\n 803 |             'id': t.id,\n 804 |             'title': t.title,\n 805 |             'desktop': desktopLabel(t.desktopGuid).replaceFirst('🖥 ', ''),\n 806 |             'docCount': t.documents.length,\n 807 |             'open': [\n 808 |               for (final d in t.documents) '${d.path}${d.name}'.toLowerCase(),\n 809 |             ],\n 810 |           },\n 811 |       ],\n 812 |     };\n 813 |   }\n 814 | \n 815 |   /// Seçim penceresinden gelen planı uygular: `new` hedefli her grup için yeni\n 816 |   /// bir CATIA başlatılır (ayarlardaki başlatıcıyla), diğerleri seçilen açık\n 817 |   /// CATIA'ya açtırılır. Aynı açık CATIA'yı hedefleyen gruplar tek işte\n 818 |   /// birleştirilir.\n 819 |   Future<void> restorePlan(SavedSession session, List<PickerPlanItem> plan) async {\n 820 |     if (_sessionBusy) {\n 821 |       _setSessionMessage('Başka bir oturum işlemi sürüyor, bitmesini bekleyin.',\n 822 |           warn: true);\n 823 |       return;\n 824 |     }\n 825 | \n 826 |     // Aynı açık CATIA'yı hedefleyen grupları birleştir (tek worker işi).\n 827 |     final entries = <_PlanEntry>[];\n 828 |     for (final item in plan) {\n 829 |       if (item.group < 0 || item.group >= session.instances.length) continue;\n 830 |       final saved = session.instances[item.group];\n 831 |       final byPath = {\n 832 |         for (final d in saved.documents) d.fullPath.toLowerCase(): d,\n 833 |       };\n 834 |       final docs = <SavedDocument>[\n 835 |         for (final p in item.paths)\n 836 |           if (byPath[p.toLowerCase()] != null) byPath[p.toLowerCase()]!,\n 837 |       ];\n 838 |       if (docs.isEmpty) continue;\n 839 |       if (item.target != 'new') {\n 840 |         final same = entries.where((e) => e.target == item.target).firstOrNull;\n 841 |         if (same != null) {\n 842 |           same.docs.addAll(docs);\n 843 |           continue;\n 844 |         }\n 845 |       }\n 846 |       entries.add(_PlanEntry(item.target, saved, docs));\n 847 |     }\n 848 |     if (entries.isEmpty) return;\n 849 | \n 850 |     _sessionBusy = true;\n 851 |     _restoreOfferDismissed = true;\n 852 |     // İlerleme bandı popup'ta görünüyor → popup'ı öne getir.\n 853 |     unawaited(WindowTrayService.current?.showWindow());\n 854 |     final tally = _RestoreTally();\n 855 |     final jobs = <Future<void>>[];\n 856 |     final total = entries.length;\n 857 | \n 858 |     try {\n 859 |       var n = 0;\n 860 |       for (final e in entries) {\n 861 |         n++;\n 862 |         final label = '${e.saved.title} (${e.saved.desktopLabel})';\n 863 |         final step = '[$n/$total] $label';\n 864 | \n 865 |         CatiaInstance? live;\n 866 |         var launched = false;\n 867 |         if (e.target == 'new') {\n 868 |           final before = _liveInstances.map((i) => i.id).toSet();\n 869 |           _setSessionMessage('⏳ $step: yeni CATIA başlatılıyor…');\n 870 |           final err = await _launchCatia();\n 871 |           if (err != null) {\n 872 |             tally.errors.add('$label: $err');\n 873 |             tally.failed += e.docs.length;\n 874 |             debugPrint('session plan: $label: $err');\n 875 |             continue;\n 876 |           }\n 877 |           tally.launched++;\n 878 |           launched = true;\n 879 |           _setSessionMessage(\n 880 |               '⏳ $step: CATIA açılıyor, macro kaydı bekleniyor…');\n 881 |           live = await _waitForNewInstance(before);\n 882 |           if (live == null) {\n 883 |             const msg = 'CATIA açıldı ama macro kayıt olmadı (zaman aşımı)';\n 884 |             tally.errors.add('$label: $msg');\n 885 |             tally.failed += e.docs.length;\n 886 |             debugPrint('session plan: $label: $msg');\n 887 |             continue;\n 888 |           }\n 889 |         } else {\n 890 |           live = _liveInstances.where((i) => i.id == e.target).firstOrNull;\n 891 |           if (live == null) {\n 892 |             final msg = '${e.target} artık bağlı değil';\n 893 |             tally.errors.add('$label: $msg');\n 894 |             tally.failed += e.docs.length;\n 895 |             debugPrint('session plan: $label: $msg');\n 896 |             continue;\n 897 |           }\n 898 |         }\n 899 | \n 900 |         // Yeni açılan CATIA'ya kayıtlı başlık + masaüstü uygulanır; kullanıcının\n 901 |         // kendi seçtiği açık bir CATIA'nın adına/masaüstüne dokunulmaz.\n 902 |         if (launched) {\n 903 |           if (e.saved.title != e.saved.id) renameInstance(live.id, e.saved.title);\n 904 |           await _tryMoveToDesktop(live, e.saved, tally);\n 905 |         }\n 906 | \n 907 |         // Aynı döküman iki kez seçilmişse teke indir; ürün → çizim → parça.\n 908 |         final seen = <String>{};\n 909 |         final docs = <SavedDocument>[\n 910 |           for (final d in e.docs)\n 911 |             if (seen.add(d.fullPath.toLowerCase())) d,\n 912 |         ];\n 913 |         final order = {for (var k = 0; k < docs.length; k++) docs[k]: k};\n 914 |         docs.sort((a, b) {\n 915 |           final c = a.openPriority.compareTo(b.openPriority);\n 916 |           return c != 0 ? c : order[a]!.compareTo(order[b]!);\n 917 |         });\n 918 |         final active = docs.where((d) => d.isActive).firstOrNull;\n 919 | \n 920 |         _setSessionMessage('⏳ $step: ${docs.length} döküman açtırılıyor…');\n 921 |         final jobId = await NativeBridge.openDocuments(\n 922 |           live.id,\n 923 |           [for (final d in docs) d.fullPath],\n 924 |           activate: active?.name,\n 925 |         );\n 926 |         if (jobId <= 0) {\n 927 |           final msg = 'döküman açma başlatılamadı (kod $jobId)';\n 928 |           tally.errors.add('$label: $msg');\n 929 |           tally.failed += docs.length;\n 930 |           debugPrint('session plan: $label: $msg');\n 931 |           continue;\n 932 |         }\n 933 |         jobs.add(_awaitOpenJob(jobId, label, tally));\n 934 |       }\n 935 | \n 936 |       if (jobs.isNotEmpty) {\n 937 |         _setSessionMessage('⏳ Dökümanlar CATIA\\'larda açılıyor… '\n 938 |             '(büyük montajlar dakikalar sürebilir)');\n 939 |         await Future.wait(jobs);\n 940 |       }\n 941 |     } catch (e, st) {\n 942 |       tally.errors.add('Beklenmeyen hata: $e');\n 943 |       debugPrint('session plan EXCEPTION: $e\\n$st');\n 944 |     } finally {\n 945 |       _sessionBusy = false;\n 946 |     }\n 947 |     _reportRestore(tally);\n 948 |   }\n 949 | \n 950 |   /// Geri yükleme sonuç özeti (banner mesajı).\n 951 |   void _reportRestore(_RestoreTally tally) {\n 952 |     final parts = <String>[\n 953 |       if (tally.launched > 0) '${tally.launched} CATIA başlatıldı',\n 954 |       '${tally.opened} döküman açıldı',\n 955 |       if (tally.alreadyOpen > 0) '${tally.alreadyOpen} zaten açıktı',\n 956 |       if (tally.moved > 0) '${tally.moved} CATIA masaüstüne taşındı',\n 957 |     ];\n 958 |     final problems = <String>[\n 959 |       if (tally.failed > 0) '${tally.failed} döküman açılamadı',\n 960 |       if (tally.desktopFailed > 0)\n 961 |         '${tally.desktopFailed} CATIA kayıtlı masaüstüne taşınamadı '\n 962 |             '(${tally.desktopHr ?? '?'}) — pencereyi elle sürükleyin',\n 963 |       ...tally.errors.take(3),\n 964 |     ];\n 965 |     final ok = problems.isEmpty;\n 966 |     _setSessionMessage(\n 967 |       '${ok ? '✅' : '⚠'} Geri yükleme bitti: ${parts.join(' · ')}'\n 968 |       '${ok ? '' : '\\n${problems.join('\\n')}'}',\n 969 |       warn: !ok,\n 970 |     );\n 971 |   }\n 972 | \n 973 |   /// 📂 Kayıtlı oturumu geri yükle.\n 974 |   Future<void> restoreSession({bool auto = false}) async {\n 975 |     if (_sessionBusy) return;\n 976 |     final session = _savedSession ?? await SessionService.loadSession();\n 977 |     if (session == null) {\n 978 |       _setSessionMessage('Kayıtlı oturum bulunamadı — önce 💾 ile kaydedin.',\n 979 |           warn: true);\n 980 |       return;\n 981 |     }\n 982 |     _sessionBusy = true;\n 983 |     _restoreOfferDismissed = true;\n 984 |     final tally = _RestoreTally();\n 985 |     final jobs = <Future<void>>[];\n 986 |     final total = session.instances.length;\n 987 | \n 988 |     try {\n 989 |       final assigned = <String>{};\n 990 |       var n = 0;\n 991 |       for (final saved in session.instances) {\n 992 |         n++;\n 993 |         final label = '${saved.title} (${saved.desktopLabel})';\n 994 |         final step = '[$n/$total] $label';\n 995 |         _setSessionMessage('⏳ $step: CATIA aranıyor…');\n 996 | \n 997 |         var live = _matchLive(saved, assigned);\n 998 |         if (live == null) {\n 999 |           final before = _liveInstances.map((i) => i.id).toSet();\n1000 |           _setSessionMessage('⏳ $step: CATIA başlatılıyor…');\n1001 |           final err = await _launchCatia();\n1002 |           if (err != null) {\n1003 |             tally.errors.add('$label: $err');\n1004 |             debugPrint('session restore: $label: $err');\n1005 |             continue;\n1006 |           }\n1007 |           tally.launched++;\n1008 |           _setSessionMessage(\n1009 |               '⏳ $step: CATIA açılıyor, macro kaydı bekleniyor…');\n1010 |           live = await _waitForNewInstance(before);\n1011 |           if (live == null) {\n1012 |             const msg = 'CATIA açıldı ama macro kayıt olmadı (zaman aşımı)';\n1013 |             tally.errors.add('$label: $msg');\n1014 |             debugPrint('session restore: $label: $msg');\n1015 |             continue;\n1016 |           }\n1017 |         }\n1018 |         assigned.add(live.id);\n1019 |         if (saved.title != saved.id) renameInstance(live.id, saved.title);\n1020 |         await _tryMoveToDesktop(live, saved, tally);\n1021 | \n1022 |         // Zaten açık olanları atla, kalanları ürün → çizim → parça sırasıyla aç.\n1023 |         final openNow = live.documents\n1024 |             .map((d) => '${d.path}${d.name}'.toLowerCase())\n1025 |             .toSet();\n1026 |         final restorable =\n1027 |             saved.documents.where((d) => d.path.isNotEmpty).toList();\n1028 |         final todoIdx = <int>[\n1029 |           for (var k = 0; k < restorable.length; k++)\n1030 |             if (!openNow.contains(restorable[k].fullPath.toLowerCase())) k,\n1031 |         ]..sort((a, b) {\n1032 |             final c = restorable[a]\n1033 |                 .openPriority\n1034 |                 .compareTo(restorable[b].openPriority);\n1035 |             return c != 0 ? c : a.compareTo(b);\n1036 |           });\n1037 |         tally.alreadyOpen += restorable.length - todoIdx.length;\n1038 |         if (todoIdx.isEmpty) continue;\n1039 | \n1040 |         final todo = [for (final k in todoIdx) restorable[k]];\n1041 |         final active = todo.where((d) => d.isActive).firstOrNull ??\n1042 |             restorable.where((d) => d.isActive).firstOrNull;\n1043 |         _setSessionMessage(\n1044 |             '⏳ $step: ${todo.length} döküman açtırılıyor…');\n1045 |         final jobId = await NativeBridge.openDocuments(\n1046 |           live.id,\n1047 |           [for (final d in todo) d.fullPath],\n1048 |           activate: active?.name,\n1049 |         );\n1050 |         if (jobId <= 0) {\n1051 |           final msg = 'döküman açma başlatılamadı (kod $jobId)';\n1052 |           tally.errors.add('$label: $msg');\n1053 |           tally.failed += todo.length;\n1054 |           debugPrint('session restore: $label: $msg');\n1055 |           continue;\n1056 |         }\n1057 |         jobs.add(_awaitOpenJob(jobId, label, tally));\n1058 |       }\n1059 | \n1060 |       if (jobs.isNotEmpty) {\n1061 |         _setSessionMessage('⏳ Dökümanlar CATIA\\'larda açılıyor… '\n1062 |             '(büyük montajlar dakikalar sürebilir)');\n1063 |         await Future.wait(jobs);\n1064 |       }\n1065 |     } catch (e, st) {\n1066 |       tally.errors.add('Beklenmeyen hata: $e');\n1067 |       debugPrint('session restore EXCEPTION: $e\\n$st');\n1068 |     } finally {\n1069 |       _sessionBusy = false;\n1070 |     }\n1071 |     _reportRestore(tally);\n1072 |   }\n1073 | \n1074 |   /// Kayıtlı CATIA'ya karşılık gelen çalışan (henüz atanmamış) CATIA'yı bul:\n1075 |   /// önce ortak dökümanı en çok olan, yoksa aynı sanal masaüstündeki.\n1076 |   CatiaInstance? _matchLive(SavedInstance saved, Set<String> assigned) {\n1077 |     final live =\n1078 |         _liveInstances.where((i) => !assigned.contains(i.id)).toList();\n1079 |     final savedPaths =\n1080 |         saved.documents.map((d) => d.fullPath.toLowerCase()).toSet();\n1081 |     CatiaInstance? best;\n1082 |     var bestScore = 0;\n1083 |     for (final i in live) {\n1084 |       final score = i.documents\n1085 |           .where((d) => savedPaths.contains('${d.path}${d.name}'.toLowerCase()))\n1086 |           .length;\n1087 |       if (score > bestScore) {\n1088 |         best = i;\n1089 |         bestScore = score;\n1090 |       }\n1091 |     }\n1092 |     if (best != null) return best;\n1093 |     if (saved.desktopGuid.isNotEmpty) {\n1094 |       for (final i in live) {\n1095 |         if (i.desktopGuid == saved.desktopGuid) return i;\n1096 |       }\n1097 |     }\n1098 |     return null;\n1099 |   }\n1100 | \n1101 |   /// Ayarlardaki başlatıcıyla yeni bir CATIA açar. Hata metni ya da null.\n1102 |   Future<String?> _launchCatia() async {\n1103 |     final exe = _settings.catiaExe.trim();\n1104 |     if (exe.isEmpty) {\n1105 |       return 'CATIA başlatıcı yolu ayarlı değil (Dosya ▸ Oturum ayarları)';\n1106 |     }\n1107 |     final file = File(exe);\n1108 |     if (!file.existsSync()) return 'Başlatıcı bulunamadı: $exe';\n1109 |     try {\n1110 |       final args = [\n1111 |         for (final m\n1112 |             in RegExp(r'\"([^\"]*)\"|(\\S+)').allMatches(_settings.catiaArgs))\n1113 |           m.group(1) ?? m.group(2)!,\n1114 |       ];\n1115 |       await Process.start(exe, args,\n1116 |           mode: ProcessStartMode.detached, workingDirectory: file.parent.path);\n1117 |       return null;\n1118 |     } catch (e) {\n1119 |       return 'CATIA başlatılamadı: $e';\n1120 |     }\n1121 |   }\n1122 | \n1123 |   /// Yeni başlatılan CATIA'nın macro'sunun COM'a kayıt olmasını bekler.\n1124 |   Future<CatiaInstance?> _waitForNewInstance(Set<String> before,\n1125 |       {Duration timeout = const Duration(minutes: 4)}) async {\n1126 |     final deadline = DateTime.now().add(timeout);\n1127 |     while (DateTime.now().isBefore(deadline) && !_disposed) {\n1128 |       for (final i in _liveInstances) {\n1129 |         if (!before.contains(i.id)) return i;\n1130 |       }\n1131 |       await Future<void>.delayed(const Duration(seconds: 1));\n1132 |     }\n1133 |     return null;\n1134 |   }\n1135 | \n1136 |   /// CATIA penceresini kayıtlı sanal masaüstüne taşımayı dener. Windows,\n1137 |   /// başka process'in penceresini taşımaya E_ACCESSDENIED ile izin\n1138 |   /// vermeyebilir; bu durumda sadece sayılır ve özette bildirilir.\n1139 |   Future<void> _tryMoveToDesktop(\n1140 |       CatiaInstance live, SavedInstance saved, _RestoreTally tally) async {\n1141 |     if (saved.desktopGuid.isEmpty || live.pid <= 0) return;\n1142 |     await refreshDesktops();\n1143 |     final cur = _instances.firstWhere((i) => i.id == live.id,\n1144 |         orElse: () => live);\n1145 |     if (cur.desktopGuid == saved.desktopGuid) return; // zaten doğru masaüstü\n1146 |     int? hr;\n1147 |     for (var attempt = 0; attempt < 5; attempt++) {\n1148 |       hr = await NativeBridge.moveWindowToDesktop(live.pid, saved.desktopGuid);\n1149 |       // 0x80070490 (ERROR_NOT_FOUND) = pencere henüz oluşmadı → biraz bekle.\n1150 |       if (hr != -2147023728) break;\n1151 |       await Future<void>.delayed(const Duration(seconds: 2));\n1152 |     }\n1153 |     if (hr == 0) {\n1154 |       tally.moved++;\n1155 |     } else {\n1156 |       tally.desktopFailed++;\n1157 |       tally.desktopHr ??= hr == null\n1158 |           ? 'yanıt yok'\n1159 |           : '0x${(hr & 0xFFFFFFFF).toRadixString(16).toUpperCase().padLeft(8, '0')}';\n1160 |       debugPrint('session restore: masaüstü taşıma ${live.id} -> '\n1161 |           '${saved.desktopGuid} hr=$hr');\n1162 |     }\n1163 |   }\n1164 | \n1165 |   /// Native worker'daki döküman açma işini bitene kadar yoklar.\n1166 |   Future<void> _awaitOpenJob(\n1167 |       int jobId, String label, _RestoreTally tally) async {\n1168 |     final deadline = DateTime.now().add(const Duration(minutes: 45));\n1169 |     while (!_disposed) {\n1170 |       final r = await NativeBridge.getOpenJob(jobId);\n1171 |       if (r != null && r['done'] == true) {\n1172 |         for (final e in (r['results'] as List? ?? const [])) {\n1173 |           final m = Map<String, dynamic>.from(e as Map);\n1174 |           final path = (m['path'] as String?) ?? '';\n1175 |           final err = (m['error'] as String?) ?? '';\n1176 |           if (m['ok'] == true) {\n1177 |             if (err == 'zaten açık') {\n1178 |               tally.alreadyOpen++;\n1179 |             } else {\n1180 |               tally.opened++;\n1181 |             }\n1182 |           } else {\n1183 |             tally.failed++;\n1184 |             final file = path.split(RegExp(r'[\\\\/]')).last;\n1185 |             tally.errors.add('$label: $file — $err');\n1186 |             debugPrint('session restore: $label: $path -> $err');\n1187 |           }\n1188 |         }\n1189 |         return;\n1190 |       }\n1191 |       if (DateTime.now().isAfter(deadline)) {\n1192 |         tally.errors.add('$label: döküman açma zaman aşımına uğradı');\n1193 |         return;\n1194 |       }\n1195 |       await Future<void>.delayed(const Duration(seconds: 1));\n1196 |     }\n1197 |   }\n1198 | \n1199 |   // ---- Döküman aksiyonları ----\n1200 | \n1201 |   /// 💾 Dökümanı kaydet (mock: saved=true yapar).\n1202 |   void saveDocument(String instanceId, String docId) {\n1203 |     _mutateDocument(instanceId, docId, (d) => d.copyWith(saved: true));\n1204 |   }\n1205 | \n1206 |   /// ❌ Dökümanı oturumdan kapat (mock: listeden düşürür).\n1207 |   void closeDocument(String instanceId, String docId) {\n1208 |     final i = _indexOf(instanceId);\n1209 |     if (i < 0) return;\n1210 |     final cur = _instances[i];\n1211 |     _instances[i] = cur.copyWith(\n1212 |       documents: cur.documents.where((d) => d.id != docId).toList(),\n1213 |     );\n1214 |     notifyListeners();\n1215 |   }\n1216 | \n1217 |   /// 📂 Dökümanın klasörünü Explorer'da aç (gerçek yol, mock'ta yoksa no-op).\n1218 |   Future<void> openDocumentFolder(\n1219 |       String instanceId, String docId) async {\n1220 |     final i = _indexOf(instanceId);\n1221 |     if (i < 0) return;\n1222 |     for (final d in _instances[i].documents) {\n1223 |       if (d.id == docId && d.path.isNotEmpty && Directory(d.path).existsSync()) {\n1224 |         try {\n1225 |           await Process.run('explorer.exe', [d.path]);\n1226 |         } catch (_) {}\n1227 |         return;\n1228 |       }\n1229 |     }\n1230 |   }\n1231 | \n1232 |   void _mutateDocument(\n1233 |       String instanceId, String docId, CatiaDocument Function(CatiaDocument) f) {\n1234 |     final i = _indexOf(instanceId);\n1235 |     if (i < 0) return;\n1236 |     final cur = _instances[i];\n1237 |     _instances[i] = cur.copyWith(\n1238 |       documents: cur.documents\n\n[Showing lines 97-1238 of 1280. Use start_line/end_line to read other sections.]",
              "success": true
            },
            {
              "query": "d:\\__sistem__\\Masaustu\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\lib\\services\\catia_provider.dart:290-EOF",
              "result": " 290 |   /// Bağlı bir CATIA'ysa (pid>0) GERÇEK pencere başlığını da değiştirir —\n 291 |   /// böylece CATIA'nın görev çubuğu/pencere başlıkları da aynı ismi taşır.\n 292 |   void renameInstance(String id, String newTitle) {\n 293 |     final i = _indexOf(id);\n 294 |     if (i < 0) return;\n 295 |     final t = newTitle.trim();\n 296 |     _instances[i] = t.isEmpty\n 297 |         ? _instances[i].copyWith(clearDisplayName: true)\n 298 |         : _instances[i].copyWith(displayName: t);\n 299 |     notifyListeners();\n 300 |     // CATIA pencere başlığını eşitle (boş bırakılırsa \"CATIA V5\"e dön).\n 301 |     final inst = _instances[i];\n 302 |     if (inst.pid > 0 && inst.connected) {\n 303 |       final label = t.isEmpty\n 304 |           ? (inst.documents.where((d) => d.isActive).isNotEmpty\n 305 |               ? inst.documents.firstWhere((d) => d.isActive).name\n 306 |               : '')\n 307 |           : t;\n 308 |       unawaited(NativeBridge.setInstanceWindowTitle(inst.pid, label));\n 309 |     }\n 310 |   }\n 311 | \n 312 |   /// ✏️ Geçmiş kaydının başlığını yeniden adlandır.\n 313 |   void renameHistory(String id, String newTitle) {\n 314 |     final i = _history.indexWhere((h) => h.id == id);\n 315 |     if (i < 0) return;\n 316 |     final t = newTitle.trim();\n 317 |     _history[i] = t.isEmpty\n 318 |         ? _history[i].copyWith(clearDisplayName: true)\n 319 |         : _history[i].copyWith(displayName: t);\n 320 |     notifyListeners();\n 321 |   }\n 322 | \n 323 |   // ---- Genel toolbar aksiyonları (üst menü altı) ----\n 324 | \n 325 |   /// ➕ Yeni CATIA ekle (mock: listeye boşta bir instance ekler).\n 326 |   void addInstance() {\n 327 |     final n = _instances.length + 1;\n 328 |     final id = 'CATIA-${n.toString().padLeft(2, '0')}';\n 329 |     if (_instances.any((i) => i.id == id)) {\n 330 |       _instances.add(CatiaInstance(\n 331 |         id: '$id-${DateTime.now().millisecond}',\n 332 |         fileName: '—',\n 333 |         status: CatiaStatus.idle,\n 334 |         uptime: Duration.zero,\n 335 |         pid: 0,\n 336 |         worker: 'Worker-$n',\n 337 |         connected: false,\n 338 |       ));\n 339 |     } else {\n 340 |       _instances.add(CatiaInstance(\n 341 |         id: id,\n 342 |         fileName: '—',\n 343 |         status: CatiaStatus.idle,\n 344 |         uptime: Duration.zero,\n 345 |         pid: 0,\n 346 |         worker: 'Worker-$n',\n 347 |         connected: false,\n 348 |       ));\n 349 |     }\n 350 |     notifyListeners();\n 351 |   }\n 352 | \n 353 |   /// ▶️ Tüm boşta instance'ları hesaplamaya al.\n 354 |   void startAll() {\n 355 |     var changed = false;\n 356 |     for (var i = 0; i < _instances.length; i++) {\n 357 |       final cur = _instances[i];\n 358 |       if (!cur.status.isBusy) {\n 359 |         _instances[i] = cur.copyWith(status: CatiaStatus.calculating, progress: 0);\n 360 |         changed = true;\n 361 |       }\n 362 |     }\n 363 |     if (changed) notifyListeners();\n 364 |   }\n 365 | \n 366 |   /// ⏹ Tüm süren işleri durdur.\n 367 |   void stopAll() {\n 368 |     var changed = false;\n 369 |     for (var i = 0; i < _instances.length; i++) {\n 370 |       final cur = _instances[i];\n 371 |       if (cur.status.isBusy) {\n 372 |         _instances[i] = cur.copyWith(status: CatiaStatus.idle, clearProgress: true);\n 373 |         changed = true;\n 374 |       }\n 375 |     }\n 376 |     if (changed) notifyListeners();\n 377 |   }\n 378 | \n 379 |   /// 🔄 Tüm listeyi yenile (mock: seçim + aramayı sıfırlar).\n 380 |   void refreshAll() {\n 381 |     _selectedInstanceId = null;\n 382 |     _query = '';\n 383 |     notifyListeners();\n 384 |   }\n 385 | \n 386 |   /// 🧹 Sorunlu olmayan boş kayıtları temizle (mock: bağlantısızları at).\n 387 |   void clearIdle() {\n 388 |     _instances.removeWhere((i) => !i.connected && !i.status.isBusy);\n 389 |     notifyListeners();\n 390 |   }\n 391 | \n 392 |   void setTab(HomeTab t) {\n 393 |     _tab = t;\n 394 |     notifyListeners();\n 395 |   }\n 396 | \n 397 |   // ---- Sanal masaüstü etiketi ----\n 398 | \n 399 |   final Map<String, int> _desktopIndexes = {};\n 400 | \n 401 |   /// GUID → \"M1/M2/…\" etiketi. Bilinmeyen GUID'ler sırayla numaralanır.\n 402 |   String desktopLabel(String guid) {\n 403 |     if (guid.isEmpty) return '🖥 M?';\n 404 |     final idx = _desktopIndexes.putIfAbsent(guid, () => _desktopIndexes.length + 1);\n 405 |     return '🖥 M$idx';\n 406 |   }\n 407 | \n 408 |   /// Gerçek CATIA pid'leri varsa native IVirtualDesktopManager'dan masaüstü\n 409 |   /// GUID'ini sorgular (mock pid=0'da sessizce atlanır).\n 410 |   Future<void> refreshDesktops() async {\n 411 |     var changed = false;\n 412 |     for (var i = 0; i < _instances.length; i++) {\n 413 |       final cur = _instances[i];\n 414 |       if (cur.pid <= 0) continue;\n 415 |       final guid = await NativeBridge.getWindowDesktopGuid(cur.pid);\n 416 |       if (guid != null && guid.isNotEmpty && guid != cur.desktopGuid) {\n 417 |         _instances[i] = cur.copyWith(desktopGuid: guid);\n 418 |         changed = true;\n 419 |       }\n 420 |     }\n 421 |     if (changed) notifyListeners();\n 422 |   }\n 423 | \n 424 |   // ---- Oturum kaydet / geri yükle (tüm bağlı CATIA'lar) ----\n 425 |   //\n 426 |   // 💾 Kaydet: bağlı (gerçek) tüm CATIA'ların açık dökümanlarını, hangi\n 427 |   // CATIA'da ve hangi sanal masaüstünde olduklarıyla birlikte\n 428 |   // `%LOCALAPPDATA%\\FikoCatia\\session.json`'a yazar. Dökümanların KENDİSİNİ\n 429 |   // kaydetmez (Document.Save çağırmaz) — kaydedilmemiş değişiklik varsa\n 430 |   // yalnızca uyarır; yanlışlıkla üzerine yazma riski olmasın.\n 431 |   // 📂 Geri yükle: her kayıtlı CATIA için (aynı masaüstündeki ya da aynı\n 432 |   // dökümanlara sahip) çalışan bir CATIA bulur, yoksa ayarlardaki\n 433 |   // başlatıcıyla yenisini açar; macro kaydolunca dökümanları native worker\n 434 |   // thread'de Documents.Open ile açar.\n 435 | \n 436 |   SessionSettings _settings = const SessionSettings();\n 437 |   SavedSession? _savedSession;\n 438 |   bool _restoreOfferDismissed = false;\n 439 |   bool _sessionBusy = false;\n 440 |   String? _sessionMessage;\n 441 |   bool _sessionMessageIsWarning = false;\n 442 | \n 443 |   SessionSettings get sessionSettings => _settings;\n 444 |   SavedSession? get savedSession => _savedSession;\n 445 |   bool get sessionBusy => _sessionBusy;\n 446 |   String? get sessionMessage => _sessionMessage;\n 447 |   bool get sessionMessageIsWarning => _sessionMessageIsWarning;\n 448 | \n 449 |   /// Native COM bridge'e gerçekten bağlı (mock olmayan) CATIA'lar.\n 450 |   List<CatiaInstance> get _liveInstances =>\n 451 |       _instances.where((i) => i.pid > 0 && i.connected).toList();\n 452 | \n 453 |   /// Bilgisayar yeni açıldığında (hiç bağlı CATIA yokken) kayıtlı oturum\n 454 |   /// varsa üstte \"geri yükle?\" bandı gösterilir.\n 455 |   bool get showRestoreOffer =>\n 456 |       _savedSession != null &&\n 457 |       !_restoreOfferDismissed &&\n 458 |       !_sessionBusy &&\n 459 |       _liveInstances.isEmpty;\n 460 | \n 461 |   void dismissRestoreOffer() {\n 462 |     _restoreOfferDismissed = true;\n 463 |     _safeNotify();\n 464 |   }\n 465 | \n 466 |   void dismissSessionMessage() {\n 467 |     _sessionMessage = null;\n 468 |     _safeNotify();\n 469 |   }\n 470 | \n 471 |   void _setSessionMessage(String? msg, {bool warn = false}) {\n 472 |     _sessionMessage = msg;\n 473 |     _sessionMessageIsWarning = warn;\n 474 |     _safeNotify();\n 475 |   }\n 476 | \n 477 |   Future<void> _initSession() async {\n 478 |     _settings = await SessionService.loadSettings();\n 479 |     // Kayıtlı izleme aralığını native poller'a uygula.\n 480 |     unawaited(NativeBridge.setPollInterval(_settings.pollIntervalMs));\n 481 |     _savedSession = await SessionService.loadSession();\n 482 |     _safeNotify();\n 483 |     if (selfTestSession) unawaited(_selfTestLoop());\n 484 |     if (_savedSession != null && _settings.autoRestore) {\n 485 |       // Zaten çalışan CATIA'ların (macro'ları) ilk push'larının gelmesi için\n 486 |       // kısa bir süre bekle; sonra eşleşmeyenler için CATIA başlatılır.\n 487 |       await Future<void>.delayed(const Duration(seconds: 6));\n 488 |       if (!_disposed && !_sessionBusy) unawaited(restoreSession(auto: true));\n 489 |     }\n 490 |   }\n 491 | \n 492 |   /// Teşhis bayrağı (`fikocatia.exe --selftest-session`): UI'a dokunmadan\n 493 |   /// oturum kaydet/geri yüklemeyi dosya komutlarıyla sınamaya yarar\n 494 |   /// (`%LOCALAPPDATA%\\FikoCatia\\selftest\\cmd_save|cmd_restore` dosyası\n 495 |   /// oluşturulunca ilgili işlem çalışır, sonuç `result_*.txt`'e yazılır).\n 496 |   /// Bayrak verilmedikçe hiçbir şey çalışmaz.\n 497 |   static bool selfTestSession = false;\n 498 | \n 499 |   Future<void> _selfTestLoop() async {\n 500 |     final base =\n 501 |         Platform.environment['LOCALAPPDATA'] ?? Directory.systemTemp.path;\n 502 |     final sep = Platform.pathSeparator;\n 503 |     final dir = Directory('$base${sep}FikoCatia${sep}selftest')\n 504 |       ..createSync(recursive: true);\n 505 |     debugPrint('SELFTEST: döngü başladı dir=${dir.path}');\n 506 |     while (!_disposed) {\n 507 |       for (final cmd in const ['save', 'restore']) {\n 508 |         final f = File('${dir.path}${sep}cmd_$cmd');\n 509 |         if (!f.existsSync()) continue;\n 510 |         try {\n 511 |           f.deleteSync();\n 512 |         } catch (_) {}\n 513 |         debugPrint('SELFTEST: $cmd başlıyor');\n 514 |         if (cmd == 'save') {\n 515 |           await saveSession();\n 516 |         } else {\n 517 |           await restoreSession();\n 518 |         }\n 519 |         File('${dir.path}${sep}result_$cmd.txt').writeAsStringSync(\n 520 |             '${_sessionMessageIsWarning ? 'WARN' : 'OK'}\\n'\n 521 |             '${_sessionMessage ?? ''}\\n');\n 522 |         debugPrint('SELFTEST: $cmd bitti: $_sessionMessage');\n 523 |       }\n 524 |       await Future<void>.delayed(const Duration(milliseconds: 500));\n 525 |     }\n 526 |   }\n 527 | \n 528 |   /// ⚙ Merkezi ayarlar penceresini (ayrı pencere) açar. Kaydet →\n 529 |   /// [updateSessionSettings] (diske yazar + native tarafa uygular).\n 530 |   Future<void> openSettingsWindow() async {\n 531 |     try {\n 532 |       await SettingsWindowService.open(\n 533 |         current: _settings,\n 534 |         onSave: updateSessionSettings,\n 535 |       );\n 536 |     } catch (e) {\n 537 |       _setSessionMessage('Ayarlar penceresi açılamadı: $e', warn: true);\n 538 |     }\n 539 |   }\n 540 | \n 541 |   Future<void> updateSessionSettings(SessionSettings s) async {\n 542 |     final intervalChanged = s.pollIntervalMs != _settings.pollIntervalMs;\n 543 |     _settings = s;\n 544 |     _safeNotify();\n 545 |     if (intervalChanged) {\n 546 |       unawaited(NativeBridge.setPollInterval(s.pollIntervalMs));\n 547 |     }\n 548 |     try {\n 549 |       await SessionService.saveSettings(s);\n 550 |     } catch (e) {\n 551 |       _setSessionMessage('Ayarlar kaydedilemedi: $e', warn: true);\n 552 |     }\n 553 |   }\n 554 | \n 555 |   /// GUID → \"M1/M2…\" numarası (desktopLabel ile aynı sıralama).\n 556 |   int _desktopOrderOf(String guid) => guid.isEmpty\n 557 |       ? 0\n 558 |       : _desktopIndexes.putIfAbsent(guid, () => _desktopIndexes.length + 1);\n 559 | \n 560 |   /// Bağlı (gerçek) tüm CATIA'ların anlık oturum görüntüsünü üretir; hiçbir\n 561 |   /// şey yazmaz. `notes`: kullanıcıya gösterilecek uyarılar (kaydedilmemiş\n 562 |   /// değişiklik, yolu olmayan yeni dökümanlar).\n 563 |   Future<({SavedSession session, List<String> notes})>\n 564 |       _snapshotLiveSession() async {\n 565 |     await refreshDesktops(); // masaüstü bilgisi en güncel haliyle alınsın\n 566 | \n 567 |     var skippedNoPath = 0;\n 568 |     var unsaved = 0;\n 569 |     final saved = <SavedInstance>[];\n 570 |     for (final i in _liveInstances) {\n 571 |       final docs = <SavedDocument>[];\n 572 |       for (final d in i.documents) {\n 573 |         if (d.path.isEmpty) {\n 574 |           // Henüz diske hiç kaydedilmemiş (yeni) döküman — yeniden açılamaz.\n 575 |           skippedNoPath++;\n 576 |           continue;\n 577 |         }\n 578 |         if (!d.saved) unsaved++;\n 579 |         docs.add(SavedDocument(\n 580 |           name: d.name,\n 581 |           path: d.path,\n 582 |           saved: d.saved,\n 583 |           isActive: d.isActive,\n 584 |         ));\n 585 |       }\n 586 |       saved.add(SavedInstance(\n 587 |         id: i.id,\n 588 |         title: i.title,\n 589 |         desktopGuid: i.desktopGuid,\n 590 |         desktopOrder: _desktopOrderOf(i.desktopGuid),\n 591 |         documents: docs,\n 592 |       ));\n 593 |     }\n 594 |     saved.sort((a, b) {\n 595 |       // Masaüstü sırasına göre (M1, M2, …; bilinmeyenler sona), sonra id.\n 596 |       int key(SavedInstance s) => s.desktopOrder == 0 ? 1 << 30 : s.desktopOrder;\n 597 |       final c = key(a).compareTo(key(b));\n 598 |       return c != 0 ? c : a.id.compareTo(b.id);\n 599 |     });\n 600 | \n 601 |     final notes = <String>[];\n 602 |     if (unsaved > 0) {\n 603 |       notes.add('⚠ $unsaved dökümanda kaydedilmemiş değişiklik var — dosyaların '\n 604 |           'kendisi kaydedilmedi');\n 605 |     }\n 606 |     if (skippedNoPath > 0) {\n 607 |       notes.add('$skippedNoPath yeni (diske hiç kaydedilmemiş) döküman atlandı');\n 608 |     }\n 609 |     return (\n 610 |       session: SavedSession(savedAt: DateTime.now(), instances: saved),\n 611 |       notes: notes,\n 612 |     );\n 613 |   }\n 614 | \n 615 |   /// \"Son oturum\" kopyası (`%LOCALAPPDATA%\\FikoCatia\\session.json`): açılışta\n 616 |   /// \"kayıtlı oturum var, geri yükle?\" teklifi ve otomatik geri yükleme bunu\n 617 |   /// kullanır. Kullanıcının seçtiği dosyadan bağımsızdır.\n 618 |   Future<void> _rememberLastSession(SavedSession session) async {\n 619 |     try {\n 620 |       await SessionService.saveSession(session);\n 621 |       _savedSession = session;\n 622 |       _restoreOfferDismissed = true;\n 623 |     } catch (e) {\n 624 |       debugPrint('son oturum kopyası yazılamadı: $e');\n 625 |     }\n 626 |   }\n 627 | \n 628 |   static String _fileNameOf(String path) => path.split(RegExp(r'[\\\\/]')).last;\n 629 | \n 630 |   /// 💾 (teşhis / eski akış) Oturumu sabit konuma (`session.json`) kaydeder.\n 631 |   /// Arayüzdeki 💾 artık [saveSessionToFile] kullanıyor.\n 632 |   Future<void> saveSession() async {\n 633 |     if (_sessionBusy) return;\n 634 |     if (_liveInstances.isEmpty) {\n 635 |       _setSessionMessage('Bağlı CATIA yok — kaydedilecek oturum bulunamadı.',\n 636 |           warn: true);\n 637 |       return;\n 638 |     }\n 639 |     final (:session, :notes) = await _snapshotLiveSession();\n 640 |     if (session.documentCount == 0) {\n 641 |       _setSessionMessage(\n 642 |           'Bağlı CATIA\\'larda kaydedilebilir (diskte yolu olan) döküman yok — '\n 643 |           'mevcut kayıt korundu.',\n 644 |           warn: true);\n 645 |       return;\n 646 |     }\n 647 |     try {\n 648 |       await SessionService.saveSession(session);\n 649 |     } catch (e) {\n 650 |       _setSessionMessage('Oturum kaydedilemedi: $e', warn: true);\n 651 |       return;\n 652 |     }\n 653 |     _savedSession = session;\n 654 |     _restoreOfferDismissed = true;\n 655 |     _setSessionMessage(\n 656 |       '💾 ${session.instances.length} CATIA · ${session.desktopCount} masaüstü · '\n 657 |       '${session.documentCount} döküman kaydedildi'\n 658 |       '${notes.isEmpty ? '' : '\\n${notes.join('\\n')}'}',\n 659 |       warn: notes.isNotEmpty,\n 660 |     );\n 661 |   }\n 662 | \n 663 |   /// 💾 Tüm bağlı CATIA'ların oturumunu, kullanıcının seçeceği bir dosyaya\n 664 |   /// kaydeder (sistem \"Farklı kaydet\" diyaloğu açılır).\n 665 |   Future<void> saveSessionToFile() async {\n 666 |     if (_sessionBusy) return;\n 667 |     if (_liveInstances.isEmpty) {\n 668 |       _setSessionMessage('Bağlı CATIA yok — kaydedilecek oturum bulunamadı.',\n 669 |           warn: true);\n 670 |       return;\n 671 |     }\n 672 |     final (:session, :notes) = await _snapshotLiveSession();\n 673 |     if (session.documentCount == 0) {\n 674 |       _setSessionMessage(\n 675 |           'Bağlı CATIA\\'larda kaydedilebilir (diskte yolu olan) döküman yok.',\n 676 |           warn: true);\n 677 |       return;\n 678 |     }\n 679 | \n 680 |     // Diyalog açıkken popup odak kaybında kapanmasın.\n 681 |     WindowTrayService.beginModal();\n 682 |     String? path;\n 683 |     try {\n 684 |       path = await NativeBridge.pickSaveFile(\n 685 |         title: 'Oturumu kaydet',\n 686 |         defaultName: SessionService.defaultFileName(),\n 687 |         filterName: 'FikoCATIA oturumu (*.${SessionService.fileExtension})',\n 688 |         extension: SessionService.fileExtension,\n 689 |         allFilesLabel: 'Tüm dosyalar (*.*)',\n 690 |       );\n 691 |     } catch (e) {\n 692 |       _setSessionMessage('$e', warn: true);\n 693 |       return;\n 694 |     } finally {\n 695 |       await WindowTrayService.endModal();\n 696 |     }\n 697 |     if (path == null) return; // kullanıcı vazgeçti\n 698 | \n 699 |     try {\n 700 |       await SessionService.saveToFile(session, path);\n 701 |     } catch (e) {\n 702 |       _setSessionMessage('Oturum dosyası yazılamadı: $e', warn: true);\n 703 |       return;\n 704 |     }\n 705 |     await _rememberLastSession(session);\n 706 | \n 707 |     _setSessionMessage(\n 708 |       '💾 ${_fileNameOf(path)} kaydedildi\\n'\n 709 |       '${session.instances.length} CATIA · ${session.desktopCount} masaüstü · '\n 710 |       '${session.documentCount} döküman'\n 711 |       '${notes.isEmpty ? '' : '\\n${notes.join('\\n')}'}',\n 712 |       warn: notes.isNotEmpty,\n 713 |     );\n 714 |   }\n 715 | \n 716 |   /// 📂 Oturum dosyası seç → ortada seçim penceresi aç → kullanıcının verdiği\n 717 |   /// plana göre (yeni CATIA / açık bir CATIA) dökümanları aç.\n 718 |   Future<void> openSessionFromFile() async {\n 719 |     if (_sessionBusy) return;\n 720 | \n 721 |     WindowTrayService.beginModal();\n 722 |     String? path;\n 723 |     try {\n 724 |       path = await NativeBridge.pickOpenFile(\n 725 |         title: 'Oturum dosyası aç',\n 726 |         filterName: 'FikoCATIA oturumu (*.${SessionService.fileExtension})',\n 727 |         extension: SessionService.fileExtension,\n 728 |         allFilesLabel: 'Tüm dosyalar (*.*)',\n 729 |       );\n 730 |     } catch (e) {\n 731 |       _setSessionMessage('$e', warn: true);\n 732 |       return;\n 733 |     } finally {\n 734 |       await WindowTrayService.endModal();\n 735 |     }\n 736 |     if (path == null) return; // kullanıcı vazgeçti\n 737 | \n 738 |     final SavedSession session;\n 739 |     try {\n 740 |       session = await SessionService.loadFromFile(path);\n 741 |     } on FormatException catch (e) {\n 742 |       _setSessionMessage('${_fileNameOf(path)}: ${e.message}', warn: true);\n 743 |       return;\n 744 |     } catch (e) {\n 745 |       _setSessionMessage('${_fileNameOf(path)} okunamadı: $e', warn: true);\n 746 |       return;\n 747 |     }\n 748 |     if (session.documentCount == 0) {\n 749 |       _setSessionMessage(\n 750 |           '${_fileNameOf(path)}: açılacak döküman bulunamadı.',\n 751 |           warn: true);\n 752 |       return;\n 753 |     }\n 754 | \n 755 |     await refreshDesktops(); // hedef listesinde güncel masaüstü etiketleri\n 756 |     final List<PickerPlanItem>? plan;\n 757 |     try {\n 758 |       plan = await SessionPickerService.pick(_pickerPayload(session, path));\n 759 |     } catch (e) {\n 760 |       _setSessionMessage('Seçim penceresi açılamadı: $e', warn: true);\n 761 |       return;\n 762 |     }\n 763 |     if (plan == null || plan.isEmpty) return; // vazgeçildi\n 764 |     await restorePlan(session, plan);\n 765 |   }\n 766 | \n 767 |   /// Seçim penceresine gönderilecek veri: oturumdaki CATIA grupları (önerilen\n 768 |   /// hedefle) + şu an açık (gerçek) CATIA'lar.\n 769 |   Map<String, dynamic> _pickerPayload(SavedSession s, String path) {\n 770 |     final assigned = <String>{};\n 771 |     final groups = <Map<String, dynamic>>[];\n 772 |     for (var i = 0; i < s.instances.length; i++) {\n 773 |       final g = s.instances[i];\n 774 |       // Aynı dökümanları / aynı masaüstünü taşıyan açık bir CATIA varsa\n 775 |       // varsayılan hedef odur; yoksa yeni CATIA.\n 776 |       final match = _matchLive(g, assigned);\n 777 |       if (match != null) assigned.add(match.id);\n 778 |       groups.add({\n 779 |         'index': i,\n 780 |         'title': g.title,\n 781 |         'desktop': g.desktopLabel,\n 782 |         'suggested': match?.id ?? 'new',\n 783 |         'docs': [\n 784 |           for (final d in g.documents)\n 785 |             if (d.path.isNotEmpty)\n 786 |               {\n 787 |                 'name': d.name,\n 788 |                 'fullPath': d.fullPath,\n 789 |                 'isActive': d.isActive,\n 790 |                 'saved': d.saved,\n 791 |               },\n 792 |         ],\n 793 |       });\n 794 |     }\n 795 |     return {\n 796 |       'fileName': _fileNameOf(path),\n 797 |       'savedAt': s.savedAt.toIso8601String(),\n 798 |       'canLaunch': _settings.catiaExe.trim().isNotEmpty,\n 799 |       'groups': groups,\n 800 |       'targets': [\n 801 |         for (final t in _liveInstances)\n 802 |           {\n 803 |             'id': t.id,\n 804 |             'title': t.title,\n 805 |             'desktop': desktopLabel(t.desktopGuid).replaceFirst('🖥 ', ''),\n 806 |             'docCount': t.documents.length,\n 807 |             'open': [\n 808 |               for (final d in t.documents) '${d.path}${d.name}'.toLowerCase(),\n 809 |             ],\n 810 |           },\n 811 |       ],\n 812 |     };\n 813 |   }\n 814 | \n 815 |   /// Seçim penceresinden gelen planı uygular: `new` hedefli her grup için yeni\n 816 |   /// bir CATIA başlatılır (ayarlardaki başlatıcıyla), diğerleri seçilen açık\n 817 |   /// CATIA'ya açtırılır. Aynı açık CATIA'yı hedefleyen gruplar tek işte\n 818 |   /// birleştirilir.\n 819 |   Future<void> restorePlan(SavedSession session, List<PickerPlanItem> plan) async {\n 820 |     if (_sessionBusy) {\n 821 |       _setSessionMessage('Başka bir oturum işlemi sürüyor, bitmesini bekleyin.',\n 822 |           warn: true);\n 823 |       return;\n 824 |     }\n 825 | \n 826 |     // Aynı açık CATIA'yı hedefleyen grupları birleştir (tek worker işi).\n 827 |     final entries = <_PlanEntry>[];\n 828 |     for (final item in plan) {\n 829 |       if (item.group < 0 || item.group >= session.instances.length) continue;\n 830 |       final saved = session.instances[item.group];\n 831 |       final byPath = {\n 832 |         for (final d in saved.documents) d.fullPath.toLowerCase(): d,\n 833 |       };\n 834 |       final docs = <SavedDocument>[\n 835 |         for (final p in item.paths)\n 836 |           if (byPath[p.toLowerCase()] != null) byPath[p.toLowerCase()]!,\n 837 |       ];\n 838 |       if (docs.isEmpty) continue;\n 839 |       if (item.target != 'new') {\n 840 |         final same = entries.where((e) => e.target == item.target).firstOrNull;\n 841 |         if (same != null) {\n 842 |           same.docs.addAll(docs);\n 843 |           continue;\n 844 |         }\n 845 |       }\n 846 |       entries.add(_PlanEntry(item.target, saved, docs));\n 847 |     }\n 848 |     if (entries.isEmpty) return;\n 849 | \n 850 |     _sessionBusy = true;\n 851 |     _restoreOfferDismissed = true;\n 852 |     // İlerleme bandı popup'ta görünüyor → popup'ı öne getir.\n 853 |     unawaited(WindowTrayService.current?.showWindow());\n 854 |     final tally = _RestoreTally();\n 855 |     final jobs = <Future<void>>[];\n 856 |     final total = entries.length;\n 857 | \n 858 |     try {\n 859 |       var n = 0;\n 860 |       for (final e in entries) {\n 861 |         n++;\n 862 |         final label = '${e.saved.title} (${e.saved.desktopLabel})';\n 863 |         final step = '[$n/$total] $label';\n 864 | \n 865 |         CatiaInstance? live;\n 866 |         var launched = false;\n 867 |         if (e.target == 'new') {\n 868 |           final before = _liveInstances.map((i) => i.id).toSet();\n 869 |           _setSessionMessage('⏳ $step: yeni CATIA başlatılıyor…');\n 870 |           final err = await _launchCatia();\n 871 |           if (err != null) {\n 872 |             tally.errors.add('$label: $err');\n 873 |             tally.failed += e.docs.length;\n 874 |             debugPrint('session plan: $label: $err');\n 875 |             continue;\n 876 |           }\n 877 |           tally.launched++;\n 878 |           launched = true;\n 879 |           _setSessionMessage(\n 880 |               '⏳ $step: CATIA açılıyor, macro kaydı bekleniyor…');\n 881 |           live = await _waitForNewInstance(before);\n 882 |           if (live == null) {\n 883 |             const msg = 'CATIA açıldı ama macro kayıt olmadı (zaman aşımı)';\n 884 |             tally.errors.add('$label: $msg');\n 885 |             tally.failed += e.docs.length;\n 886 |             debugPrint('session plan: $label: $msg');\n 887 |             continue;\n 888 |           }\n 889 |         } else {\n 890 |           live = _liveInstances.where((i) => i.id == e.target).firstOrNull;\n 891 |           if (live == null) {\n 892 |             final msg = '${e.target} artık bağlı değil';\n 893 |             tally.errors.add('$label: $msg');\n 894 |             tally.failed += e.docs.length;\n 895 |             debugPrint('session plan: $label: $msg');\n 896 |             continue;\n 897 |           }\n 898 |         }\n 899 | \n 900 |         // Yeni açılan CATIA'ya kayıtlı başlık + masaüstü uygulanır; kullanıcının\n 901 |         // kendi seçtiği açık bir CATIA'nın adına/masaüstüne dokunulmaz.\n 902 |         if (launched) {\n 903 |           if (e.saved.title != e.saved.id) renameInstance(live.id, e.saved.title);\n 904 |           await _tryMoveToDesktop(live, e.saved, tally);\n 905 |         }\n 906 | \n 907 |         // Aynı döküman iki kez seçilmişse teke indir; ürün → çizim → parça.\n 908 |         final seen = <String>{};\n 909 |         final docs = <SavedDocument>[\n 910 |           for (final d in e.docs)\n 911 |             if (seen.add(d.fullPath.toLowerCase())) d,\n 912 |         ];\n 913 |         final order = {for (var k = 0; k < docs.length; k++) docs[k]: k};\n 914 |         docs.sort((a, b) {\n 915 |           final c = a.openPriority.compareTo(b.openPriority);\n 916 |           return c != 0 ? c : order[a]!.compareTo(order[b]!);\n 917 |         });\n 918 |         final active = docs.where((d) => d.isActive).firstOrNull;\n 919 | \n 920 |         _setSessionMessage('⏳ $step: ${docs.length} döküman açtırılıyor…');\n 921 |         final jobId = await NativeBridge.openDocuments(\n 922 |           live.id,\n 923 |           [for (final d in docs) d.fullPath],\n 924 |           activate: active?.name,\n 925 |         );\n 926 |         if (jobId <= 0) {\n 927 |           final msg = 'döküman açma başlatılamadı (kod $jobId)';\n 928 |           tally.errors.add('$label: $msg');\n 929 |           tally.failed += docs.length;\n 930 |           debugPrint('session plan: $label: $msg');\n 931 |           continue;\n 932 |         }\n 933 |         jobs.add(_awaitOpenJob(jobId, label, tally));\n 934 |       }\n 935 | \n 936 |       if (jobs.isNotEmpty) {\n 937 |         _setSessionMessage('⏳ Dökümanlar CATIA\\'larda açılıyor… '\n 938 |             '(büyük montajlar dakikalar sürebilir)');\n 939 |         await Future.wait(jobs);\n 940 |       }\n 941 |     } catch (e, st) {\n 942 |       tally.errors.add('Beklenmeyen hata: $e');\n 943 |       debugPrint('session plan EXCEPTION: $e\\n$st');\n 944 |     } finally {\n 945 |       _sessionBusy = false;\n 946 |     }\n 947 |     _reportRestore(tally);\n 948 |   }\n 949 | \n 950 |   /// Geri yükleme sonuç özeti (banner mesajı).\n 951 |   void _reportRestore(_RestoreTally tally) {\n 952 |     final parts = <String>[\n 953 |       if (tally.launched > 0) '${tally.launched} CATIA başlatıldı',\n 954 |       '${tally.opened} döküman açıldı',\n 955 |       if (tally.alreadyOpen > 0) '${tally.alreadyOpen} zaten açıktı',\n 956 |       if (tally.moved > 0) '${tally.moved} CATIA masaüstüne taşındı',\n 957 |     ];\n 958 |     final problems = <String>[\n 959 |       if (tally.failed > 0) '${tally.failed} döküman açılamadı',\n 960 |       if (tally.desktopFailed > 0)\n 961 |         '${tally.desktopFailed} CATIA kayıtlı masaüstüne taşınamadı '\n 962 |             '(${tally.desktopHr ?? '?'}) — pencereyi elle sürükleyin',\n 963 |       ...tally.errors.take(3),\n 964 |     ];\n 965 |     final ok = problems.isEmpty;\n 966 |     _setSessionMessage(\n 967 |       '${ok ? '✅' : '⚠'} Geri yükleme bitti: ${parts.join(' · ')}'\n 968 |       '${ok ? '' : '\\n${problems.join('\\n')}'}',\n 969 |       warn: !ok,\n 970 |     );\n 971 |   }\n 972 | \n 973 |   /// 📂 Kayıtlı oturumu geri yükle.\n 974 |   Future<void> restoreSession({bool auto = false}) async {\n 975 |     if (_sessionBusy) return;\n 976 |     final session = _savedSession ?? await SessionService.loadSession();\n 977 |     if (session == null) {\n 978 |       _setSessionMessage('Kayıtlı oturum bulunamadı — önce 💾 ile kaydedin.',\n 979 |           warn: true);\n 980 |       return;\n 981 |     }\n 982 |     _sessionBusy = true;\n 983 |     _restoreOfferDismissed = true;\n 984 |     final tally = _RestoreTally();\n 985 |     final jobs = <Future<void>>[];\n 986 |     final total = session.instances.length;\n 987 | \n 988 |     try {\n 989 |       final assigned = <String>{};\n 990 |       var n = 0;\n 991 |       for (final saved in session.instances) {\n 992 |         n++;\n 993 |         final label = '${saved.title} (${saved.desktopLabel})';\n 994 |         final step = '[$n/$total] $label';\n 995 |         _setSessionMessage('⏳ $step: CATIA aranıyor…');\n 996 | \n 997 |         var live = _matchLive(saved, assigned);\n 998 |         if (live == null) {\n 999 |           final before = _liveInstances.map((i) => i.id).toSet();\n1000 |           _setSessionMessage('⏳ $step: CATIA başlatılıyor…');\n1001 |           final err = await _launchCatia();\n1002 |           if (err != null) {\n1003 |             tally.errors.add('$label: $err');\n1004 |             debugPrint('session restore: $label: $err');\n1005 |             continue;\n1006 |           }\n1007 |           tally.launched++;\n1008 |           _setSessionMessage(\n1009 |               '⏳ $step: CATIA açılıyor, macro kaydı bekleniyor…');\n1010 |           live = await _waitForNewInstance(before);\n1011 |           if (live == null) {\n1012 |             const msg = 'CATIA açıldı ama macro kayıt olmadı (zaman aşımı)';\n1013 |             tally.errors.add('$label: $msg');\n1014 |             debugPrint('session restore: $label: $msg');\n1015 |             continue;\n1016 |           }\n1017 |         }\n1018 |         assigned.add(live.id);\n1019 |         if (saved.title != saved.id) renameInstance(live.id, saved.title);\n1020 |         await _tryMoveToDesktop(live, saved, tally);\n1021 | \n1022 |         // Zaten açık olanları atla, kalanları ürün → çizim → parça sırasıyla aç.\n1023 |         final openNow = live.documents\n1024 |             .map((d) => '${d.path}${d.name}'.toLowerCase())\n1025 |             .toSet();\n1026 |         final restorable =\n1027 |             saved.documents.where((d) => d.path.isNotEmpty).toList();\n1028 |         final todoIdx = <int>[\n1029 |           for (var k = 0; k < restorable.length; k++)\n1030 |             if (!openNow.contains(restorable[k].fullPath.toLowerCase())) k,\n1031 |         ]..sort((a, b) {\n1032 |             final c = restorable[a]\n1033 |                 .openPriority\n1034 |                 .compareTo(restorable[b].openPriority);\n1035 |             return c != 0 ? c : a.compareTo(b);\n1036 |           });\n1037 |         tally.alreadyOpen += restorable.length - todoIdx.length;\n1038 |         if (todoIdx.isEmpty) continue;\n1039 | \n1040 |         final todo = [for (final k in todoIdx) restorable[k]];\n1041 |         final active = todo.where((d) => d.isActive).firstOrNull ??\n1042 |             restorable.where((d) => d.isActive).firstOrNull;\n1043 |         _setSessionMessage(\n1044 |             '⏳ $step: ${todo.length} döküman açtırılıyor…');\n1045 |         final jobId = await NativeBridge.openDocuments(\n1046 |           live.id,\n1047 |           [for (final d in todo) d.fullPath],\n1048 |           activate: active?.name,\n1049 |         );\n1050 |         if (jobId <= 0) {\n1051 |           final msg = 'döküman açma başlatılamadı (kod $jobId)';\n1052 |           tally.errors.add('$label: $msg');\n1053 |           tally.failed += todo.length;\n1054 |           debugPrint('session restore: $label: $msg');\n1055 |           continue;\n1056 |         }\n1057 |         jobs.add(_awaitOpenJob(jobId, label, tally));\n1058 |       }\n1059 | \n1060 |       if (jobs.isNotEmpty) {\n1061 |         _setSessionMessage('⏳ Dökümanlar CATIA\\'larda açılıyor… '\n1062 |             '(büyük montajlar dakikalar sürebilir)');\n1063 |         await Future.wait(jobs);\n1064 |       }\n1065 |     } catch (e, st) {\n1066 |       tally.errors.add('Beklenmeyen hata: $e');\n1067 |       debugPrint('session restore EXCEPTION: $e\\n$st');\n1068 |     } finally {\n1069 |       _sessionBusy = false;\n1070 |     }\n1071 |     _reportRestore(tally);\n1072 |   }\n1073 | \n1074 |   /// Kayıtlı CATIA'ya karşılık gelen çalışan (henüz atanmamış) CATIA'yı bul:\n1075 |   /// önce ortak dökümanı en çok olan, yoksa aynı sanal masaüstündeki.\n1076 |   CatiaInstance? _matchLive(SavedInstance saved, Set<String> assigned) {\n1077 |     final live =\n1078 |         _liveInstances.where((i) => !assigned.contains(i.id)).toList();\n1079 |     final savedPaths =\n1080 |         saved.documents.map((d) => d.fullPath.toLowerCase()).toSet();\n1081 |     CatiaInstance? best;\n1082 |     var bestScore = 0;\n1083 |     for (final i in live) {\n1084 |       final score = i.documents\n1085 |           .where((d) => savedPaths.contains('${d.path}${d.name}'.toLowerCase()))\n1086 |           .length;\n1087 |       if (score > bestScore) {\n1088 |         best = i;\n1089 |         bestScore = score;\n1090 |       }\n1091 |     }\n1092 |     if (best != null) return best;\n1093 |     if (saved.desktopGuid.isNotEmpty) {\n1094 |       for (final i in live) {\n1095 |         if (i.desktopGuid == saved.desktopGuid) return i;\n1096 |       }\n1097 |     }\n1098 |     return null;\n1099 |   }\n1100 | \n1101 |   /// Ayarlardaki başlatıcıyla yeni bir CATIA açar. Hata metni ya da null.\n1102 |   Future<String?> _launchCatia() async {\n1103 |     final exe = _settings.catiaExe.trim();\n1104 |     if (exe.isEmpty) {\n1105 |       return 'CATIA başlatıcı yolu ayarlı değil (Dosya ▸ Oturum ayarları)';\n1106 |     }\n1107 |     final file = File(exe);\n1108 |     if (!file.existsSync()) return 'Başlatıcı bulunamadı: $exe';\n1109 |     try {\n1110 |       final args = [\n1111 |         for (final m\n1112 |             in RegExp(r'\"([^\"]*)\"|(\\S+)').allMatches(_settings.catiaArgs))\n1113 |           m.group(1) ?? m.group(2)!,\n1114 |       ];\n1115 |       await Process.start(exe, args,\n1116 |           mode: ProcessStartMode.detached, workingDirectory: file.parent.path);\n1117 |       return null;\n1118 |     } catch (e) {\n1119 |       return 'CATIA başlatılamadı: $e';\n1120 |     }\n1121 |   }\n1122 | \n1123 |   /// Yeni başlatılan CATIA'nın macro'sunun COM'a kayıt olmasını bekler.\n1124 |   Future<CatiaInstance?> _waitForNewInstance(Set<String> before,\n1125 |       {Duration timeout = const Duration(minutes: 4)}) async {\n1126 |     final deadline = DateTime.now().add(timeout);\n1127 |     while (DateTime.now().isBefore(deadline) && !_disposed) {\n1128 |       for (final i in _liveInstances) {\n1129 |         if (!before.contains(i.id)) return i;\n1130 |       }\n1131 |       await Future<void>.delayed(const Duration(seconds: 1));\n1132 |     }\n1133 |     return null;\n1134 |   }\n1135 | \n1136 |   /// CATIA penceresini kayıtlı sanal masaüstüne taşımayı dener. Windows,\n1137 |   /// başka process'in penceresini taşımaya E_ACCESSDENIED ile izin\n1138 |   /// vermeyebilir; bu durumda sadece sayılır ve özette bildirilir.\n1139 |   Future<void> _tryMoveToDesktop(\n1140 |       CatiaInstance live, SavedInstance saved, _RestoreTally tally) async {\n1141 |     if (saved.desktopGuid.isEmpty || live.pid <= 0) return;\n1142 |     await refreshDesktops();\n1143 |     final cur = _instances.firstWhere((i) => i.id == live.id,\n1144 |         orElse: () => live);\n1145 |     if (cur.desktopGuid == saved.desktopGuid) return; // zaten doğru masaüstü\n1146 |     int? hr;\n1147 |     for (var attempt = 0; attempt < 5; attempt++) {\n1148 |       hr = await NativeBridge.moveWindowToDesktop(live.pid, saved.desktopGuid);\n1149 |       // 0x80070490 (ERROR_NOT_FOUND) = pencere henüz oluşmadı → biraz bekle.\n1150 |       if (hr != -2147023728) break;\n1151 |       await Future<void>.delayed(const Duration(seconds: 2));\n1152 |     }\n1153 |     if (hr == 0) {\n1154 |       tally.moved++;\n1155 |     } else {\n1156 |       tally.desktopFailed++;\n1157 |       tally.desktopHr ??= hr == null\n1158 |           ? 'yanıt yok'\n1159 |           : '0x${(hr & 0xFFFFFFFF).toRadixString(16).toUpperCase().padLeft(8, '0')}';\n1160 |       debugPrint('session restore: masaüstü taşıma ${live.id} -> '\n1161 |           '${saved.desktopGuid} hr=$hr');\n1162 |     }\n1163 |   }\n1164 | \n1165 |   /// Native worker'daki döküman açma işini bitene kadar yoklar.\n1166 |   Future<void> _awaitOpenJob(\n1167 |       int jobId, String label, _RestoreTally tally) async {\n1168 |     final deadline = DateTime.now().add(const Duration(minutes: 45));\n1169 |     while (!_disposed) {\n1170 |       final r = await NativeBridge.getOpenJob(jobId);\n1171 |       if (r != null && r['done'] == true) {\n1172 |         for (final e in (r['results'] as List? ?? const [])) {\n1173 |           final m = Map<String, dynamic>.from(e as Map);\n1174 |           final path = (m['path'] as String?) ?? '';\n1175 |           final err = (m['error'] as String?) ?? '';\n1176 |           if (m['ok'] == true) {\n1177 |             if (err == 'zaten açık') {\n1178 |               tally.alreadyOpen++;\n1179 |             } else {\n1180 |               tally.opened++;\n1181 |             }\n1182 |           } else {\n1183 |             tally.failed++;\n1184 |             final file = path.split(RegExp(r'[\\\\/]')).last;\n1185 |             tally.errors.add('$label: $file — $err');\n1186 |             debugPrint('session restore: $label: $path -> $err');\n1187 |           }\n1188 |         }\n1189 |         return;\n1190 |       }\n1191 |       if (DateTime.now().isAfter(deadline)) {\n1192 |         tally.errors.add('$label: döküman açma zaman aşımına uğradı');\n1193 |         return;\n1194 |       }\n1195 |       await Future<void>.delayed(const Duration(seconds: 1));\n1196 |     }\n1197 |   }\n1198 | \n1199 |   // ---- Döküman aksiyonları ----\n1200 | \n1201 |   /// 💾 Dökümanı kaydet (mock: saved=true yapar).\n1202 |   void saveDocument(String instanceId, String docId) {\n1203 |     _mutateDocument(instanceId, docId, (d) => d.copyWith(saved: true));\n1204 |   }\n1205 | \n1206 |   /// ❌ Dökümanı oturumdan kapat (mock: listeden düşürür).\n1207 |   void closeDocument(String instanceId, String docId) {\n1208 |     final i = _indexOf(instanceId);\n1209 |     if (i < 0) return;\n1210 |     final cur = _instances[i];\n1211 |     _instances[i] = cur.copyWith(\n1212 |       documents: cur.documents.where((d) => d.id != docId).toList(),\n1213 |     );\n1214 |     notifyListeners();\n1215 |   }\n1216 | \n1217 |   /// 📂 Dökümanın klasörünü Explorer'da aç (gerçek yol, mock'ta yoksa no-op).\n1218 |   Future<void> openDocumentFolder(\n1219 |       String instanceId, String docId) async {\n1220 |     final i = _indexOf(instanceId);\n1221 |     if (i < 0) return;\n1222 |     for (final d in _instances[i].documents) {\n1223 |       if (d.id == docId && d.path.isNotEmpty && Directory(d.path).existsSync()) {\n1224 |         try {\n1225 |           await Process.run('explorer.exe', [d.path]);\n1226 |         } catch (_) {}\n1227 |         return;\n1228 |       }\n1229 |     }\n1230 |   }\n1231 | \n1232 |   void _mutateDocument(\n1233 |       String instanceId, String docId, CatiaDocument Function(CatiaDocument) f) {\n1234 |     final i = _indexOf(instanceId);\n1235 |     if (i < 0) return;\n1236 |     final cur = _instances[i];\n1237 |     _instances[i] = cur.copyWith(\n1238 |       documents: cur.documents\n1239 |           .map((d) => d.id == docId ? f(d) : d)\n1240 |           .toList(),\n1241 |     );\n1242 |     notifyListeners();\n1243 |   }\n1244 | \n1245 |   void setQuery(String q) {\n1246 |     _query = q;\n1247 |     notifyListeners();\n1248 |   }\n1249 | \n1250 |   void selectInstance(String id) {\n1251 |     _selectedInstanceId = id;\n1252 |     notifyListeners();\n1253 |   }\n1254 | \n1255 |   void clearSelection() {\n1256 |     _selectedInstanceId = null;\n1257 |     notifyListeners();\n1258 |   }\n1259 | }\n1260 | \n1261 | /// Seçim penceresi planının uygulanabilir tek bir adımı: bir hedef (yeni CATIA\n1262 | /// ya da açık bir CATIA) ve ona açılacak dökümanlar.\n1263 | class _PlanEntry {\n1264 |   final String target; // 'new' ya da açık CATIA id'si\n1265 |   final SavedInstance saved; // etiket/başlık/masaüstü bilgisi için\n1266 |   final List<SavedDocument> docs;\n1267 |   _PlanEntry(this.target, this.saved, this.docs);\n1268 | }\n1269 | \n1270 | /// Bir geri yükleme çalışmasının sayaçları (özet mesajı için).\n1271 | class _RestoreTally {\n1272 |   int launched = 0; // yeni başlatılan CATIA\n1273 |   int opened = 0; // açılan döküman\n1274 |   int alreadyOpen = 0; // zaten açık olduğu için atlanan döküman\n1275 |   int failed = 0; // açılamayan döküman\n1276 |   int moved = 0; // kayıtlı masaüstüne taşınan CATIA\n1277 |   int desktopFailed = 0; // taşınamayan CATIA\n1278 |   String? desktopHr; // ilk taşıma hatasının HRESULT'ı\n1279 |   final List<String> errors = [];\n1280 | }",
              "success": true
            },
            {
              "query": "d:\\__sistem__\\Masaustu\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\lib\\services\\native_bridge.dart:1-EOF",
              "result": "  1 | import 'package:flutter/services.dart';\n  2 | \n  3 | /// Native (Windows runner) köprüsü.\n  4 | ///\n  5 | /// NEDEN GEREKLİ: `desktop_multi_window` yeni bir alt pencere açtığında\n  6 | /// window_manager / tray_manager / screen_retriever plugin'leri child\n  7 | /// engine'in registrar'ı ile yeniden kaydedilir. Bu plugin'lerin native\n  8 | /// tarafında kanal **global** bir değişkende tutulur; global değişken child\n  9 | /// engine'in messenger'ına kayar. Sonuç:\n 10 | ///   • ana pencereden `windowManager.show()/hide()/isVisible()` çağrıları\n 11 | ///     yanıtsız (asılı) kalır → sağ alttaki tray popup bir daha açılmaz,\n 12 | ///   • tray event'leri ana isolate'e ulaşmaz.\n 13 | ///\n 14 | /// Runner'daki `fikocatia/native` kanalı, bu üç plugin'i ANA engine'in\n 15 | /// registrar'ı üzerinde yeniden kaydederek kanalları ana pencereye geri\n 16 | /// bağlar (desktop_multi_window'a dokunmaz, o plugin'i tekrar kaydetmek\n 17 | /// alt pencereyi çift sahiplenip 0xc0000005 çökmesi üretiyordu).\n 18 | class NativeBridge {\n 19 |   static const MethodChannel _channel = MethodChannel('fikocatia/native');\n 20 | \n 21 |   /// Native taraf henüz/artık yoksa tekrar tekrar denemeyelim.\n 22 |   static bool _unsupported = false;\n 23 | \n 24 |   /// CATScript macro'sunun CATIA içinden `CreateObject(\"FikoCatia.Bridge\")`\n 25 |   /// ile eriştiği COM nesnesi artık ayrı bir C# process'te değil, bu exe'nin\n 26 |   /// kendi native (Windows runner) tarafında barındırılıyor (bkz.\n 27 |   /// windows/runner/com_bridge.h). RegisterInstance/UpdateStatus/Unregister\n 28 |   /// her çağrıldığında native taraf `instancesSnapshot` metodunu bu kanaldan\n 29 |   /// push eder — dosya/polling yok, gecikme yok.\n 30 |   ///\n 31 |   /// `onSnapshot`'a gelen liste elemanları server JSON şemasıyla aynı alan\n 32 |   /// adlarını taşır: id, fileName, status, progress, pid, worker, connected,\n 33 |   /// desktopGuid, lastError, registeredAtUtc.\n 34 |   static void listenInstancesSnapshot(\n 35 |       void Function(List<dynamic> snapshot) onSnapshot) {\n 36 |     _channel.setMethodCallHandler((call) async {\n 37 |       if (call.method == 'instancesSnapshot') {\n 38 |         final args = call.arguments;\n 39 |         if (args is List) onSnapshot(args);\n 40 |       }\n 41 |       return null;\n 42 |     });\n 43 |   }\n 44 | \n 45 |   /// Açılışta (ilk push henüz gelmeden önce) mevcut anlık görüntüyü bir\n 46 |   /// kerelik sorgular. Native taraf henüz hiç instance kaydolmadıysa boş\n 47 |   /// liste döner.\n 48 |   static Future<List<dynamic>> getInstancesSnapshot() async {\n 49 |     try {\n 50 |       final result = await _channel\n 51 |           .invokeMethod<List<dynamic>>('getInstancesSnapshot')\n 52 |           .timeout(const Duration(milliseconds: 700));\n 53 |       return result ?? const [];\n 54 |     } on MissingPluginException {\n 55 |       return const [];\n 56 |     } catch (_) {\n 57 |       return const [];\n 58 |     }\n 59 |   }\n 60 | \n 61 |   /// Oturum geri yükleme: `instanceId` (\"CATIA-01\") ile kayıtlı CATIA'da\n 62 |   /// `paths`'teki dosyaları izole bir native worker thread'de açtırır.\n 63 |   /// Dönüş: jobId (>0) ya da hata (-1: instance yok, -2: CATIA referansı\n 64 |   /// yok, 0: native cevap vermedi).\n 65 |   static Future<int> openDocuments(String instanceId, List<String> paths,\n 66 |       {String? activate}) async {\n 67 |     try {\n 68 |       final r = await _channel.invokeMethod<int>('openDocuments', {\n 69 |         'instanceId': instanceId,\n 70 |         'paths': paths,\n 71 |         if (activate != null && activate.isNotEmpty) 'activate': activate,\n 72 |       }).timeout(const Duration(seconds: 3));\n 73 |       return r ?? 0;\n 74 |     } catch (_) {\n 75 |       return 0;\n 76 |     }\n 77 |   }\n 78 | \n 79 |   /// `openDocuments` işinin durumu: {found, done, results:[{path, ok,\n 80 |   /// error}]}. Native cevap vermezse null.\n 81 |   static Future<Map<String, dynamic>?> getOpenJob(int jobId) async {\n 82 |     try {\n 83 |       final r = await _channel\n 84 |           .invokeMethod<Map<dynamic, dynamic>>('getOpenJob', {'jobId': jobId})\n 85 |           .timeout(const Duration(seconds: 3));\n 86 |       return r == null ? null : Map<String, dynamic>.from(r);\n 87 |     } catch (_) {\n 88 |       return null;\n 89 |     }\n 90 |   }\n 91 | \n 92 |   /// PID'in ana penceresini verilen sanal masaüstüne taşımayı dener.\n 93 |   /// HRESULT döner (0 = başarılı; 0x80070005 = başka process'in penceresi\n 94 |   /// için Windows'un erişim kısıtı). Native cevap vermezse null.\n 95 |   static Future<int?> moveWindowToDesktop(int pid, String guid) async {\n 96 |     try {\n 97 |       return await _channel.invokeMethod<int>(\n 98 |           'moveWindowToDesktop', {'pid': pid, 'guid': guid}).timeout(\n 99 |           const Duration(seconds: 3));\n100 |     } catch (_) {\n101 |       return null;\n102 |     }\n103 |   }\n104 | \n105 |   /// Sistem \"Farklı kaydet\" diyaloğu (native IFileDialog). Seçilen tam yolu\n106 |   /// döner; kullanıcı vazgeçtiyse null. Diyalog açılamazsa [StateError].\n107 |   /// Kullanıcı etkileşimi beklendiği için zaman aşımı YOK.\n108 |   static Future<String?> pickSaveFile({\n109 |     required String title,\n110 |     required String defaultName,\n111 |     required String filterName,\n112 |     required String extension,\n113 |     required String allFilesLabel,\n114 |   }) =>\n115 |       _pickFile('pickSaveFile', {\n116 |         'title': title,\n117 |         'defaultName': defaultName,\n118 |         'filterName': filterName,\n119 |         'ext': extension,\n120 |         'allLabel': allFilesLabel,\n121 |       });\n122 | \n123 |   /// Sistem \"Aç\" diyaloğu (native IFileDialog). Seçilen tam yolu döner;\n124 |   /// vazgeçildiyse null. Diyalog açılamazsa [StateError].\n125 |   static Future<String?> pickOpenFile({\n126 |     required String title,\n127 |     required String filterName,\n128 |     required String extension,\n129 |     required String allFilesLabel,\n130 |   }) =>\n131 |       _pickFile('pickOpenFile', {\n132 |         'title': title,\n133 |         'filterName': filterName,\n134 |         'ext': extension,\n135 |         'allLabel': allFilesLabel,\n136 |       });\n137 | \n138 |   static Future<String?> _pickFile(\n139 |       String method, Map<String, dynamic> args) async {\n140 |     try {\n141 |       final r = await _channel.invokeMethod<String>(method, args);\n142 |       return (r == null || r.isEmpty) ? null : r;\n143 |     } on PlatformException catch (e) {\n144 |       throw StateError('Dosya diyaloğu açılamadı: ${e.message ?? e.code}');\n145 |     } on MissingPluginException {\n146 |       throw StateError('Dosya diyaloğu bu sürümde desteklenmiyor');\n147 |     }\n148 |   }\n149 | \n150 |   /// Bu process'teki `desktop_multi_window` alt pencerelerinin HWND'leri.\n151 |   /// Yeni açılan alt pencereyi bulmak için (önce/sonra farkı alınır).\n152 |   static Future<List<int>> listChildWindows() async {\n153 |     try {\n154 |       final r = await _channel\n155 |           .invokeMethod<List<dynamic>>('listChildWindows')\n156 |           .timeout(const Duration(seconds: 2));\n157 |       return [for (final e in (r ?? const [])) (e as num).toInt()];\n158 |     } catch (_) {\n159 |       return const [];\n160 |     }\n161 |   }\n162 | \n163 |   /// Alt pencereyi boyutlandırır, ekranın ortasına koyar, başlığını\n164 |   /// ayarlar (X → gizle) ve [show] ise öne getirir. Başarıyı döner.\n165 |   static Future<bool> setupChildWindow({\n166 |     required int hwnd,\n167 |     required int width,\n168 |     required int height,\n169 |     required String title,\n170 |     bool show = true,\n171 |   }) async {\n172 |     try {\n173 |       final r = await _channel.invokeMethod<bool>('setupChildWindow', {\n174 |         'hwnd': hwnd,\n175 |         'width': width,\n176 |         'height': height,\n177 |         'title': title,\n178 |         'show': show,\n179 |       }).timeout(const Duration(seconds: 3));\n180 |       return r ?? false;\n181 |     } catch (_) {\n182 |       return false;\n183 |     }\n184 |   }\n185 | \n186 |   /// CATIA açık/aktif döküman izleme (native poller) aralığını ayarlar.\n187 |   /// [intervalMs] 0 ise izleme kapanır. Başarıyı döner; hata/eski native\n188 |   /// sürümde sessizce false.\n189 |   static Future<bool> setPollInterval(int intervalMs) async {\n190 |     try {\n191 |       final r = await _channel.invokeMethod<bool>(\n192 |           'setPollInterval', {'intervalMs': intervalMs}).timeout(\n193 |         const Duration(seconds: 2),\n194 |       );\n195 |       return r ?? false;\n196 |     } catch (_) {\n197 |       return false;\n198 |     }\n199 |   }\n200 | \n201 |   /// Ana pencere plugin kanallarını onarır. Hata/eksik plugin durumunda\n202 |   /// sessizce döner — asla exception fırlatmaz.\n203 |   static Future<void> repairPluginChannels() async {\n204 |     if (_unsupported) return;\n205 |     try {\n206 |       await _channel\n207 |           .invokeMethod<bool>('repairPluginChannels')\n208 |           .timeout(const Duration(milliseconds: 900));\n209 |     } on MissingPluginException {\n210 |       _unsupported = true;\n211 |     } catch (_) {\n212 |       // Zaman aşımı vb. → sonraki denemede yeniden deneriz.\n213 |     }\n214 |   }\n215 | \n216 |   /// Masaüstünde (tray'in üstünde) kısa bir toast bildirimi gösterir.\n217 |   /// Native Win32 penceresi — Flutter engine'i meşgul etmez.\n218 |   static Future<void> showToast({\n219 |     required String title,\n220 |     String subtitle = '',\n221 |     int durationMs = 2000,\n222 |   }) async {\n223 |     if (_unsupported) return;\n224 |     try {\n225 |       await _channel.invokeMethod<bool>('showToast', {\n226 |         'title': title,\n227 |         'subtitle': subtitle,\n228 |         'durationMs': durationMs,\n229 |       }).timeout(const Duration(milliseconds: 900));\n230 |     } on MissingPluginException {\n231 |       _unsupported = true;\n232 |     } catch (_) {\n233 |       // Toast kritik değil; sessizce geç.\n234 |     }\n235 |   }\n236 | \n237 |   /// Verilen PID'deki (CATIA) ana pencerenin başlığını değiştirir.\n238 |   /// [title] boşsa varsayılan \"CATIA V5\" yazılır. Başarıyı döner.\n239 |   static Future<bool> setInstanceWindowTitle(int pid, String title) async {\n240 |     if (_unsupported || pid <= 0) return false;\n241 |     try {\n242 |       final r = await _channel.invokeMethod<bool>('setInstanceWindowTitle', {\n243 |         'pid': pid,\n244 |         'title': title,\n245 |       }).timeout(const Duration(milliseconds: 900));\n246 |       return r ?? false;\n247 |     } on MissingPluginException {\n248 |       _unsupported = true;\n249 |       return false;\n250 |     } catch (_) {\n251 |       return false;\n252 |     }\n253 |   }\n254 | \n255 |   /// Verilen PID'in ana penceresinin bulunduğu sanal masaüstünün GUID'ini\n256 |   /// döner (IVirtualDesktopManager). Başarısızsa null.\n257 |   static Future<String?> getWindowDesktopGuid(int pid) async {\n258 |     if (_unsupported) return null;\n259 |     try {\n260 |       return await _channel\n261 |           .invokeMethod<String>('getWindowDesktop', {'pid': pid})\n262 |           .timeout(const Duration(milliseconds: 700));\n263 |     } on MissingPluginException {\n264 |       _unsupported = true;\n265 |       return null;\n266 |     } catch (_) {\n267 |       return null;\n268 |     }\n269 |   }\n270 | }",
              "success": true
            }
          ]
        }
      ],
      "ts": 1789804240667
    },
    {
      "id": "msg_jEpqsh6e",
      "role": "assistant",
      "content": [
        {
          "type": "thinking",
          "thinking": "Native toast API var. Şimdi catia_provider'daki showConnectToast ve renameInstance fonksiyonlarını okumam lazım. read_file cache sorunu veriyor, terminal ile okuyayım.\n"
        },
        {
          "type": "text",
          "text": "Native API'ler tamam. Şimdi catia_provider'daki `_showConnectToast`, `renameInstance` ve `_onRegister` fonksiyonlarını terminal ile okuyayım.\n\n"
        },
        {
          "type": "tool_use",
          "id": "dbN0HiGcm45wnacgEMJblRHuuJNRAAq2",
          "name": "run_commands",
          "input": {
            "commands": [
              "Get-Content 'd:\\__sistem__\\Masaustu\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\lib\\services\\catia_provider.dart' | Select-Object -Skip 96 -First 40"
            ]
          }
        }
      ],
      "ts": 1789804247613,
      "modelInfo": {
        "id": "ornith-1.5:35b",
        "provider": "ollama"
      },
      "metrics": {
        "inputTokens": 26230,
        "outputTokens": 169,
        "cacheReadTokens": 0,
        "cacheWriteTokens": 0
      }
    },
    {
      "id": "msg_dBlDBBWJ",
      "role": "user",
      "content": [
        {
          "type": "tool_result",
          "tool_use_id": "dbN0HiGcm45wnacgEMJblRHuuJNRAAq2",
          "name": "run_commands",
          "content": [
            {
              "query": "Get-Content 'd:\\__sistem__\\Masaustu\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\lib\\services\\catia_provider.dart' | Select-Object -Skip 96 -First 40",
              "result": "  Future<void> _loadInitialSnapshot() async {\r\n    final raw = await NativeBridge.getInstancesSnapshot();\r\n    if (raw.isNotEmpty) _onNativeSnapshot(raw);\r\n  }\r\n\r\n  /// Native COM bridge'den gelen push/sorgu sonucunu iÅŸler. KullanÄ±cÄ±nÄ±n\r\n  /// Ã§ift tÄ±klayarak verdiÄŸi Ã¶zel baÅŸlÄ±k (displayName) â€” id eÅŸleÅŸmesiyle\r\n  /// korunur. DÃ¶kÃ¼man listesi artÄ±k native'den (CATIA'nÄ±n gerÃ§ek\r\n  /// Application.Documents koleksiyonundan) geliyor, bu yÃ¼zden burada\r\n  /// eskisi gibi Ã¼stÃ¼ne yazÄ±lmÄ±yor.\r\n  void _onNativeSnapshot(List<dynamic> raw) {\r\n    if (raw.isEmpty) return;\r\n    final real = raw.map(_instanceFromNative).toList();\r\n    final existingById = {for (final i in _instances) i.id: i};\r\n    _instances = real.map((r) {\r\n      final prev = existingById[r.id];\r\n      return prev == null ? r : r.copyWith(displayName: prev.displayName);\r\n    }).toList();\r\n    _serverConnected = true;\r\n    notifyListeners();\r\n    // Yeni baÄŸlanan CATIA'larÄ± bul (ilk snapshot hariÃ§: uygulama aÃ§Ä±lÄ±rken\r\n    // zaten baÄŸlÄ± olanlar iÃ§in bildirim basmayalÄ±m).\r\n    if (_firstSnapshotDone) {\r\n      for (final r in real) {\r\n        if (!existingById.containsKey(r.id) && r.pid > 0 && r.connected) {\r\n          _showConnectToast(r);\r\n        }\r\n      }\r\n    }\r\n    _firstSnapshotDone = true;\r\n    // Kart widget'Ä± zaten mount olmuÅŸsa (mock'tan gerÃ§ek instance'a\r\n    // aynÄ± id ile geÃ§ildiÄŸinde State yeniden kurulmaz, initState tekrar\r\n    // Ã§alÄ±ÅŸmaz) CatiaListTile'Ä±n initState'i bir daha tetiklenmeyebilir.\r\n    // Bu yÃ¼zden gerÃ§ek pid geldiÄŸi her anda masaÃ¼stÃ¼ sorgusunu burada da\r\n    // (widget'tan baÄŸÄ±msÄ±z olarak) tetikliyoruz â€” fire-and-forget.\r\n    unawaited(refreshDesktops());\r\n  }\r\n\r\n  /// ğŸ”” Yeni CATIA baÄŸlandÄ± â†’ masaÃ¼stÃ¼nde kÄ±sa toast (ayarlardan aÃ§Ä±lÄ±p\r\n  /// kapanÄ±r; sÃ¼re ayarlanabilir).\r\n",
              "success": true
            }
          ]
        }
      ],
      "ts": 1789804248346
    },
    {
      "id": "msg_4hebHlLh",
      "role": "assistant",
      "content": [
        {
          "type": "thinking",
          "thinking": "Şimdi `_showConnectToast` ve `renameInstance` fonksiyonlarını, ayar ekranını, session_service'i ve native_bridge'i okudum. `_showConnectToast` çalışır görünüyor. Toast özelliği tamamlanmış. Şimdi diğer iki özelliği tamamlamam gerekiyor:\n\n1. **CATIA başlığı değiştirme** — çift tıklama ile rename. Bu `renameInstance` üzerinden oluyor. Renames_dialog var mı bakayım.\n\nŞimdi `_showConnectToast`'u okuyayım.\n"
        },
        {
          "type": "text",
          "text": "`_showConnectToast` tamamlanmış görünüyor. `renameInstance`'ı ve toast'u okuyayım.\n\n"
        },
        {
          "type": "tool_use",
          "id": "x8ONKbD5ZppzSgXDosKY8URr32KWNdwY",
          "name": "run_commands",
          "input": {
            "commands": [
              "Get-Content 'd:\\__sistem__\\Masaustu\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\lib\\services\\catia_provider.dart' | Select-Object -Skip 135 -First 60"
            ]
          }
        }
      ],
      "ts": 1789804254608,
      "modelInfo": {
        "id": "ornith-1.5:35b",
        "provider": "ollama"
      },
      "metrics": {
        "inputTokens": 27134,
        "outputTokens": 234,
        "cacheReadTokens": 0,
        "cacheWriteTokens": 0
      }
    },
    {
      "id": "msg_BpIrzvIx",
      "role": "user",
      "content": [
        {
          "type": "tool_result",
          "tool_use_id": "x8ONKbD5ZppzSgXDosKY8URr32KWNdwY",
          "name": "run_commands",
          "content": [
            {
              "query": "Get-Content 'd:\\__sistem__\\Masaustu\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\lib\\services\\catia_provider.dart' | Select-Object -Skip 135 -First 60",
              "result": "  /// kapanÄ±r; sÃ¼re ayarlanabilir).\r\n  void _showConnectToast(CatiaInstance i) {\r\n    if (!_settings.toastEnabled) return;\r\n    unawaited(NativeBridge.showToast(\r\n      title: 'ğŸŸ¢ ${i.id} baÄŸlandÄ±',\r\n      subtitle: i.fileName,\r\n      durationMs: _settings.toastDurationMs,\r\n    ));\r\n  }\r\n\r\n  /// Ä°lk native snapshot iÅŸlendi mi? (AÃ§Ä±lÄ±ÅŸ toast'larÄ±nÄ± bastÄ±rmak iÃ§in.)\r\n  bool _firstSnapshotDone = false;\r\n\r\n  CatiaInstance _instanceFromNative(dynamic j) {\r\n    final map = Map<String, dynamic>.from(j as Map);\r\n    final registeredAt = DateTime.parse(map['registeredAtUtc'] as String);\r\n    CatiaStatus status;\r\n    try {\r\n      status = CatiaStatus.values.byName(map['status'] as String);\r\n    } catch (_) {\r\n      status = CatiaStatus.idle;\r\n    }\r\n    final id = map['id'] as String;\r\n    final activeDocument = map['activeDocument'] as String?;\r\n    final rawDocs = (map['documents'] as List?) ?? const [];\r\n    final documents = <CatiaDocument>[\r\n      for (var i = 0; i < rawDocs.length; i++)\r\n        () {\r\n          final d = Map<String, dynamic>.from(rawDocs[i] as Map);\r\n          final name = d['name'] as String? ?? '';\r\n          return CatiaDocument(\r\n            id: '$id-doc${i + 1}',\r\n            name: name,\r\n            path: (d['path'] as String?) ?? '',\r\n            saved: (d['saved'] as bool?) ?? true,\r\n            isActive: activeDocument != null && activeDocument == name,\r\n          );\r\n        }(),\r\n    ];\r\n    return CatiaInstance(\r\n      id: id,\r\n      fileName: map['fileName'] as String,\r\n      status: status,\r\n      progress: map['progress'] as int?,\r\n      uptime: DateTime.now().toUtc().difference(registeredAt),\r\n      pid: map['pid'] as int,\r\n      worker: map['worker'] as String,\r\n      connected: map['connected'] as bool,\r\n      lastError: map['lastError'] as String?,\r\n      desktopGuid: (map['desktopGuid'] as String?) ?? '',\r\n      documents: documents,\r\n    );\r\n  }\r\n\r\n  HomeTab _tab = HomeTab.active;\r\n  String _query = '';\r\n  String? _selectedInstanceId;\r\n\r\n  HomeTab get tab => _tab;\r\n  String get query => _query;\r\n",
              "success": true
            }
          ]
        }
      ],
      "ts": 1789804255314
    },
    {
      "id": "msg_TYMKA9CU",
      "role": "assistant",
      "content": [
        {
          "type": "thinking",
          "thinking": "Toast zaten çalışıyor. Şimdi `renameInstance` fonksiyonunu ve CATIA COM başlığı değiştirme mantığını kontrol edeyim.\n"
        },
        {
          "type": "text",
          "text": "Toast fonksiyonu tamam. `renameInstance` fonksiyonunu ve COM başlığı değiştirme kısmını okuyayım.\n\n"
        },
        {
          "type": "tool_use",
          "id": "EYeMpyiCWv7GMB9pQjftZCYUiuGmcCjK",
          "name": "run_commands",
          "input": {
            "commands": [
              "Get-Content 'd:\\__sistem__\\Masaustu\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\lib\\services\\catia_provider.dart' | Select-Object -Skip 292 -First 40"
            ]
          }
        }
      ],
      "ts": 1789804259898,
      "modelInfo": {
        "id": "ornith-1.5:35b",
        "provider": "ollama"
      },
      "metrics": {
        "inputTokens": 28020,
        "outputTokens": 148,
        "cacheReadTokens": 0,
        "cacheWriteTokens": 0
      }
    },
    {
      "id": "msg_m2GZ9DqX",
      "role": "user",
      "content": [
        {
          "type": "tool_result",
          "tool_use_id": "EYeMpyiCWv7GMB9pQjftZCYUiuGmcCjK",
          "name": "run_commands",
          "content": [
            {
              "query": "Get-Content 'd:\\__sistem__\\Masaustu\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\lib\\services\\catia_provider.dart' | Select-Object -Skip 292 -First 40",
              "result": "    final i = _indexOf(id);\r\n    if (i < 0) return;\r\n    final t = newTitle.trim();\r\n    _instances[i] = t.isEmpty\r\n        ? _instances[i].copyWith(clearDisplayName: true)\r\n        : _instances[i].copyWith(displayName: t);\r\n    notifyListeners();\r\n    // CATIA pencere baÅŸlÄ±ÄŸÄ±nÄ± eÅŸitle (boÅŸ bÄ±rakÄ±lÄ±rsa \"CATIA V5\"e dÃ¶n).\r\n    final inst = _instances[i];\r\n    if (inst.pid > 0 && inst.connected) {\r\n      final label = t.isEmpty\r\n          ? (inst.documents.where((d) => d.isActive).isNotEmpty\r\n              ? inst.documents.firstWhere((d) => d.isActive).name\r\n              : '')\r\n          : t;\r\n      unawaited(NativeBridge.setInstanceWindowTitle(inst.pid, label));\r\n    }\r\n  }\r\n\r\n  /// âœï¸ GeÃ§miÅŸ kaydÄ±nÄ±n baÅŸlÄ±ÄŸÄ±nÄ± yeniden adlandÄ±r.\r\n  void renameHistory(String id, String newTitle) {\r\n    final i = _history.indexWhere((h) => h.id == id);\r\n    if (i < 0) return;\r\n    final t = newTitle.trim();\r\n    _history[i] = t.isEmpty\r\n        ? _history[i].copyWith(clearDisplayName: true)\r\n        : _history[i].copyWith(displayName: t);\r\n    notifyListeners();\r\n  }\r\n\r\n  // ---- Genel toolbar aksiyonlarÄ± (Ã¼st menÃ¼ altÄ±) ----\r\n\r\n  /// â• Yeni CATIA ekle (mock: listeye boÅŸta bir instance ekler).\r\n  void addInstance() {\r\n    final n = _instances.length + 1;\r\n    final id = 'CATIA-${n.toString().padLeft(2, '0')}';\r\n    if (_instances.any((i) => i.id == id)) {\r\n      _instances.add(CatiaInstance(\r\n        id: '$id-${DateTime.now().millisecond}',\r\n        fileName: 'â€”',\r\n",
              "success": true
            }
          ]
        }
      ],
      "ts": 1789804260596
    },
    {
      "id": "msg_1Ph54_zi",
      "role": "assistant",
      "content": [
        {
          "type": "thinking",
          "thinking": "Now I understand the rename logic. `renameInstance` calls `NativeBridge.setInstanceWindowTitle` which sets the *window* title. But the user wants to change CATIA's COM object title too (`catia.title`). Let me check the com_bridge for SetWindowCaption or similar. Let me look at the COM bridge and native_bridge for the COM title API.\n\nLet me look at the native com_bridge for SetWindowCaption / SetTitle.\n"
        },
        {
          "type": "text",
          "text": "Şimdi rename'in CATIA COM nesnesine de yansıtılması ve aktif döküman adının canlı olması için native tarafa bakayım.\n\n"
        },
        {
          "type": "tool_use",
          "id": "LEPwjLs2WMhFGgBD3lW46MTGS2wVDwxE",
          "name": "run_commands",
          "input": {
            "commands": [
              "Select-String -Path 'd:\\__sistem__\\Masaustu\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\windows\\runner\\com_bridge.cpp' -Pattern 'Caption|SetTitle|SetCaption|title|SetTitle' -CaseSensitive | Select-Object LineNumber, Line -First 30"
            ]
          }
        }
      ],
      "ts": 1789804266595,
      "modelInfo": {
        "id": "ornith-1.5:35b",
        "provider": "ollama"
      },
      "metrics": {
        "inputTokens": 28774,
        "outputTokens": 228,
        "cacheReadTokens": 0,
        "cacheWriteTokens": 0
      }
    },
    {
      "id": "msg_RE9Ee8xu",
      "role": "user",
      "content": [
        {
          "type": "tool_result",
          "tool_use_id": "LEPwjLs2WMhFGgBD3lW46MTGS2wVDwxE",
          "name": "run_commands",
          "content": [
            {
              "query": "Select-String -Path 'd:\\__sistem__\\Masaustu\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\windows\\runner\\com_bridge.cpp' -Pattern 'Caption|SetTitle|SetCaption|title|SetTitle' -CaseSensitive | Select-Object L ... [command truncated: 25 more chars; full command is in the tool call input]",
              "result": "",
              "success": true
            }
          ]
        }
      ],
      "ts": 1789804267370
    },
    {
      "id": "msg_zbHcTL1V",
      "role": "assistant",
      "content": [
        {
          "type": "thinking",
          "thinking": "com_bridge.cpp'i okumam gerekiyor. Önce dosya boyutunu ve UpdateStatus fonksiyonunu görmem lazım.\n"
        },
        {
          "type": "text",
          "text": "com_bridge.cpp'te başlık değiştirme fonksiyonu yok gibi görünüyor. Dosyayı ve UpdateStatus fonksiyonunu okuyayım.\n\n"
        },
        {
          "type": "tool_use",
          "id": "lPg47zc5xyIEtF3HMdiSHHiPNLzGrlkv",
          "name": "run_commands",
          "input": {
            "commands": [
              "$p='d:\\__sistem__\\Masaustu\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\windows\\runner\\com_bridge.cpp'; (Get-Content $p).Count; Select-String -Path $p -Pattern 'UpdateStatus|activeDocument|SetActiveDoc|SetActiveDocument' | Select-Object LineNumber -First 20"
            ]
          }
        }
      ],
      "ts": 1789804271785,
      "modelInfo": {
        "id": "ornith-1.5:35b",
        "provider": "ollama"
      },
      "metrics": {
        "inputTokens": 29020,
        "outputTokens": 176,
        "cacheReadTokens": 0,
        "cacheWriteTokens": 0
      }
    },
    {
      "id": "msg_IKfHTYDH",
      "role": "user",
      "content": [
        {
          "type": "tool_result",
          "tool_use_id": "lPg47zc5xyIEtF3HMdiSHHiPNLzGrlkv",
          "name": "run_commands",
          "content": [
            {
              "query": "$p='d:\\__sistem__\\Masaustu\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\windows\\runner\\com_bridge.cpp'; (Get-Content $p).Count; Select-String -Path $p -Pattern 'UpdateStatus|activeDocument|SetActiveDoc|SetA ... [command truncated: 51 more chars; full command is in the tool call input]",
              "result": "1160\r\n\r\nLineNumber\r\n----------\r\n        60\r\n        61\r\n        63\r\n       127\r\n       182\r\n       258\r\n       287\r\n       530\r\n       531\r\n       533\r\n       540\r\n       582\r\n       583\r\n       590\r\n       605\r\n       608\r\n       609\r\n       675\r\n       721\r\n       733\r\n\r\n\r\n",
              "success": true
            }
          ]
        }
      ],
      "ts": 1789804272537
    },
    {
      "id": "msg_qhlfSTjc",
      "role": "assistant",
      "content": [
        {
          "type": "thinking",
          "thinking": "Şimdi UpdateStatus fonksiyonunu ve activeDocument'ın nasıl güncellendiğini, ayrıca `setInstanceWindowTitle` native handler'ı ve CATIA başlığını değiştirme fonksiyonlarını görmem lazım. Ayrıca flutter_window.cpp'deki native handler'ları ve com_bridge.h'deki COM başlığı değiştirme fonksiyonlarını kontrol etmeliyim.\n"
        },
        {
          "type": "text",
          "text": "com_bridge.cpp 1160 satır. UpdateStatus fonksiyonunu ve activeDocument yönetimini, ayrıca COM başlığı set fonksiyonunu görmem lazım.\n\n"
        },
        {
          "type": "tool_use",
          "id": "05GIwaxS6mMNqSRpnUZhY3aOmsDIS2kR",
          "name": "run_commands",
          "input": {
            "commands": [
              "$p='d:\\__sistem__\\Masaustu\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\windows\\runner\\com_bridge.cpp'; Select-String -Path $p -Pattern 'UpdateStatus' | Select-Object LineNumber; Select-String -Path $p -Pattern 'SetActiveDocument|SetActiveDoc|SetActive|activeDocument|SetActiveCaption' | Select-Object LineNumber -First 20; Get-Content $p | Select-Object -Skip 540 -First 130 | Out-String"
            ]
          }
        }
      ],
      "ts": 1789804278818,
      "modelInfo": {
        "id": "ornith-1.5:35b",
        "provider": "ollama"
      },
      "metrics": {
        "inputTokens": 29438,
        "outputTokens": 268,
        "cacheReadTokens": 0,
        "cacheWriteTokens": 0
      }
    },
    {
      "id": "msg_Xaj--gUf",
      "role": "user",
      "content": [
        {
          "type": "tool_result",
          "tool_use_id": "05GIwaxS6mMNqSRpnUZhY3aOmsDIS2kR",
          "name": "run_commands",
          "content": [
            {
              "query": "$p='d:\\__sistem__\\Masaustu\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\windows\\runner\\com_bridge.cpp'; Select-String -Path $p -Pattern 'UpdateStatus' | Select-Object LineNumber; Select-String -Path $p -Pat ... [command truncated: 182 more chars; full command is in the tool call input]",
              "result": "\r\nLineNumber\r\n----------\r\n        61\r\n       287\r\n       540\r\n       582\r\n       590\r\n       675\r\n       721\r\n       733\r\n       735\r\n       915\r\n        60\r\n        63\r\n       127\r\n       182\r\n       258\r\n       530\r\n       531\r\n       533\r\n       583\r\n       605\r\n       608\r\n       609\r\n       919\r\n      1011\r\n      1039\r\n      1042\r\n// COM'un STA mesaj pompasÄ±ndan (ana thread) Ã§aÄŸrÄ±ldÄ±ÄŸÄ± iÃ§in bu gÃ¼venlidir.\r\nvoid PushSnapshotLocked() {\r\n  if (!g_snapshot_cb) return;\r\n  g_snapshot_cb(BuildSnapshotLocked());\r\n}\r\n\r\nstd::wstring RegisterInstanceImpl(IDispatch* catiaApp, int pid,\r\n                                   const std::wstring& fileName) {\r\n  std::wstring id;\r\n  std::lock_guard<std::mutex> lock(g_mutex);\r\n  ++g_counter;\r\n  wchar_t idBuf[16];\r\n  StringCchPrintfW(idBuf, 16, L\"CATIA-%02d\", g_counter);\r\n  id = idBuf;\r\n\r\n  InstanceState s;\r\n  s.id = id;\r\n  s.fileName = fileName;\r\n  s.status = L\"idle\";\r\n  s.pid = pid;\r\n  wchar_t workerBuf[24];\r\n  StringCchPrintfW(workerBuf, 24, L\"Worker-%02d\", g_counter);\r\n  s.worker = workerBuf;\r\n  s.connected = true;\r\n  ::GetSystemTimeAsFileTime(&s.registeredAtUtc);\r\n  if (catiaApp) {\r\n    s.catiaApp = catiaApp;\r\n    // Worker thread'lerin (dÃ¶kÃ¼man aÃ§ma) kullanabilmesi iÃ§in GIT'e kaydet.\r\n    if (IGlobalInterfaceTable* git = GetGit()) {\r\n      DWORD cookie = 0;\r\n      HRESULT hrGit =\r\n          git->RegisterInterfaceInGlobal(catiaApp, IID_IDispatch, &cookie);\r\n      if (SUCCEEDED(hrGit)) {\r\n        s.gitCookie = cookie;\r\n      } else {\r\n        wchar_t log[96];\r\n        StringCchPrintfW(log, 96, L\"GIT Register FAILED hr=0x%08X\", hrGit);\r\n        LogLine(log);\r\n      }\r\n    }\r\n    // Ä°lk kayÄ±tta aÃ§Ä±k dÃ¶kÃ¼manlarÄ±/aktif dÃ¶kÃ¼manÄ± hemen oku (macro ilk\r\n    // UpdateStatus'u geÃ§ Ã§aÄŸÄ±rÄ±rsa bile UI boÅŸ gÃ¶rÃ¼nmesin).\r\n    RefreshDocumentsFromCatia(catiaApp, &s.documents, &s.activeDocumentName);\r\n  }\r\n  g_map[id] = std::move(s);\r\n  PushSnapshotLocked();\r\n  return id;\r\n}\r\n\r\nbool UpdateStatusImpl(const std::wstring& id, const std::wstring& status,\r\n                       int progress) {\r\n  std::lock_guard<std::mutex> lock(g_mutex);\r\n  auto it = g_map.find(id);\r\n  if (it == g_map.end()) return false;\r\n  it->second.status = status;\r\n  if (progress < 0) {\r\n    it->second.progress.reset();\r\n  } else {\r\n    it->second.progress = progress;\r\n  }\r\n  // Her durum gÃ¼ncellemesinde dÃ¶kÃ¼manlarÄ± da tazele (macro periyodik\r\n  // Ã§aÄŸÄ±rdÄ±ÄŸÄ± iÃ§in pratikte \"canlÄ±\" bir aÃ§Ä±k dÃ¶kÃ¼man listesi elde ederiz).\r\n  if (it->second.catiaApp) {\r\n    RefreshDocumentsFromCatia(it->second.catiaApp.Get(), &it->second.documents,\r\n                               &it->second.activeDocumentName);\r\n    // Alt baÅŸlÄ±k da aktif dÃ¶kÃ¼manÄ± izlesin (kullanÄ±cÄ± dÃ¶kÃ¼man deÄŸiÅŸtirince\r\n    // UI'daki alt satÄ±r hemen gÃ¼ncellensin).\r\n    if (!it->second.activeDocumentName.empty()) {\r\n      it->second.fileName = it->second.activeDocumentName;\r\n    }\r\n  }\r\n  PushSnapshotLocked();\r\n  return true;\r\n}\r\n\r\nbool UnregisterImpl(const std::wstring& id) {\r\n  std::lock_guard<std::mutex> lock(g_mutex);\r\n  auto it = g_map.find(id);\r\n  if (it == g_map.end()) return false;\r\n  if (it->second.gitCookie) {\r\n    if (IGlobalInterfaceTable* git = GetGit()) {\r\n      git->RevokeInterfaceFromGlobal(it->second.gitCookie);\r\n    }\r\n  }\r\n  g_map.erase(it);\r\n  PushSnapshotLocked();\r\n  return true;\r\n}\r\n\r\n// ---------------------------------------------------------------------\r\n// IDispatch implementasyonu â€” VBScript/CATScript'in late-bound\r\n// CreateObject + Ã§aÄŸrÄ± yapabilmesi iÃ§in minimum Automation desteÄŸi.\r\n// Tip kÃ¼tÃ¼phanesi (typelib) yok; GetIDsOfNames sabit DISPID'lere eÅŸler.\r\n// ---------------------------------------------------------------------\r\n\r\nclass BridgeDispatch : public IDispatch {\r\n public:\r\n  BridgeDispatch() : ref_(1) {}\r\n\r\n  // IUnknown\r\n  HRESULT __stdcall QueryInterface(REFIID riid, void** ppv) override {\r\n    if (!ppv) return E_POINTER;\r\n    if (riid == IID_IUnknown || riid == IID_IDispatch) {\r\n      *ppv = static_cast<IDispatch*>(this);\r\n      AddRef();\r\n      return S_OK;\r\n    }\r\n    *ppv = nullptr;\r\n    return E_NOINTERFACE;\r\n  }\r\n  ULONG __stdcall AddRef() override { return ++ref_; }\r\n  ULONG __stdcall Release() override {\r\n    ULONG r = --ref_;\r\n    if (r == 0) delete this;\r\n    return r;\r\n  }\r\n\r\n  // IDispatch\r\n  HRESULT __stdcall GetTypeInfoCount(UINT* pctinfo) override {\r\n    *pctinfo = 0;\r\n    return S_OK;\r\n  }\r\n  HRESULT __stdcall GetTypeInfo(UINT, LCID, ITypeInfo** ppTInfo) override {\r\n    if (ppTInfo) *ppTInfo = nullptr;\r\n    return E_NOTIMPL;\r\n  }\r\n  HRESULT __stdcall GetIDsOfNames(REFIID, LPOLESTR* rgszNames, UINT cNames,\r\n                                   LCID, DISPID* rgDispId) override {\r\n    for (UINT i = 0; i < cNames; ++i) {\r\n      const wchar_t* name = rgszNames[i];\r\n\r\n\r\n\r\n",
              "success": true
            }
          ]
        }
      ],
      "ts": 1789804279546
    },
    {
      "id": "msg_6O5WPCxM",
      "role": "assistant",
      "content": [
        {
          "type": "thinking",
          "thinking": "Active document name zaten UpdateStatusImpl içinde güncelleniyor. `it->second.fileName = it->second.activeDocumentName;` — yani aktif döküman adı timer ile değişmeli. Ama kullanıcı sabit kaldığını söylüyor. Sorun muhtemelen macro'nun UpdateStatus'u yeterli sıklıkla çağırmaması ya da activeDocumentName'in doğru doldurulmaması. Şimdi RefreshDocumentsFromCatia'yı ve SetTitle fonksiyonunu görmem lazım.\n\nKullanıcının istediği özellikler:\n1. ✅ Toast — zaten var\n2. ✅ CATIA başlığını değiştirme — var (rename + setInstanceWindowTitle)\n3. Aktif döküman adı sabit kalıyor\n\nAktif döküman adının sabit kalmasının sebebi: UpdateStatusImpl fileName'i activeDocumentName'e göre güncelliyor ama activeDocumentName doğru belirleniyor mu? RefreshDocumentsFromCatia'yı görmem lazım.\n\nAyrıca COM başlığı set etmek için SetTitle fonksiyonu var mı? native_bridge.setInstanceWindowTitle var ama com_bridge.cpp'te implementasyonu var mı?\n\nKullanıcı \"com object catia.title\" dedi — CATIA Application.Title property'sini mi demek istiyor yoksa COM nesnesinin başlığını mı? CATIA'nın Application.Title property'si var ama bu CATIA'nın sürüm bilgisini gösterir (\"CATIA V5 Rxxx\"). Daha büyük ihtimalle CATIA pencere başlığını (SetWindowTitle) demek istiyor.\n\nŞimdi RefreshDocumentsFromCatia'yı ve SetWindowTitle fonksiyonunu görmem lazım.\n"
        },
        {
          "type": "text",
          "text": "Şimdi active document'in doğru okunup okunmadığını ve COM pencere başlığı set fonksiyonunu görmem lazım.\n\n"
        },
        {
          "type": "tool_use",
          "id": "9AAkkRD94flI8t7zSyUDXVkE7waQOsQR",
          "name": "run_commands",
          "input": {
            "commands": [
              "$p='d:\\__sistem__\\Masaustu\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\windows\\runner\\com_bridge.cpp'; Select-String -Path $p -Pattern 'RefreshDocumentsFromCatia|SetTitle|SetWindowTitle|SetTitle\\(' | Select-Object LineNumber; Write-Host '---REFRESHFUNCS---'; Get-Content $p | Select-Object -Skip 940 -First 140 | Out-String"
            ]
          }
        }
      ],
      "ts": 1789804291258,
      "modelInfo": {
        "id": "ornith-1.5:35b",
        "provider": "ollama"
      },
      "metrics": {
        "inputTokens": 31556,
        "outputTokens": 514,
        "cacheReadTokens": 0,
        "cacheWriteTokens": 0
      }
    },
    {
      "id": "msg_1bk9ufXP",
      "role": "user",
      "content": [
        {
          "type": "tool_result",
          "tool_use_id": "9AAkkRD94flI8t7zSyUDXVkE7waQOsQR",
          "name": "run_commands",
          "content": [
            {
              "query": "$p='d:\\__sistem__\\Masaustu\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\windows\\runner\\com_bridge.cpp'; Select-String -Path $p -Pattern 'RefreshDocumentsFromCatia|SetTitle|SetWindowTitle|SetTitle\\(' | Selec ... [command truncated: 118 more chars; full command is in the tool call input]",
              "result": "\r\n---REFRESHFUNCS---\nLineNumber\r\n----------\r\n        42\r\n       185\r\n       196\r\n       276\r\n       419\r\n       583\r\n       604\r\n      1028\r\n  return ::DefWindowProcW(h, m, w, l);\r\n}\r\n\r\nvoid CreatePushWindow() {\r\n  if (g_push_hwnd) return;\r\n  HINSTANCE inst = ::GetModuleHandleW(nullptr);\r\n  WNDCLASSW wc{};\r\n  wc.lpfnWndProc = PushWndProc;\r\n  wc.hInstance = inst;\r\n  wc.lpszClassName = L\"FikoComBridgePushWnd\";\r\n  ::RegisterClassW(&wc);  // zaten kayÄ±tlÄ±ysa hata dÃ¶ner, sorun deÄŸil\r\n  g_push_hwnd = ::CreateWindowExW(0, wc.lpszClassName, L\"\", 0, 0, 0, 0, 0,\r\n                                   HWND_MESSAGE, nullptr, inst, nullptr);\r\n  if (!g_push_hwnd) LogLine(L\"DocPoller: push window create FAILED\");\r\n}\r\n\r\nbool DocListsEqual(const std::vector<DocInfo>& a,\r\n                   const std::vector<DocInfo>& b) {\r\n  if (a.size() != b.size()) return false;\r\n  for (size_t i = 0; i < a.size(); ++i) {\r\n    if (a[i].name != b[i].name || a[i].path != b[i].path ||\r\n        a[i].saved != b[i].saved) {\r\n      return false;\r\n    }\r\n  }\r\n  return true;\r\n}\r\n\r\nbool AnyOpenJobRunning() {\r\n  std::lock_guard<std::mutex> lock(g_jobs_mutex);\r\n  for (const auto& kv : g_jobs) {\r\n    if (!kv.second.done) return true;\r\n  }\r\n  return false;\r\n}\r\n\r\nvoid DocPollerLoop() {\r\n  const HRESULT hrInit = ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);\r\n  LogLine(L\"DocPoller started\");\r\n  struct Target {\r\n    std::wstring id;\r\n    DWORD cookie;\r\n    std::vector<DocInfo> docs;\r\n    std::wstring active;\r\n  };\r\n  while (!g_poll_stop.load()) {\r\n    // AralÄ±k kadar bekle, ama kapanÄ±ÅŸa/ayar deÄŸiÅŸimine hÄ±zlÄ± tepki ver.\r\n    // 0 = izleme kapalÄ±: sadece 200 ms'de bir ayarÄ± yeniden kontrol et.\r\n    const int interval = g_poll_interval_ms.load();\r\n    if (interval <= 0) {\r\n      ::Sleep(200);\r\n      continue;\r\n    }\r\n    for (int waited = 0; waited < interval && !g_poll_stop.load() &&\r\n                         g_poll_interval_ms.load() == interval;\r\n         waited += 100) {\r\n      ::Sleep(100);\r\n    }\r\n    if (g_poll_stop.load()) break;\r\n    if (g_poll_interval_ms.load() <= 0) continue;\r\n    // DÃ¶kÃ¼man aÃ§ma iÅŸi sÃ¼rerken CATIA'yÄ± ek Ã§aÄŸrÄ±larla meÅŸgul etme.\r\n    if (AnyOpenJobRunning()) continue;\r\n\r\n    std::vector<Target> targets;\r\n    {\r\n      std::lock_guard<std::mutex> lock(g_mutex);\r\n      for (const auto& kv : g_map) {\r\n        const InstanceState& s = kv.second;\r\n        if (s.gitCookie != 0) {\r\n          targets.push_back(\r\n              {kv.first, s.gitCookie, s.documents, s.activeDocumentName});\r\n        }\r\n      }\r\n    }\r\n\r\n    for (auto& t : targets) {\r\n      if (g_poll_stop.load()) break;\r\n      Microsoft::WRL::ComPtr<IDispatch> app;\r\n      IGlobalInterfaceTable* git = GetGit();\r\n      HRESULT hr = git ? git->GetInterfaceFromGlobal(\r\n                             t.cookie, IID_IDispatch,\r\n                             reinterpret_cast<void**>(app.GetAddressOf()))\r\n                       : E_FAIL;\r\n      if (FAILED(hr) || !app) continue;\r\n\r\n      std::vector<DocInfo> cur = t.docs;\r\n      std::wstring act = t.active;\r\n      RefreshDocumentsFromCatia(app.Get(), &cur, &act, /*quiet=*/true);\r\n      app.Reset();\r\n\r\n      std::lock_guard<std::mutex> lock(g_mutex);\r\n      auto it = g_map.find(t.id);\r\n      if (it == g_map.end()) continue;  // bu arada unregister edilmiÅŸ\r\n      // Kart alt baÅŸlÄ±ÄŸÄ± (fileName) da aktif dÃ¶kÃ¼manÄ± izlesin: CATIA'da\r\n      // dÃ¶kÃ¼man deÄŸiÅŸince UI'daki alt satÄ±r bu sayede canlÄ± kalÄ±r.\r\n      const bool fileNameChanged =\r\n          !act.empty() && it->second.fileName != act;\r\n      if (!DocListsEqual(it->second.documents, cur) ||\r\n          it->second.activeDocumentName != act ||\r\n          fileNameChanged) {\r\n        it->second.documents = std::move(cur);\r\n        it->second.activeDocumentName = act;\r\n        if (fileNameChanged) it->second.fileName = act;\r\n        wchar_t log[256];\r\n        StringCchPrintfW(log, 256, L\"DocPoller: %s changed count=%d active=%s\",\r\n                          t.id.c_str(),\r\n                          static_cast<int>(it->second.documents.size()),\r\n                          act.empty() ? L\"(none)\" : act.c_str());\r\n        LogLine(log);\r\n        // Snapshot'Ä± ana thread'de it (Flutter kanalÄ± thread-safe deÄŸil).\r\n        if (g_push_hwnd) ::PostMessageW(g_push_hwnd, kWmPushSnapshot, 0, 0);\r\n      }\r\n    }\r\n  }\r\n  LogLine(L\"DocPoller stopped\");\r\n  if (SUCCEEDED(hrInit)) ::CoUninitialize();\r\n}\r\n\r\n}  // namespace\r\n\r\nvoid SetPollIntervalMs(int ms) {\r\n  if (ms <= 0) {\r\n    g_poll_interval_ms = 0;\r\n  } else {\r\n    g_poll_interval_ms = ms < 500 ? 500 : (ms > 60000 ? 60000 : ms);\r\n  }\r\n  wchar_t log[96];\r\n  StringCchPrintfW(log, 96, L\"DocPoller interval=%d ms\", g_poll_interval_ms.load());\r\n  LogLine(log);\r\n}\r\n\r\nHRESULT RegisterClassObject() {\r\n  g_factory = new BridgeClassFactory();\r\n  HRESULT hr =\r\n      ::CoRegisterClassObject(kBridgeClsid, g_factory, CLSCTX_LOCAL_SERVER,\r\n                              REGCLS_MULTIPLEUSE, &g_cookie);\r\n  if (FAILED(hr)) {\r\n    LogLine(L\"CoRegisterClassObject FAILED\");\r\n  } else if (!g_poller.joinable()) {\r\n    CreatePushWindow();  // ana thread'de (Register Ã§aÄŸÄ±ran thread) yaratÄ±lÄ±r\r\n\r\n\r\n\r\n",
              "success": true
            }
          ]
        }
      ],
      "ts": 1789804292034
    },
    {
      "id": "msg_mZSJkP_0",
      "role": "assistant",
      "content": [
        {
          "type": "thinking",
          "thinking": "Now I understand the whole system. Let me review the three feature requirements:\n\n1. **Yeni CATIA bağlanınca toast gösterimi** — already done. `_showConnectToast` calls `NativeBridge.showToast`. The `setInstanceWindowTitle` method exists. But I need to check whether renaming actually sets the COM-level CATIA title (`catia.title`) — the user asked for it. Let me check the rename flow. Actually looking at renameInstance, it calls `setInstanceWindowTitle` for the window title. But the user wants the actual CATIA Application.title to change. Let me check if there's a way to set the CATIA title via COM.\n\nLet me re-read the user's requirements:\n\n1. **Yeni CATIA bağlanınca masaüstünde toast bildirimi** — 1-2 saniye, ayarlardan ayarlanabilir. ✅ Already implemented.\n2. **Catia titleına çift tıklayıp değiştirince com object catia.title dat ona göre ayarlansın** — CATIA'nın başlıklarınıda değiştirebilelim o şekilde. — The rename should set both the window title AND the CATIA Application title.\n3. **O başlığın altında aktif doküman ismi var, timer ile değişiyor** — sabit kalmamalı. — Already handled by DocPoller.\n\nSo the missing piece is #2: renaming should set the CATIA Application title via COM. Let me check the com_bridge.h to see what methods exist for setting CATIA title.\n\nLet me look at the com_bridge.h header and the method channels registered on the native side, plus how rename sets things.\n"
        },
        {
          "type": "text",
          "text": "Şimdi CATIA başlığını COM nesnesine set etmek için gerekli method'ların varlığını ve com_bridge.h'deki method kayıtlarını kontrol edeyim.\n\n"
        },
        {
          "type": "tool_use",
          "id": "lPzEzcxDdI7WhTcl1hxUNt5tjOk09svJ",
          "name": "run_commands",
          "input": {
            "commands": [
              "$p='d:\\__sistem__\\Masaustu\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\windows\\runner\\com_bridge.cpp'; Select-String -Path $p -Pattern 'SetTitle\\(|SetTitle\\s*\\(|CATApplication|Application\\s*\\(|\\bCaption\\b|SetTitleW|SetTitle\\(pid' | Select-Object LineNumber, Line; Write-Host '---CHANNELS---'; Select-String -Path 'd:\\__sistem__\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\windows\\runner' -Pattern 'showToast|setInstanceWindowTitle|setPollInterval|instancesSnapshot' | Select-Object Filename, LineNumber, Line"
            ]
          }
        }
      ],
      "ts": 1789804302444,
      "modelInfo": {
        "id": "ornith-1.5:35b",
        "provider": "ollama"
      },
      "metrics": {
        "inputTokens": 33690,
        "outputTokens": 573,
        "cacheReadTokens": 0,
        "cacheWriteTokens": 0
      }
    },
    {
      "id": "msg_fJmKJKpc",
      "role": "user",
      "content": [
        {
          "type": "tool_result",
          "tool_use_id": "lPzEzcxDdI7WhTcl1hxUNt5tjOk09svJ",
          "name": "run_commands",
          "content": [
            {
              "query": "$p='d:\\__sistem__\\Masaustu\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\windows\\runner\\com_bridge.cpp'; Select-String -Path $p -Pattern 'SetTitle\\(|SetTitle\\s*\\(|CATApplication|Application\\s*\\(|\\bCaption\\b| ... [command truncated: 298 more chars; full command is in the tool call input]",
              "result": "[Command exited with code 1]\n---CHANNELS---\n\n[stderr]\nSelect-String : Cannot find path 'D:\\__sistem__\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\windows\\runner' because it does \r\nnot exist.\r\nAt line:1 char:289\r\n+ ... ANNELS---'; Select-String -Path 'd:\\__sistem__\\FikoYT.Yeni.Cag\\flutte ...\r\n+                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\r\n    + CategoryInfo          : ObjectNotFound: (D:\\__sistem__\\F...\\windows\\runner:String) [Select-String], ItemNotFound \r\n   Exception\r\n    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.SelectStringCommand\r\n \r\n",
              "error": "Command exited with code 1",
              "success": false
            }
          ]
        }
      ],
      "ts": 1789804303285
    },
    {
      "id": "msg_4RflGMER",
      "role": "assistant",
      "content": [
        {
          "type": "thinking",
          "thinking": "Yanlış yol. Doğru yolu kullanayım ve channel handler'ları bulayım.\n"
        },
        {
          "type": "text",
          "text": "Yol hatası oldu. Doğru yolu kullanıp channel handler'ları ve COM başlık set mekanizmasını bulayım.\n\n"
        },
        {
          "type": "tool_use",
          "id": "IVmVCtmW6rCObW8mUW9HNLeeA3KgkIBH",
          "name": "run_commands",
          "input": {
            "commands": [
              "$p='d:\\__sistem__\\Masaustu\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\windows\\runner\\com_bridge.cpp'; Select-String -Path $p -Pattern 'SetTitle\\(|SetCaption|SetTitleW|SetTitle' | Select-Object LineNumber; Write-Host '---CHANNELS---'; Select-String -Path 'd:\\__sistem__\\Masaustu\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\windows\\runner' -Pattern 'showToast|setInstanceWindowTitle|setPollInterval' | Select-Object Filename, LineNumber, Line"
            ]
          }
        }
      ],
      "ts": 1789804307278,
      "modelInfo": {
        "id": "ornith-1.5:35b",
        "provider": "ollama"
      },
      "metrics": {
        "inputTokens": 34226,
        "outputTokens": 216,
        "cacheReadTokens": 0,
        "cacheWriteTokens": 0
      }
    },
    {
      "id": "msg__-MhNLOp",
      "role": "user",
      "content": [
        {
          "type": "tool_result",
          "tool_use_id": "IVmVCtmW6rCObW8mUW9HNLeeA3KgkIBH",
          "name": "run_commands",
          "content": [
            {
              "query": "$p='d:\\__sistem__\\Masaustu\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\windows\\runner\\com_bridge.cpp'; Select-String -Path $p -Pattern 'SetTitle\\(|SetCaption|SetTitleW|SetTitle' | Select-Object LineNumber; ... [command truncated: 231 more chars; full command is in the tool call input]",
              "result": "[Command exited with code 1]\n---CHANNELS---\n\n[stderr]\nSelect-String : The file D:\\__sistem__\\Masaustu\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\windows\\runner cannot be read: '\r\nD:\\__sistem__\\Masaustu\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\windows\\runner' yoluna erişim reddedildi.\r\nAt line:1 char:231\r\n+ ... ANNELS---'; Select-String -Path 'd:\\__sistem__\\Masaustu\\FikoYT.Yeni.C ...\r\n+                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\r\n    + CategoryInfo          : InvalidArgument: (:) [Select-String], ArgumentException\r\n    + FullyQualifiedErrorId : ProcessingFile,Microsoft.PowerShell.Commands.SelectStringCommand\r\n \r\n",
              "error": "Command exited with code 1",
              "success": false
            }
          ]
        }
      ],
      "ts": 1789804308032
    },
    {
      "id": "msg_FHd_JyBZ",
      "role": "assistant",
      "content": [
        {
          "type": "thinking",
          "thinking": "Let me search within the specific files.\n"
        },
        {
          "type": "text",
          "text": "Dizin izni reddedildi. Bireysel dosyaları seçeyim.\n\n"
        },
        {
          "type": "tool_use",
          "id": "2CrwboTrsyGKJQuMKW2z2rG4QfgLfHlB",
          "name": "run_commands",
          "input": {
            "commands": [
              "Select-String -Path 'd:\\__sistem__\\Masaustu\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\windows\\runner\\flutter_window.cpp' -Pattern 'showToast|setInstanceWindowTitle|setPollInterval' | Select-Object Filename, LineNumber, Line"
            ]
          }
        }
      ],
      "ts": 1789804313912,
      "modelInfo": {
        "id": "ornith-1.5:35b",
        "provider": "ollama"
      },
      "metrics": {
        "inputTokens": 34744,
        "outputTokens": 124,
        "cacheReadTokens": 0,
        "cacheWriteTokens": 0
      }
    },
    {
      "id": "msg_GI_2rsrM",
      "role": "user",
      "content": [
        {
          "type": "tool_result",
          "tool_use_id": "2CrwboTrsyGKJQuMKW2z2rG4QfgLfHlB",
          "name": "run_commands",
          "content": [
            {
              "query": "Select-String -Path 'd:\\__sistem__\\Masaustu\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\windows\\runner\\flutter_window.cpp' -Pattern 'showToast|setInstanceWindowTitle|setPollInterval' | Select-Object Filena ... [command truncated: 20 more chars; full command is in the tool call input]",
              "result": "\r\nFilename           LineNumber Line                                                                   \r\n--------           ---------- ----                                                                   \r\nflutter_window.cpp        322 void ShowToast(const std::wstring& title, const std::wstring& subtitle,\r\nflutter_window.cpp        744         if (call.method_name() == \"showToast\") {                       \r\nflutter_window.cpp        748           ShowToast(Utf8ToW(GetStringArg(args, \"title\")),              \r\nflutter_window.cpp        754         if (call.method_name() == \"setInstanceWindowTitle\") {          \r\nflutter_window.cpp        764         if (call.method_name() == \"setPollInterval\") {                 \r\nflutter_window.cpp        769           fikocatia_com::SetPollIntervalMs(                            \r\n\r\n\r\n",
              "success": true
            }
          ]
        }
      ],
      "ts": 1789804314659
    },
    {
      "id": "msg_89cBvXVy",
      "role": "assistant",
      "content": [
        {
          "type": "thinking",
          "thinking": "Good. So the channel handlers exist: showToast, setInstanceWindowTitle, setPollInterval. Now let me check setInstanceWindowTitle in flutter_window.cpp and see how it maps to com_bridge, and also check com_bridge.h for the method registrations.\n"
        },
        {
          "type": "text",
          "text": "setInstanceWindowTitle handler'ı var. Şimdi bu handler'ın com_bridge'e nasıl düştüğünü ve CATIA başlığını COM nesnesine nasıl set ettiğini görmem lazım.\n\n"
        },
        {
          "type": "tool_use",
          "id": "KkkWHMttap5qkfTozRn1ex19bRYlxVXf",
          "name": "run_commands",
          "input": {
            "commands": [
              "Get-Content 'd:\\__sistem__\\Masaustu\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\windows\\runner\\flutter_window.cpp' | Select-Object -Skip 753 -First 20"
            ]
          }
        }
      ],
      "ts": 1789804318136,
      "modelInfo": {
        "id": "ornith-1.5:35b",
        "provider": "ollama"
      },
      "metrics": {
        "inputTokens": 35148,
        "outputTokens": 175,
        "cacheReadTokens": 0,
        "cacheWriteTokens": 0
      }
    },
    {
      "id": "msg_HGMO11ed",
      "role": "user",
      "content": [
        {
          "type": "tool_result",
          "tool_use_id": "KkkWHMttap5qkfTozRn1ex19bRYlxVXf",
          "name": "run_commands",
          "content": [
            {
              "query": "Get-Content 'd:\\__sistem__\\Masaustu\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\windows\\runner\\flutter_window.cpp' | Select-Object -Skip 753 -First 20",
              "result": "        if (call.method_name() == \"setInstanceWindowTitle\") {\r\n          // ArgÃ¼man: {\"pid\": int, \"title\": String}. BoÅŸ title â†’ \"CATIA V5\".\r\n          const auto* args =\r\n              std::get_if<flutter::EncodableMap>(call.arguments());\r\n          const bool ok = SetWindowTitleOfPid(\r\n              static_cast<DWORD>(GetInt64Arg(args, \"pid\", 0)),\r\n              Utf8ToW(GetStringArg(args, \"title\")));\r\n          result->Success(flutter::EncodableValue(ok));\r\n          return;\r\n        }\r\n        if (call.method_name() == \"setPollInterval\") {\r\n          // ArgÃ¼man: {\"intervalMs\": int}. CATIA aÃ§Ä±k/aktif dÃ¶kÃ¼man izleme\r\n          // aralÄ±ÄŸÄ± (ms); 0 = kapalÄ±. Merkezi ayarlar penceresinden gelir.\r\n          const auto* args =\r\n              std::get_if<flutter::EncodableMap>(call.arguments());\r\n          fikocatia_com::SetPollIntervalMs(\r\n              static_cast<int>(GetInt64Arg(args, \"intervalMs\", 1500)));\r\n          result->Success(flutter::EncodableValue(true));\r\n          return;\r\n        }\r\n",
              "success": true
            }
          ]
        }
      ],
      "ts": 1789804318847
    },
    {
      "id": "msg_68Xxhf5m",
      "role": "assistant",
      "content": [
        {
          "type": "thinking",
          "thinking": "Şimdi SetWindowTitleOfPid fonksiyonunu görmem lazım. Bu flutter_window.cpp içinde olmalı.\n"
        },
        {
          "type": "text",
          "text": "Şimdi `SetWindowTitleOfPid`'in CATIA başlığını COM nesnesine set edip etmediğini kontrol edeyim — bu kritik çünkü CATIA'da gerçek pencere başlığını `SetTitle` ile değiştirmek gerekiyor.\n\n"
        },
        {
          "type": "tool_use",
          "id": "1lRPQTbmpXG7wwJX1G6aMjNeFsaNzLLv",
          "name": "run_commands",
          "input": {
            "commands": [
              "Select-String -Path 'd:\\__sistem__\\Masaustu\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\windows\\runner\\flutter_window.cpp' -Pattern 'SetWindowTitleOfPid' | Select-Object LineNumber; Get-Content 'd:\\__sistem__\\Masaustu\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\windows\\runner\\flutter_window.cpp' | Select-Object -Skip 640 -First 120 | Out-String"
            ]
          }
        }
      ],
      "ts": 1789804323147,
      "modelInfo": {
        "id": "ornith-1.5:35b",
        "provider": "ollama"
      },
      "metrics": {
        "inputTokens": 35678,
        "outputTokens": 222,
        "cacheReadTokens": 0,
        "cacheWriteTokens": 0
      }
    },
    {
      "id": "msg_jj2DpzYJ",
      "role": "user",
      "content": [
        {
          "type": "tool_result",
          "tool_use_id": "1lRPQTbmpXG7wwJX1G6aMjNeFsaNzLLv",
          "name": "run_commands",
          "content": [
            {
              "query": "Select-String -Path 'd:\\__sistem__\\Masaustu\\FikoYT.Yeni.Cag\\flutter\\fikatia_merkez\\windows\\runner\\flutter_window.cpp' -Pattern 'SetWindowTitleOfPid' | Select-Object LineNumber; Get-Content 'd:\\__siste ... [command truncated: 136 more chars; full command is in the tool call input]",
              "result": "\r\nLineNumber\r\n----------\r\n       365\r\n       758\r\n          int pid = 0;\r\n          std::wstring guidStr;\r\n          if (args) {\r\n            if (const auto* v = FindArg(*args, \"pid\")) {\r\n              if (const auto* i = std::get_if<int>(v)) pid = *i;\r\n            }\r\n            if (const auto* v = FindArg(*args, \"guid\")) {\r\n              if (const auto* s = std::get_if<std::string>(v)) {\r\n                guidStr = Utf8ToW(*s);\r\n              }\r\n            }\r\n          }\r\n          HRESULT hrMove = E_FAIL;\r\n          GUID guid{};\r\n          HWND hwnd = pid > 0 ? FindMainWindowOfPid(static_cast<DWORD>(pid))\r\n                              : nullptr;\r\n          if (!hwnd) {\r\n            hrMove = HRESULT_FROM_WIN32(ERROR_NOT_FOUND);\r\n          } else if (FAILED(::CLSIDFromString(guidStr.c_str(), &guid))) {\r\n            hrMove = E_INVALIDARG;\r\n          } else {\r\n            Microsoft::WRL::ComPtr<IVirtualDesktopManager> vdm;\r\n            hrMove = CoCreateInstance(__uuidof(VirtualDesktopManager),\r\n                                      nullptr, CLSCTX_ALL,\r\n                                      IID_PPV_ARGS(&vdm));\r\n            if (SUCCEEDED(hrMove) && vdm) {\r\n              hrMove = vdm->MoveWindowToDesktop(hwnd, guid);\r\n            }\r\n          }\r\n          {\r\n            wchar_t log[160];\r\n            StringCchPrintfW(log, 160,\r\n                              L\"moveWindowToDesktop pid=%d -> hr=0x%08X\", pid,\r\n                              static_cast<unsigned>(hrMove));\r\n            LogDesktopLine(log);\r\n          }\r\n          result->Success(flutter::EncodableValue(static_cast<int>(hrMove)));\r\n          return;\r\n        }\r\n        if (call.method_name() == \"pickSaveFile\" ||\r\n            call.method_name() == \"pickOpenFile\") {\r\n          // ArgÃ¼man: {\"title\",\"defaultName\",\"filterName\",\"ext\",\"allLabel\"}.\r\n          // Sistem Kaydet/AÃ§ diyaloÄŸu; seÃ§ilen yol (String) ya da null\r\n          // (vazgeÃ§ildi). Diyalog modal: kullanÄ±cÄ± seÃ§im yapana kadar bu\r\n          // Ã§aÄŸrÄ± cevap vermez, Dart tarafÄ± bu yÃ¼zden zaman aÅŸÄ±mÄ± koymaz.\r\n          const bool save = call.method_name() == \"pickSaveFile\";\r\n          const auto* args =\r\n              std::get_if<flutter::EncodableMap>(call.arguments());\r\n          std::wstring path;\r\n          HRESULT hrDlg = S_OK;\r\n          const bool picked = ShowFileDialog(\r\n              GetHandle(), save, Utf8ToW(GetStringArg(args, \"title\")),\r\n              Utf8ToW(GetStringArg(args, \"defaultName\")),\r\n              Utf8ToW(GetStringArg(args, \"filterName\")),\r\n              Utf8ToW(GetStringArg(args, \"ext\")),\r\n              Utf8ToW(GetStringArg(args, \"allLabel\")), &path, &hrDlg);\r\n          {\r\n            wchar_t log[160];\r\n            StringCchPrintfW(log, 160, L\"%s hr=0x%08X picked=%d\",\r\n                              save ? L\"pickSaveFile\" : L\"pickOpenFile\",\r\n                              static_cast<unsigned>(hrDlg), picked ? 1 : 0);\r\n            LogDesktopLine(log);\r\n          }\r\n          if (FAILED(hrDlg)) {\r\n            wchar_t msg[64];\r\n            StringCchPrintfW(msg, 64, L\"hr=0x%08X\",\r\n                              static_cast<unsigned>(hrDlg));\r\n            result->Error(\"dialog_failed\", WToUtf8(msg));\r\n            return;\r\n          }\r\n          if (!picked) {\r\n            result->Success(flutter::EncodableValue());\r\n          } else {\r\n            result->Success(flutter::EncodableValue(WToUtf8(path)));\r\n          }\r\n          return;\r\n        }\r\n        if (call.method_name() == \"listChildWindows\") {\r\n          // desktop_multi_window alt pencerelerinin HWND listesi (int64).\r\n          flutter::EncodableList list;\r\n          for (HWND h : FindChildFlutterWindows()) {\r\n            list.push_back(flutter::EncodableValue(\r\n                static_cast<int64_t>(reinterpret_cast<intptr_t>(h))));\r\n          }\r\n          result->Success(flutter::EncodableValue(list));\r\n          return;\r\n        }\r\n        if (call.method_name() == \"setupChildWindow\") {\r\n          // ArgÃ¼man: {\"hwnd\": int, \"width\": int, \"height\": int,\r\n          // \"title\": String, \"show\": bool}. Alt pencereyi kompakt boyuta\r\n          // getirir, ana ekranÄ±n ortasÄ±na koyar, X'i \"gizle\"ye Ã§evirir.\r\n          const auto* args =\r\n              std::get_if<flutter::EncodableMap>(call.arguments());\r\n          HWND h = reinterpret_cast<HWND>(\r\n              static_cast<intptr_t>(GetInt64Arg(args, \"hwnd\")));\r\n          const bool ok = SetupChildWindow(\r\n              h, static_cast<int>(GetInt64Arg(args, \"width\", 360)),\r\n              static_cast<int>(GetInt64Arg(args, \"height\", 520)),\r\n              Utf8ToW(GetStringArg(args, \"title\")),\r\n              GetBoolArg(args, \"show\", true));\r\n          result->Success(flutter::EncodableValue(ok));\r\n          return;\r\n        }\r\n        if (call.method_name() == \"showToast\") {\r\n          // ArgÃ¼man: {\"title\": String, \"subtitle\": String, \"durationMs\": int}.\r\n          const auto* args =\r\n              std::get_if<flutter::EncodableMap>(call.arguments());\r\n          ShowToast(Utf8ToW(GetStringArg(args, \"title\")),\r\n                    Utf8ToW(GetStringArg(args, \"subtitle\")),\r\n                    static_cast<int>(GetInt64Arg(args, \"durationMs\", 2000)));\r\n          result->Success(flutter::EncodableValue(true));\r\n          return;\r\n        }\r\n        if (call.method_name() == \"setInstanceWindowTitle\") {\r\n          // ArgÃ¼man: {\"pid\": int, \"title\": String}. BoÅŸ title â†’ \"CATIA V5\".\r\n          const auto* args =\r\n              std::get_if<flutter::EncodableMap>(call.arguments());\r\n          const bool ok = SetWindowTitleOfPid(\r\n              static_cast<DWORD>(GetInt64Arg(args, \"pid\", 0)),\r\n              Utf8ToW(GetStringArg(args, \"title\")));\r\n\r\n\r\n\r\n",
              "success": true
            }
          ]
        }
      ],
      "ts": 1789804323911
    }
  ]
}
